X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Factivitypub%2Factor.ts;h=19f3f7e04ef67279d33e5b66fb78284552302eb6;hb=91723454849076387176684f0d9c73deab824e20;hp=00e8dc9541614b04a6f154742d66cb39bac8ed56;hpb=7cd1b12c19d0589d1d692ed0571ca0800f028aea;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 00e8dc954..19f3f7e04 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts @@ -1,5 +1,6 @@ import { values } from 'lodash' import { extname } from 'path' +import { literal, Op, Transaction } from 'sequelize' import { AllowNull, BelongsTo, @@ -16,8 +17,9 @@ import { Table, UpdatedAt } from 'sequelize-typescript' +import { ModelCache } from '@server/models/model-cache' import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub' -import { Avatar } from '../../../shared/models/avatars/avatar.model' +import { ActorImage } from '../../../shared/models/actors/actor-image.model' import { activityPubContextify } from '../../helpers/activitypub' import { isActorFollowersCountValid, @@ -27,27 +29,34 @@ import { isActorPublicKeyValid } from '../../helpers/custom-validators/activitypub/actor' import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' -import { ACTIVITY_PUB, ACTIVITY_PUB_ACTOR_TYPES, CONSTRAINTS_FIELDS, SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants' -import { AccountModel } from '../account/account' -import { AvatarModel } from '../avatar/avatar' -import { ServerModel } from '../server/server' -import { isOutdated, throwIfNotValid } from '../utils' -import { VideoChannelModel } from '../video/video-channel' -import { ActorFollowModel } from './actor-follow' -import { VideoModel } from '../video/video' +import { + ACTIVITY_PUB, + ACTIVITY_PUB_ACTOR_TYPES, + CONSTRAINTS_FIELDS, + MIMETYPES, + SERVER_ACTOR_NAME, + WEBSERVER +} from '../../initializers/constants' import { MActor, MActorAccountChannelId, - MActorAP, + MActorAPAccount, + MActorAPChannel, MActorFormattable, MActorFull, MActorHost, MActorServer, - MActorSummaryFormattable, MActorUrl, + MActorSummaryFormattable, + MActorUrl, MActorWithInboxes -} from '../../typings/models' -import * as Bluebird from 'bluebird' -import { Op, Transaction, literal } from 'sequelize' +} from '../../types/models' +import { AccountModel } from '../account/account' +import { ActorImageModel } from '../account/actor-image' +import { ServerModel } from '../server/server' +import { isOutdated, throwIfNotValid } from '../utils' +import { VideoModel } from '../video/video' +import { VideoChannelModel } from '../video/video-channel' +import { ActorFollowModel } from './actor-follow' enum ScopeNames { FULL = 'FULL' @@ -61,7 +70,6 @@ export const unusedActorAttributesForAPI = [ 'sharedInboxUrl', 'followersUrl', 'followingUrl', - 'url', 'createdAt', 'updatedAt' ] @@ -73,7 +81,8 @@ export const unusedActorAttributesForAPI = [ required: false }, { - model: AvatarModel, + model: ActorImageModel, + as: 'Avatar', required: false } ] @@ -100,7 +109,13 @@ export const unusedActorAttributesForAPI = [ required: false }, { - model: AvatarModel, + model: ActorImageModel, + as: 'Avatar', + required: false + }, + { + model: ActorImageModel, + as: 'Banner', required: false } ] @@ -122,13 +137,13 @@ export const unusedActorAttributesForAPI = [ } } }, - // { - // fields: [ 'preferredUsername' ], - // unique: true, - // where: { - // serverId: null - // } - // }, + { + fields: [ 'preferredUsername' ], + unique: true, + where: { + serverId: null + } + }, { fields: [ 'inboxUrl', 'sharedInboxUrl' ] }, @@ -146,7 +161,7 @@ export const unusedActorAttributesForAPI = [ } ] }) -export class ActorModel extends Model { +export class ActorModel extends Model { @AllowNull(false) @Column(DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES))) @@ -213,18 +228,35 @@ export class ActorModel extends Model { @UpdatedAt updatedAt: Date - @ForeignKey(() => AvatarModel) + @ForeignKey(() => ActorImageModel) @Column avatarId: number - @BelongsTo(() => AvatarModel, { + @ForeignKey(() => ActorImageModel) + @Column + bannerId: number + + @BelongsTo(() => ActorImageModel, { foreignKey: { + name: 'avatarId', allowNull: true }, + as: 'Avatar', onDelete: 'set null', hooks: true }) - Avatar: AvatarModel + Avatar: ActorImageModel + + @BelongsTo(() => ActorImageModel, { + foreignKey: { + name: 'bannerId', + allowNull: true + }, + as: 'Banner', + onDelete: 'set null', + hooks: true + }) + Banner: ActorImageModel @HasMany(() => ActorFollowModel, { foreignKey: { @@ -276,18 +308,15 @@ export class ActorModel extends Model { }) VideoChannel: VideoChannelModel - private static localNameCache: { [ id: string ]: any } = {} - private static localUrlCache: { [ id: string ]: any } = {} - - static load (id: number): Bluebird { + static load (id: number): Promise { return ActorModel.unscoped().findByPk(id) } - static loadFull (id: number): Bluebird { + static loadFull (id: number): Promise { return ActorModel.scope(ScopeNames.FULL).findByPk(id) } - static loadFromAccountByVideoId (videoId: number, transaction: Transaction): Bluebird { + static loadFromAccountByVideoId (videoId: number, transaction: Transaction): Promise { const query = { include: [ { @@ -331,7 +360,7 @@ export class ActorModel extends Model { .then(a => !!a) } - static listByFollowersUrls (followersUrls: string[], transaction?: Transaction): Bluebird { + static listByFollowersUrls (followersUrls: string[], transaction?: Transaction): Promise { const query = { where: { followersUrl: { @@ -344,58 +373,54 @@ export class ActorModel extends Model { return ActorModel.scope(ScopeNames.FULL).findAll(query) } - static loadLocalByName (preferredUsername: string, transaction?: Transaction): Bluebird { - // The server actor never change, so we can easily cache it - if (preferredUsername === SERVER_ACTOR_NAME && ActorModel.localNameCache[preferredUsername]) { - return Bluebird.resolve(ActorModel.localNameCache[preferredUsername]) - } + static loadLocalByName (preferredUsername: string, transaction?: Transaction): Promise { + const fun = () => { + const query = { + where: { + preferredUsername, + serverId: null + }, + transaction + } - const query = { - where: { - preferredUsername, - serverId: null - }, - transaction + return ActorModel.scope(ScopeNames.FULL) + .findOne(query) } - return ActorModel.scope(ScopeNames.FULL) - .findOne(query) - .then(actor => { - if (preferredUsername === SERVER_ACTOR_NAME) { - ActorModel.localNameCache[preferredUsername] = actor - } - - return actor - }) + return ModelCache.Instance.doCache({ + cacheType: 'local-actor-name', + key: preferredUsername, + // The server actor never change, so we can easily cache it + whitelist: () => preferredUsername === SERVER_ACTOR_NAME, + fun + }) } - static loadLocalUrlByName (preferredUsername: string, transaction?: Transaction): Bluebird { - // The server actor never change, so we can easily cache it - if (preferredUsername === SERVER_ACTOR_NAME && ActorModel.localUrlCache[preferredUsername]) { - return Bluebird.resolve(ActorModel.localUrlCache[preferredUsername]) - } + static loadLocalUrlByName (preferredUsername: string, transaction?: Transaction): Promise { + const fun = () => { + const query = { + attributes: [ 'url' ], + where: { + preferredUsername, + serverId: null + }, + transaction + } - const query = { - attributes: [ 'url' ], - where: { - preferredUsername, - serverId: null - }, - transaction + return ActorModel.unscoped() + .findOne(query) } - return ActorModel.unscoped() - .findOne(query) - .then(actor => { - if (preferredUsername === SERVER_ACTOR_NAME) { - ActorModel.localUrlCache[preferredUsername] = actor - } - - return actor - }) + return ModelCache.Instance.doCache({ + cacheType: 'local-actor-name', + key: preferredUsername, + // The server actor never change, so we can easily cache it + whitelist: () => preferredUsername === SERVER_ACTOR_NAME, + fun + }) } - static loadByNameAndHost (preferredUsername: string, host: string): Bluebird { + static loadByNameAndHost (preferredUsername: string, host: string): Promise { const query = { where: { preferredUsername @@ -414,7 +439,7 @@ export class ActorModel extends Model { return ActorModel.scope(ScopeNames.FULL).findOne(query) } - static loadByUrl (url: string, transaction?: Transaction): Bluebird { + static loadByUrl (url: string, transaction?: Transaction): Promise { const query = { where: { url @@ -437,7 +462,7 @@ export class ActorModel extends Model { return ActorModel.unscoped().findOne(query) } - static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Transaction): Bluebird { + static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Transaction): Promise { const query = { where: { url @@ -468,12 +493,42 @@ export class ActorModel extends Model { }, { where, transaction }) } + static loadAccountActorByVideoId (videoId: number): Promise { + const query = { + include: [ + { + attributes: [ 'id' ], + model: AccountModel.unscoped(), + required: true, + include: [ + { + attributes: [ 'id', 'accountId' ], + model: VideoChannelModel.unscoped(), + required: true, + include: [ + { + attributes: [ 'id', 'channelId' ], + model: VideoModel.unscoped(), + where: { + id: videoId + } + } + ] + } + ] + } + ] + } + + return ActorModel.unscoped().findOne(query) + } + getSharedInbox (this: MActorWithInboxes) { return this.sharedInboxUrl || this.inboxUrl } toFormattedSummaryJSON (this: MActorSummaryFormattable) { - let avatar: Avatar = null + let avatar: ActorImage = null if (this.Avatar) { avatar = this.Avatar.toFormattedJSON() } @@ -489,29 +544,51 @@ export class ActorModel extends Model { toFormattedJSON (this: MActorFormattable) { const base = this.toFormattedSummaryJSON() + let banner: ActorImage = null + if (this.Banner) { + banner = this.Banner.toFormattedJSON() + } + return Object.assign(base, { id: this.id, hostRedundancyAllowed: this.getRedundancyAllowed(), followingCount: this.followingCount, followersCount: this.followersCount, + banner, createdAt: this.createdAt, updatedAt: this.updatedAt }) } - toActivityPubObject (this: MActorAP, name: string) { + toActivityPubObject (this: MActorAPChannel | MActorAPAccount, name: string) { let icon: ActivityIconObject + let image: ActivityIconObject if (this.avatarId) { const extension = extname(this.Avatar.filename) icon = { type: 'Image', - mediaType: extension === '.png' ? 'image/png' : 'image/jpeg', + mediaType: MIMETYPES.IMAGE.EXT_MIMETYPE[extension], + height: this.Avatar.height, + width: this.Avatar.width, url: this.getAvatarUrl() } } + if (this.bannerId) { + const banner = (this as MActorAPChannel).Banner + const extension = extname(banner.filename) + + image = { + type: 'Image', + mediaType: MIMETYPES.IMAGE.EXT_MIMETYPE[extension], + height: banner.height, + width: banner.width, + url: this.getBannerUrl() + } + } + const json = { type: this.type, id: this.url, @@ -531,7 +608,8 @@ export class ActorModel extends Model { owner: this.url, publicKeyPem: this.publicKey }, - icon + icon, + image } return activityPubContextify(json) @@ -601,6 +679,12 @@ export class ActorModel extends Model { return WEBSERVER.URL + this.Avatar.getStaticPath() } + getBannerUrl () { + if (!this.bannerId) return undefined + + return WEBSERVER.URL + this.Banner.getStaticPath() + } + isOutdated () { if (this.isOwned()) return false