From 7d9ba5c08999c6482f0bc5e0c09c6f55b7724090 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 11 May 2021 11:15:29 +0200 Subject: Cleanup models directory organization --- server/models/activitypub/actor-follow.ts | 721 ------------------------------ server/models/activitypub/actor.ts | 699 ----------------------------- 2 files changed, 1420 deletions(-) delete mode 100644 server/models/activitypub/actor-follow.ts delete mode 100644 server/models/activitypub/actor.ts (limited to 'server/models/activitypub') diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts deleted file mode 100644 index 4c5f37620..000000000 --- a/server/models/activitypub/actor-follow.ts +++ /dev/null @@ -1,721 +0,0 @@ -import { difference, values } from 'lodash' -import { IncludeOptions, Op, QueryTypes, Transaction, WhereOptions } from 'sequelize' -import { - AfterCreate, - AfterDestroy, - AfterUpdate, - AllowNull, - BelongsTo, - Column, - CreatedAt, - DataType, - Default, - ForeignKey, - Is, - IsInt, - Max, - Model, - Table, - UpdatedAt -} from 'sequelize-typescript' -import { isActivityPubUrlValid } from '@server/helpers/custom-validators/activitypub/misc' -import { getServerActor } from '@server/models/application/application' -import { VideoModel } from '@server/models/video/video' -import { - MActorFollowActorsDefault, - MActorFollowActorsDefaultSubscription, - MActorFollowFollowingHost, - MActorFollowFormattable, - MActorFollowSubscriptions -} from '@server/types/models' -import { ActivityPubActorType } from '@shared/models' -import { FollowState } from '../../../shared/models/actors' -import { ActorFollow } from '../../../shared/models/actors/follow.model' -import { logger } from '../../helpers/logger' -import { ACTOR_FOLLOW_SCORE, CONSTRAINTS_FIELDS, FOLLOW_STATES, SERVER_ACTOR_NAME } from '../../initializers/constants' -import { AccountModel } from '../account/account' -import { ServerModel } from '../server/server' -import { createSafeIn, getFollowsSort, getSort, searchAttribute, throwIfNotValid } from '../utils' -import { VideoChannelModel } from '../video/video-channel' -import { ActorModel, unusedActorAttributesForAPI } from './actor' - -@Table({ - tableName: 'actorFollow', - indexes: [ - { - fields: [ 'actorId' ] - }, - { - fields: [ 'targetActorId' ] - }, - { - fields: [ 'actorId', 'targetActorId' ], - unique: true - }, - { - fields: [ 'score' ] - }, - { - fields: [ 'url' ], - unique: true - } - ] -}) -export class ActorFollowModel extends Model { - - @AllowNull(false) - @Column(DataType.ENUM(...values(FOLLOW_STATES))) - state: FollowState - - @AllowNull(false) - @Default(ACTOR_FOLLOW_SCORE.BASE) - @IsInt - @Max(ACTOR_FOLLOW_SCORE.MAX) - @Column - score: number - - // Allow null because we added this column in PeerTube v3, and don't want to generate fake URLs of remote follows - @AllowNull(true) - @Is('ActorFollowUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.COMMONS.URL.max)) - url: string - - @CreatedAt - createdAt: Date - - @UpdatedAt - updatedAt: Date - - @ForeignKey(() => ActorModel) - @Column - actorId: number - - @BelongsTo(() => ActorModel, { - foreignKey: { - name: 'actorId', - allowNull: false - }, - as: 'ActorFollower', - onDelete: 'CASCADE' - }) - ActorFollower: ActorModel - - @ForeignKey(() => ActorModel) - @Column - targetActorId: number - - @BelongsTo(() => ActorModel, { - foreignKey: { - name: 'targetActorId', - allowNull: false - }, - as: 'ActorFollowing', - onDelete: 'CASCADE' - }) - ActorFollowing: ActorModel - - @AfterCreate - @AfterUpdate - static incrementFollowerAndFollowingCount (instance: ActorFollowModel, options: any) { - if (instance.state !== 'accepted') return undefined - - return Promise.all([ - ActorModel.rebuildFollowsCount(instance.actorId, 'following', options.transaction), - ActorModel.rebuildFollowsCount(instance.targetActorId, 'followers', options.transaction) - ]) - } - - @AfterDestroy - static decrementFollowerAndFollowingCount (instance: ActorFollowModel, options: any) { - return Promise.all([ - ActorModel.rebuildFollowsCount(instance.actorId, 'following', options.transaction), - ActorModel.rebuildFollowsCount(instance.targetActorId, 'followers', options.transaction) - ]) - } - - static removeFollowsOf (actorId: number, t?: Transaction) { - const query = { - where: { - [Op.or]: [ - { - actorId - }, - { - targetActorId: actorId - } - ] - }, - transaction: t - } - - return ActorFollowModel.destroy(query) - } - - // Remove actor follows with a score of 0 (too many requests where they were unreachable) - static async removeBadActorFollows () { - const actorFollows = await ActorFollowModel.listBadActorFollows() - - const actorFollowsRemovePromises = actorFollows.map(actorFollow => actorFollow.destroy()) - await Promise.all(actorFollowsRemovePromises) - - const numberOfActorFollowsRemoved = actorFollows.length - - if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved) - } - - static isFollowedBy (actorId: number, followerActorId: number) { - const query = 'SELECT 1 FROM "actorFollow" WHERE "actorId" = $followerActorId AND "targetActorId" = $actorId LIMIT 1' - const options = { - type: QueryTypes.SELECT as QueryTypes.SELECT, - bind: { actorId, followerActorId }, - raw: true - } - - return VideoModel.sequelize.query(query, options) - .then(results => results.length === 1) - } - - static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction): Promise { - const query = { - where: { - actorId, - targetActorId: targetActorId - }, - include: [ - { - model: ActorModel, - required: true, - as: 'ActorFollower' - }, - { - model: ActorModel, - required: true, - as: 'ActorFollowing' - } - ], - transaction: t - } - - return ActorFollowModel.findOne(query) - } - - static loadByActorAndTargetNameAndHostForAPI ( - actorId: number, - targetName: string, - targetHost: string, - t?: Transaction - ): Promise { - const actorFollowingPartInclude: IncludeOptions = { - model: ActorModel, - required: true, - as: 'ActorFollowing', - where: { - preferredUsername: targetName - }, - include: [ - { - model: VideoChannelModel.unscoped(), - required: false - } - ] - } - - if (targetHost === null) { - actorFollowingPartInclude.where['serverId'] = null - } else { - actorFollowingPartInclude.include.push({ - model: ServerModel, - required: true, - where: { - host: targetHost - } - }) - } - - const query = { - where: { - actorId - }, - include: [ - actorFollowingPartInclude, - { - model: ActorModel, - required: true, - as: 'ActorFollower' - } - ], - transaction: t - } - - return ActorFollowModel.findOne(query) - } - - static listSubscribedIn (actorId: number, targets: { name: string, host?: string }[]): Promise { - const whereTab = targets - .map(t => { - if (t.host) { - return { - [Op.and]: [ - { - $preferredUsername$: t.name - }, - { - $host$: t.host - } - ] - } - } - - return { - [Op.and]: [ - { - $preferredUsername$: t.name - }, - { - $serverId$: null - } - ] - } - }) - - const query = { - attributes: [ 'id' ], - where: { - [Op.and]: [ - { - [Op.or]: whereTab - }, - { - actorId - } - ] - }, - include: [ - { - attributes: [ 'preferredUsername' ], - model: ActorModel.unscoped(), - required: true, - as: 'ActorFollowing', - include: [ - { - attributes: [ 'host' ], - model: ServerModel.unscoped(), - required: false - } - ] - } - ] - } - - return ActorFollowModel.findAll(query) - } - - static listFollowingForApi (options: { - id: number - start: number - count: number - sort: string - state?: FollowState - actorType?: ActivityPubActorType - search?: string - }) { - const { id, start, count, sort, search, state, actorType } = options - - const followWhere = state ? { state } : {} - const followingWhere: WhereOptions = {} - const followingServerWhere: WhereOptions = {} - - if (search) { - Object.assign(followingServerWhere, { - host: { - [Op.iLike]: '%' + search + '%' - } - }) - } - - if (actorType) { - Object.assign(followingWhere, { type: actorType }) - } - - const query = { - distinct: true, - offset: start, - limit: count, - order: getFollowsSort(sort), - where: followWhere, - include: [ - { - model: ActorModel, - required: true, - as: 'ActorFollower', - where: { - id - } - }, - { - model: ActorModel, - as: 'ActorFollowing', - required: true, - where: followingWhere, - include: [ - { - model: ServerModel, - required: true, - where: followingServerWhere - } - ] - } - ] - } - - return ActorFollowModel.findAndCountAll(query) - .then(({ rows, count }) => { - return { - data: rows, - total: count - } - }) - } - - static listFollowersForApi (options: { - actorId: number - start: number - count: number - sort: string - state?: FollowState - actorType?: ActivityPubActorType - search?: string - }) { - const { actorId, start, count, sort, search, state, actorType } = options - - const followWhere = state ? { state } : {} - const followerWhere: WhereOptions = {} - const followerServerWhere: WhereOptions = {} - - if (search) { - Object.assign(followerServerWhere, { - host: { - [Op.iLike]: '%' + search + '%' - } - }) - } - - if (actorType) { - Object.assign(followerWhere, { type: actorType }) - } - - const query = { - distinct: true, - offset: start, - limit: count, - order: getFollowsSort(sort), - where: followWhere, - include: [ - { - model: ActorModel, - required: true, - as: 'ActorFollower', - where: followerWhere, - include: [ - { - model: ServerModel, - required: true, - where: followerServerWhere - } - ] - }, - { - model: ActorModel, - as: 'ActorFollowing', - required: true, - where: { - id: actorId - } - } - ] - } - - return ActorFollowModel.findAndCountAll(query) - .then(({ rows, count }) => { - return { - data: rows, - total: count - } - }) - } - - static listSubscriptionsForApi (options: { - actorId: number - start: number - count: number - sort: string - search?: string - }) { - const { actorId, start, count, sort } = options - const where = { - actorId: actorId - } - - if (options.search) { - Object.assign(where, { - [Op.or]: [ - searchAttribute(options.search, '$ActorFollowing.preferredUsername$'), - searchAttribute(options.search, '$ActorFollowing.VideoChannel.name$') - ] - }) - } - - const query = { - attributes: [], - distinct: true, - offset: start, - limit: count, - order: getSort(sort), - where, - include: [ - { - attributes: [ 'id' ], - model: ActorModel.unscoped(), - as: 'ActorFollowing', - required: true, - include: [ - { - model: VideoChannelModel.unscoped(), - required: true, - include: [ - { - attributes: { - exclude: unusedActorAttributesForAPI - }, - model: ActorModel, - required: true - }, - { - model: AccountModel.unscoped(), - required: true, - include: [ - { - attributes: { - exclude: unusedActorAttributesForAPI - }, - model: ActorModel, - required: true - } - ] - } - ] - } - ] - } - ] - } - - return ActorFollowModel.findAndCountAll(query) - .then(({ rows, count }) => { - return { - data: rows.map(r => r.ActorFollowing.VideoChannel), - total: count - } - }) - } - - static async keepUnfollowedInstance (hosts: string[]) { - const followerId = (await getServerActor()).id - - const query = { - attributes: [ 'id' ], - where: { - actorId: followerId - }, - include: [ - { - attributes: [ 'id' ], - model: ActorModel.unscoped(), - required: true, - as: 'ActorFollowing', - where: { - preferredUsername: SERVER_ACTOR_NAME - }, - include: [ - { - attributes: [ 'host' ], - model: ServerModel.unscoped(), - required: true, - where: { - host: { - [Op.in]: hosts - } - } - } - ] - } - ] - } - - const res = await ActorFollowModel.findAll(query) - const followedHosts = res.map(row => row.ActorFollowing.Server.host) - - return difference(hosts, followedHosts) - } - - static listAcceptedFollowerUrlsForAP (actorIds: number[], t: Transaction, start?: number, count?: number) { - return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count) - } - - static listAcceptedFollowerSharedInboxUrls (actorIds: number[], t: Transaction) { - return ActorFollowModel.createListAcceptedFollowForApiQuery( - 'followers', - actorIds, - t, - undefined, - undefined, - 'sharedInboxUrl', - true - ) - } - - static listAcceptedFollowingUrlsForApi (actorIds: number[], t: Transaction, start?: number, count?: number) { - return ActorFollowModel.createListAcceptedFollowForApiQuery('following', actorIds, t, start, count) - } - - static async getStats () { - const serverActor = await getServerActor() - - const totalInstanceFollowing = await ActorFollowModel.count({ - where: { - actorId: serverActor.id - } - }) - - const totalInstanceFollowers = await ActorFollowModel.count({ - where: { - targetActorId: serverActor.id - } - }) - - return { - totalInstanceFollowing, - totalInstanceFollowers - } - } - - static updateScore (inboxUrl: string, value: number, t?: Transaction) { - const query = `UPDATE "actorFollow" SET "score" = LEAST("score" + ${value}, ${ACTOR_FOLLOW_SCORE.MAX}) ` + - 'WHERE id IN (' + - 'SELECT "actorFollow"."id" FROM "actorFollow" ' + - 'INNER JOIN "actor" ON "actor"."id" = "actorFollow"."actorId" ' + - `WHERE "actor"."inboxUrl" = '${inboxUrl}' OR "actor"."sharedInboxUrl" = '${inboxUrl}'` + - ')' - - const options = { - type: QueryTypes.BULKUPDATE, - transaction: t - } - - return ActorFollowModel.sequelize.query(query, options) - } - - static async updateScoreByFollowingServers (serverIds: number[], value: number, t?: Transaction) { - if (serverIds.length === 0) return - - const me = await getServerActor() - const serverIdsString = createSafeIn(ActorFollowModel, serverIds) - - const query = `UPDATE "actorFollow" SET "score" = LEAST("score" + ${value}, ${ACTOR_FOLLOW_SCORE.MAX}) ` + - 'WHERE id IN (' + - 'SELECT "actorFollow"."id" FROM "actorFollow" ' + - 'INNER JOIN "actor" ON "actor"."id" = "actorFollow"."targetActorId" ' + - `WHERE "actorFollow"."actorId" = ${me.Account.actorId} ` + // I'm the follower - `AND "actor"."serverId" IN (${serverIdsString})` + // Criteria on followings - ')' - - const options = { - type: QueryTypes.BULKUPDATE, - transaction: t - } - - return ActorFollowModel.sequelize.query(query, options) - } - - private static async createListAcceptedFollowForApiQuery ( - type: 'followers' | 'following', - actorIds: number[], - t: Transaction, - start?: number, - count?: number, - columnUrl = 'url', - distinct = false - ) { - let firstJoin: string - let secondJoin: string - - if (type === 'followers') { - firstJoin = 'targetActorId' - secondJoin = 'actorId' - } else { - firstJoin = 'actorId' - secondJoin = 'targetActorId' - } - - const selections: string[] = [] - if (distinct === true) selections.push(`DISTINCT("Follows"."${columnUrl}") AS "selectionUrl"`) - else selections.push(`"Follows"."${columnUrl}" AS "selectionUrl"`) - - selections.push('COUNT(*) AS "total"') - - const tasks: Promise[] = [] - - for (const selection of selections) { - let query = 'SELECT ' + selection + ' FROM "actor" ' + - 'INNER JOIN "actorFollow" ON "actorFollow"."' + firstJoin + '" = "actor"."id" ' + - 'INNER JOIN "actor" AS "Follows" ON "actorFollow"."' + secondJoin + '" = "Follows"."id" ' + - `WHERE "actor"."id" = ANY ($actorIds) AND "actorFollow"."state" = 'accepted' AND "Follows"."${columnUrl}" IS NOT NULL ` - - if (count !== undefined) query += 'LIMIT ' + count - if (start !== undefined) query += ' OFFSET ' + start - - const options = { - bind: { actorIds }, - type: QueryTypes.SELECT, - transaction: t - } - tasks.push(ActorFollowModel.sequelize.query(query, options)) - } - - const [ followers, [ dataTotal ] ] = await Promise.all(tasks) - const urls: string[] = followers.map(f => f.selectionUrl) - - return { - data: urls, - total: dataTotal ? parseInt(dataTotal.total, 10) : 0 - } - } - - private static listBadActorFollows () { - const query = { - where: { - score: { - [Op.lte]: 0 - } - }, - logging: false - } - - return ActorFollowModel.findAll(query) - } - - toFormattedJSON (this: MActorFollowFormattable): ActorFollow { - const follower = this.ActorFollower.toFormattedJSON() - const following = this.ActorFollowing.toFormattedJSON() - - return { - id: this.id, - follower, - following, - score: this.score, - state: this.state, - createdAt: this.createdAt, - updatedAt: this.updatedAt - } - } -} diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts deleted file mode 100644 index 1af9efac2..000000000 --- a/server/models/activitypub/actor.ts +++ /dev/null @@ -1,699 +0,0 @@ -import { values } from 'lodash' -import { extname } from 'path' -import { literal, Op, Transaction } from 'sequelize' -import { - AllowNull, - BelongsTo, - Column, - CreatedAt, - DataType, - DefaultScope, - ForeignKey, - HasMany, - HasOne, - Is, - Model, - Scopes, - Table, - UpdatedAt -} from 'sequelize-typescript' -import { ModelCache } from '@server/models/model-cache' -import { ActivityIconObject, ActivityPubActorType } from '../../../shared/models/activitypub' -import { ActorImage } from '../../../shared/models/actors/actor-image.model' -import { activityPubContextify } from '../../helpers/activitypub' -import { - isActorFollowersCountValid, - isActorFollowingCountValid, - isActorPreferredUsernameValid, - isActorPrivateKeyValid, - isActorPublicKeyValid -} from '../../helpers/custom-validators/activitypub/actor' -import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' -import { - ACTIVITY_PUB, - ACTIVITY_PUB_ACTOR_TYPES, - CONSTRAINTS_FIELDS, - MIMETYPES, - SERVER_ACTOR_NAME, - WEBSERVER -} from '../../initializers/constants' -import { - MActor, - MActorAccountChannelId, - MActorAPAccount, - MActorAPChannel, - MActorFormattable, - MActorFull, - MActorHost, - MActorServer, - MActorSummaryFormattable, - MActorUrl, - MActorWithInboxes -} 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' -} - -export const unusedActorAttributesForAPI = [ - 'publicKey', - 'privateKey', - 'inboxUrl', - 'outboxUrl', - 'sharedInboxUrl', - 'followersUrl', - 'followingUrl' -] - -@DefaultScope(() => ({ - include: [ - { - model: ServerModel, - required: false - }, - { - model: ActorImageModel, - as: 'Avatar', - required: false - } - ] -})) -@Scopes(() => ({ - [ScopeNames.FULL]: { - include: [ - { - model: AccountModel.unscoped(), - required: false - }, - { - model: VideoChannelModel.unscoped(), - required: false, - include: [ - { - model: AccountModel, - required: true - } - ] - }, - { - model: ServerModel, - required: false - }, - { - model: ActorImageModel, - as: 'Avatar', - required: false - }, - { - model: ActorImageModel, - as: 'Banner', - required: false - } - ] - } -})) -@Table({ - tableName: 'actor', - indexes: [ - { - fields: [ 'url' ], - unique: true - }, - { - fields: [ 'preferredUsername', 'serverId' ], - unique: true, - where: { - serverId: { - [Op.ne]: null - } - } - }, - { - fields: [ 'preferredUsername' ], - unique: true, - where: { - serverId: null - } - }, - { - fields: [ 'inboxUrl', 'sharedInboxUrl' ] - }, - { - fields: [ 'sharedInboxUrl' ] - }, - { - fields: [ 'serverId' ] - }, - { - fields: [ 'avatarId' ] - }, - { - fields: [ 'followersUrl' ] - } - ] -}) -export class ActorModel extends Model { - - @AllowNull(false) - @Column(DataType.ENUM(...values(ACTIVITY_PUB_ACTOR_TYPES))) - type: ActivityPubActorType - - @AllowNull(false) - @Is('ActorPreferredUsername', value => throwIfNotValid(value, isActorPreferredUsernameValid, 'actor preferred username')) - @Column - preferredUsername: string - - @AllowNull(false) - @Is('ActorUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) - url: string - - @AllowNull(true) - @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPublicKeyValid, 'public key', true)) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY.max)) - publicKey: string - - @AllowNull(true) - @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPrivateKeyValid, 'private key', true)) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY.max)) - privateKey: string - - @AllowNull(false) - @Is('ActorFollowersCount', value => throwIfNotValid(value, isActorFollowersCountValid, 'followers count')) - @Column - followersCount: number - - @AllowNull(false) - @Is('ActorFollowersCount', value => throwIfNotValid(value, isActorFollowingCountValid, 'following count')) - @Column - followingCount: number - - @AllowNull(false) - @Is('ActorInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'inbox url')) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) - inboxUrl: string - - @AllowNull(true) - @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url', true)) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) - outboxUrl: string - - @AllowNull(true) - @Is('ActorSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url', true)) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) - sharedInboxUrl: string - - @AllowNull(true) - @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url', true)) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) - followersUrl: string - - @AllowNull(true) - @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url', true)) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) - followingUrl: string - - @AllowNull(true) - @Column - remoteCreatedAt: Date - - @CreatedAt - createdAt: Date - - @UpdatedAt - updatedAt: Date - - @ForeignKey(() => ActorImageModel) - @Column - avatarId: number - - @ForeignKey(() => ActorImageModel) - @Column - bannerId: number - - @BelongsTo(() => ActorImageModel, { - foreignKey: { - name: 'avatarId', - allowNull: true - }, - as: 'Avatar', - onDelete: 'set null', - hooks: true - }) - Avatar: ActorImageModel - - @BelongsTo(() => ActorImageModel, { - foreignKey: { - name: 'bannerId', - allowNull: true - }, - as: 'Banner', - onDelete: 'set null', - hooks: true - }) - Banner: ActorImageModel - - @HasMany(() => ActorFollowModel, { - foreignKey: { - name: 'actorId', - allowNull: false - }, - as: 'ActorFollowings', - onDelete: 'cascade' - }) - ActorFollowing: ActorFollowModel[] - - @HasMany(() => ActorFollowModel, { - foreignKey: { - name: 'targetActorId', - allowNull: false - }, - as: 'ActorFollowers', - onDelete: 'cascade' - }) - ActorFollowers: ActorFollowModel[] - - @ForeignKey(() => ServerModel) - @Column - serverId: number - - @BelongsTo(() => ServerModel, { - foreignKey: { - allowNull: true - }, - onDelete: 'cascade' - }) - Server: ServerModel - - @HasOne(() => AccountModel, { - foreignKey: { - allowNull: true - }, - onDelete: 'cascade', - hooks: true - }) - Account: AccountModel - - @HasOne(() => VideoChannelModel, { - foreignKey: { - allowNull: true - }, - onDelete: 'cascade', - hooks: true - }) - VideoChannel: VideoChannelModel - - static load (id: number): Promise { - return ActorModel.unscoped().findByPk(id) - } - - static loadFull (id: number): Promise { - return ActorModel.scope(ScopeNames.FULL).findByPk(id) - } - - static loadFromAccountByVideoId (videoId: number, transaction: Transaction): Promise { - const query = { - include: [ - { - attributes: [ 'id' ], - model: AccountModel.unscoped(), - required: true, - include: [ - { - attributes: [ 'id' ], - model: VideoChannelModel.unscoped(), - required: true, - include: [ - { - attributes: [ 'id' ], - model: VideoModel.unscoped(), - required: true, - where: { - id: videoId - } - } - ] - } - ] - } - ], - transaction - } - - return ActorModel.unscoped().findOne(query) - } - - static isActorUrlExist (url: string) { - const query = { - raw: true, - where: { - url - } - } - - return ActorModel.unscoped().findOne(query) - .then(a => !!a) - } - - static listByFollowersUrls (followersUrls: string[], transaction?: Transaction): Promise { - const query = { - where: { - followersUrl: { - [Op.in]: followersUrls - } - }, - transaction - } - - return ActorModel.scope(ScopeNames.FULL).findAll(query) - } - - static loadLocalByName (preferredUsername: string, transaction?: Transaction): Promise { - const fun = () => { - const query = { - where: { - preferredUsername, - serverId: null - }, - transaction - } - - return ActorModel.scope(ScopeNames.FULL) - .findOne(query) - } - - 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): Promise { - const fun = () => { - const query = { - attributes: [ 'url' ], - where: { - preferredUsername, - serverId: null - }, - transaction - } - - return ActorModel.unscoped() - .findOne(query) - } - - 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): Promise { - const query = { - where: { - preferredUsername - }, - include: [ - { - model: ServerModel, - required: true, - where: { - host - } - } - ] - } - - return ActorModel.scope(ScopeNames.FULL).findOne(query) - } - - static loadByUrl (url: string, transaction?: Transaction): Promise { - const query = { - where: { - url - }, - transaction, - include: [ - { - attributes: [ 'id' ], - model: AccountModel.unscoped(), - required: false - }, - { - attributes: [ 'id' ], - model: VideoChannelModel.unscoped(), - required: false - } - ] - } - - return ActorModel.unscoped().findOne(query) - } - - static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Transaction): Promise { - const query = { - where: { - url - }, - transaction - } - - return ActorModel.scope(ScopeNames.FULL).findOne(query) - } - - static rebuildFollowsCount (ofId: number, type: 'followers' | 'following', transaction?: Transaction) { - const sanitizedOfId = parseInt(ofId + '', 10) - const where = { id: sanitizedOfId } - - let columnToUpdate: string - let columnOfCount: string - - if (type === 'followers') { - columnToUpdate = 'followersCount' - columnOfCount = 'targetActorId' - } else { - columnToUpdate = 'followingCount' - columnOfCount = 'actorId' - } - - return ActorModel.update({ - [columnToUpdate]: literal(`(SELECT COUNT(*) FROM "actorFollow" WHERE "${columnOfCount}" = ${sanitizedOfId})`) - }, { 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: ActorImage = null - if (this.Avatar) { - avatar = this.Avatar.toFormattedJSON() - } - - return { - url: this.url, - name: this.preferredUsername, - host: this.getHost(), - avatar - } - } - - 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.getCreatedAt() - }) - } - - 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: 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, - following: this.getFollowingUrl(), - followers: this.getFollowersUrl(), - playlists: this.getPlaylistsUrl(), - inbox: this.inboxUrl, - outbox: this.outboxUrl, - preferredUsername: this.preferredUsername, - url: this.url, - name, - endpoints: { - sharedInbox: this.sharedInboxUrl - }, - publicKey: { - id: this.getPublicKeyUrl(), - owner: this.url, - publicKeyPem: this.publicKey - }, - published: this.getCreatedAt().toISOString(), - icon, - image - } - - return activityPubContextify(json) - } - - getFollowerSharedInboxUrls (t: Transaction) { - const query = { - attributes: [ 'sharedInboxUrl' ], - include: [ - { - attribute: [], - model: ActorFollowModel.unscoped(), - required: true, - as: 'ActorFollowing', - where: { - state: 'accepted', - targetActorId: this.id - } - } - ], - transaction: t - } - - return ActorModel.findAll(query) - .then(accounts => accounts.map(a => a.sharedInboxUrl)) - } - - getFollowingUrl () { - return this.url + '/following' - } - - getFollowersUrl () { - return this.url + '/followers' - } - - getPlaylistsUrl () { - return this.url + '/playlists' - } - - getPublicKeyUrl () { - return this.url + '#main-key' - } - - isOwned () { - return this.serverId === null - } - - getWebfingerUrl (this: MActorServer) { - return 'acct:' + this.preferredUsername + '@' + this.getHost() - } - - getIdentifier () { - return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername - } - - getHost (this: MActorHost) { - return this.Server ? this.Server.host : WEBSERVER.HOST - } - - getRedundancyAllowed () { - return this.Server ? this.Server.redundancyAllowed : false - } - - getAvatarUrl () { - if (!this.avatarId) return undefined - - return WEBSERVER.URL + this.Avatar.getStaticPath() - } - - getBannerUrl () { - if (!this.bannerId) return undefined - - return WEBSERVER.URL + this.Banner.getStaticPath() - } - - isOutdated () { - if (this.isOwned()) return false - - return isOutdated(this, ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL) - } - - getCreatedAt (this: MActorAPChannel | MActorAPAccount | MActorFormattable) { - return this.remoteCreatedAt || this.createdAt - } -} -- cgit v1.2.3