X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;ds=sidebyside;f=server%2Fmodels%2Factivitypub%2Factor.ts;h=f8bb593239dbbe47792f193ad66766d394f9f945;hb=d1a63fc7ac58a1db00d8ca4f43aadba02eb9b084;hp=8422653df716c8cf3d0bef162f967ee728bcd45f;hpb=c5911fd347c76e8bdc05ea9f3ee9efed4a58c236;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 8422653df..f8bb59323 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts @@ -1,19 +1,36 @@ import { values } from 'lodash' -import { extname, join } from 'path' +import { extname } from 'path' import * as Sequelize from 'sequelize' import { - AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, DefaultScope, ForeignKey, HasMany, HasOne, Is, IsUUID, Model, Scopes, - Table, UpdatedAt + AllowNull, + BelongsTo, + Column, + CreatedAt, + DataType, + Default, + DefaultScope, + ForeignKey, + HasMany, + HasOne, + Is, + IsUUID, + Model, + Scopes, + Table, + UpdatedAt } from 'sequelize-typescript' import { ActivityPubActorType } from '../../../shared/models/activitypub' import { Avatar } from '../../../shared/models/avatars/avatar.model' import { activityPubContextify } from '../../helpers/activitypub' import { - isActorFollowersCountValid, isActorFollowingCountValid, isActorPreferredUsernameValid, isActorPrivateKeyValid, + isActorFollowersCountValid, + isActorFollowingCountValid, + isActorPreferredUsernameValid, + isActorPrivateKeyValid, isActorPublicKeyValid } from '../../helpers/custom-validators/activitypub/actor' import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' -import { ACTIVITY_PUB_ACTOR_TYPES, AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' +import { ACTIVITY_PUB, ACTIVITY_PUB_ACTOR_TYPES, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' import { AccountModel } from '../account/account' import { AvatarModel } from '../avatar/avatar' import { ServerModel } from '../server/server' @@ -25,6 +42,19 @@ enum ScopeNames { FULL = 'FULL' } +export const unusedActorAttributesForAPI = [ + 'publicKey', + 'privateKey', + 'inboxUrl', + 'outboxUrl', + 'sharedInboxUrl', + 'followersUrl', + 'followingUrl', + 'url', + 'createdAt', + 'updatedAt' +] + @DefaultScope({ include: [ { @@ -41,12 +71,18 @@ enum ScopeNames { [ScopeNames.FULL]: { include: [ { - model: () => AccountModel, + model: () => AccountModel.unscoped(), required: false }, { - model: () => VideoChannelModel, - required: false + model: () => VideoChannelModel.unscoped(), + required: false, + include: [ + { + model: () => AccountModel, + required: true + } + ] }, { model: () => ServerModel, @@ -62,9 +98,32 @@ enum ScopeNames { @Table({ tableName: 'actor', indexes: [ + { + fields: [ 'url' ], + unique: true + }, { fields: [ 'preferredUsername', 'serverId' ], unique: true + }, + { + fields: [ 'inboxUrl', 'sharedInboxUrl' ] + }, + { + fields: [ 'sharedInboxUrl' ] + }, + { + fields: [ 'serverId' ] + }, + { + fields: [ 'avatarId' ] + }, + { + fields: [ 'uuid' ], + unique: true + }, + { + fields: [ 'followersUrl' ] } ] }) @@ -87,17 +146,17 @@ export class ActorModel extends Model { @AllowNull(false) @Is('ActorUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url')) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max)) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) url: string @AllowNull(true) @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPublicKeyValid, 'public key')) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.PUBLIC_KEY.max)) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY.max)) publicKey: string @AllowNull(true) @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPrivateKeyValid, 'private key')) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.PRIVATE_KEY.max)) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.PRIVATE_KEY.max)) privateKey: string @AllowNull(false) @@ -112,27 +171,27 @@ export class ActorModel extends Model { @AllowNull(false) @Is('ActorInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'inbox url')) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max)) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) inboxUrl: string @AllowNull(false) @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url')) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max)) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) outboxUrl: string @AllowNull(false) @Is('ActorSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url')) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max)) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) sharedInboxUrl: string @AllowNull(false) @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url')) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max)) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) followersUrl: string @AllowNull(false) @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url')) - @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max)) + @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) followingUrl: string @CreatedAt @@ -149,7 +208,8 @@ export class ActorModel extends Model { foreignKey: { allowNull: true }, - onDelete: 'set null' + onDelete: 'set null', + hooks: true }) Avatar: AvatarModel @@ -160,17 +220,17 @@ export class ActorModel extends Model { }, onDelete: 'cascade' }) - AccountFollowing: ActorFollowModel[] + ActorFollowing: ActorFollowModel[] @HasMany(() => ActorFollowModel, { foreignKey: { name: 'targetActorId', allowNull: false }, - as: 'followers', + as: 'ActorFollowers', onDelete: 'cascade' }) - AccountFollowers: ActorFollowModel[] + ActorFollowers: ActorFollowModel[] @ForeignKey(() => ServerModel) @Column @@ -188,7 +248,8 @@ export class ActorModel extends Model { foreignKey: { allowNull: true }, - onDelete: 'cascade' + onDelete: 'cascade', + hooks: true }) Account: AccountModel @@ -196,12 +257,25 @@ export class ActorModel extends Model { foreignKey: { allowNull: true }, - onDelete: 'cascade' + onDelete: 'cascade', + hooks: true }) VideoChannel: VideoChannelModel static load (id: number) { - return ActorModel.scope(ScopeNames.FULL).findById(id) + return ActorModel.unscoped().findById(id) + } + + static isActorUrlExist (url: string) { + const query = { + raw: true, + where: { + url + } + } + + return ActorModel.unscoped().findOne(query) + .then(a => !!a) } static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { @@ -217,12 +291,13 @@ export class ActorModel extends Model { return ActorModel.scope(ScopeNames.FULL).findAll(query) } - static loadLocalByName (preferredUsername: string) { + static loadLocalByName (preferredUsername: string, transaction?: Sequelize.Transaction) { const query = { where: { preferredUsername, serverId: null - } + }, + transaction } return ActorModel.scope(ScopeNames.FULL).findOne(query) @@ -248,6 +323,29 @@ export class ActorModel extends Model { } static loadByUrl (url: string, transaction?: Sequelize.Transaction) { + 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?: Sequelize.Transaction) { const query = { where: { url @@ -258,25 +356,34 @@ export class ActorModel extends Model { return ActorModel.scope(ScopeNames.FULL).findOne(query) } + static incrementFollows (id: number, column: 'followersCount' | 'followingCount', by: number) { + // FIXME: typings + return (ActorModel as any).increment(column, { + by, + where: { + id + } + }) + } + toFormattedJSON () { let avatar: Avatar = null if (this.Avatar) { avatar = this.Avatar.toFormattedJSON() } - let score: number - if (this.Server) { - score = this.Server.score - } - return { id: this.id, + url: this.url, uuid: this.uuid, + name: this.preferredUsername, host: this.getHost(), - score, + hostRedundancyAllowed: this.getRedundancyAllowed(), followingCount: this.followingCount, followersCount: this.followersCount, - avatar + avatar, + createdAt: this.createdAt, + updatedAt: this.updatedAt } } @@ -330,10 +437,12 @@ export class ActorModel extends Model { attributes: [ 'sharedInboxUrl' ], include: [ { - model: ActorFollowModel, + attribute: [], + model: ActorFollowModel.unscoped(), required: true, - as: 'followers', + as: 'ActorFollowing', where: { + state: 'accepted', targetActorId: this.id } } @@ -365,13 +474,32 @@ export class ActorModel extends Model { return 'acct:' + this.preferredUsername + '@' + this.getHost() } + getIdentifier () { + return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername + } + getHost () { return this.Server ? this.Server.host : CONFIG.WEBSERVER.HOST } + getRedundancyAllowed () { + return this.Server ? this.Server.redundancyAllowed : false + } + getAvatarUrl () { if (!this.avatarId) return undefined - return CONFIG.WEBSERVER.URL + this.Avatar.getWebserverPath + return CONFIG.WEBSERVER.URL + this.Avatar.getWebserverPath() + } + + isOutdated () { + if (this.isOwned()) return false + + const now = Date.now() + const createdAtTime = this.createdAt.getTime() + const updatedAtTime = this.updatedAt.getTime() + + return (now - createdAtTime) > ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL && + (now - updatedAtTime) > ACTIVITY_PUB.ACTOR_REFRESH_INTERVAL } }