X-Git-Url: https://git.immae.eu/?a=blobdiff_plain;f=server%2Fmodels%2Faccount%2Faccount.ts;h=5a237d733a0efd06c6fa98ed258c323e7b8c9058;hb=7ad9b9846c44d198a736183fb186c2039f5236b5;hp=8b0819f395c701dba485cbeae83566daf791487f;hpb=be44767854709dbf7da4ba37fe4f16ac4e297f08;p=github%2FChocobozzz%2FPeerTube.git diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 8b0819f39..5a237d733 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts @@ -1,463 +1,273 @@ -import { join } from 'path' import * as Sequelize from 'sequelize' -import { Avatar } from '../../../shared/models/avatars/avatar.model' import { - activityPubContextify, - isAccountFollowersCountValid, - isAccountFollowingCountValid, - isAccountPrivateKeyValid, - isAccountPublicKeyValid, - isUserUsernameValid -} from '../../helpers' -import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' -import { AVATARS_DIR } from '../../initializers' -import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants' -import { sendDeleteAccount } from '../../lib/activitypub/send/send-delete' -import { addMethodsToModel } from '../utils' -import { AccountAttributes, AccountInstance, AccountMethods } from './account-interface' - -let Account: Sequelize.Model -let load: AccountMethods.Load -let loadApplication: AccountMethods.LoadApplication -let loadByUUID: AccountMethods.LoadByUUID -let loadByUrl: AccountMethods.LoadByUrl -let loadLocalByName: AccountMethods.LoadLocalByName -let loadByNameAndHost: AccountMethods.LoadByNameAndHost -let listByFollowersUrls: AccountMethods.ListByFollowersUrls -let isOwned: AccountMethods.IsOwned -let toActivityPubObject: AccountMethods.ToActivityPubObject -let toFormattedJSON: AccountMethods.ToFormattedJSON -let getFollowerSharedInboxUrls: AccountMethods.GetFollowerSharedInboxUrls -let getFollowingUrl: AccountMethods.GetFollowingUrl -let getFollowersUrl: AccountMethods.GetFollowersUrl -let getPublicKeyUrl: AccountMethods.GetPublicKeyUrl - -export default function defineAccount (sequelize: Sequelize.Sequelize, DataTypes: Sequelize.DataTypes) { - Account = sequelize.define('Account', + AllowNull, + BeforeDestroy, + BelongsTo, + Column, + CreatedAt, + Default, + DefaultScope, + ForeignKey, + HasMany, + Is, + Model, + Table, + UpdatedAt +} from 'sequelize-typescript' +import { Account } from '../../../shared/models/actors' +import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts' +import { sendDeleteActor } from '../../lib/activitypub/send' +import { ActorModel } from '../activitypub/actor' +import { ApplicationModel } from '../application/application' +import { ServerModel } from '../server/server' +import { getSort, throwIfNotValid } from '../utils' +import { VideoChannelModel } from '../video/video-channel' +import { VideoCommentModel } from '../video/video-comment' +import { UserModel } from './user' + +@DefaultScope({ + include: [ { - uuid: { - type: DataTypes.UUID, - defaultValue: DataTypes.UUIDV4, - allowNull: false, - validate: { - isUUID: 4 - } - }, - name: { - type: DataTypes.STRING, - allowNull: false, - validate: { - nameValid: value => { - const res = isUserUsernameValid(value) - if (res === false) throw new Error('Name is not valid.') - } - } - }, - url: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), - allowNull: false, - validate: { - urlValid: value => { - const res = isActivityPubUrlValid(value) - if (res === false) throw new Error('URL is not valid.') - } - } - }, - publicKey: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY.max), - allowNull: true, - validate: { - publicKeyValid: value => { - const res = isAccountPublicKeyValid(value) - if (res === false) throw new Error('Public key is not valid.') - } - } - }, - privateKey: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max), - allowNull: true, - validate: { - privateKeyValid: value => { - const res = isAccountPrivateKeyValid(value) - if (res === false) throw new Error('Private key is not valid.') - } - } - }, - followersCount: { - type: DataTypes.INTEGER, - allowNull: false, - validate: { - followersCountValid: value => { - const res = isAccountFollowersCountValid(value) - if (res === false) throw new Error('Followers count is not valid.') - } - } - }, - followingCount: { - type: DataTypes.INTEGER, - allowNull: false, - validate: { - followingCountValid: value => { - const res = isAccountFollowingCountValid(value) - if (res === false) throw new Error('Following count is not valid.') - } - } - }, - inboxUrl: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), - allowNull: false, - validate: { - inboxUrlValid: value => { - const res = isActivityPubUrlValid(value) - if (res === false) throw new Error('Inbox URL is not valid.') - } - } - }, - outboxUrl: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), - allowNull: false, - validate: { - outboxUrlValid: value => { - const res = isActivityPubUrlValid(value) - if (res === false) throw new Error('Outbox URL is not valid.') - } - } - }, - sharedInboxUrl: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), - allowNull: false, - validate: { - sharedInboxUrlValid: value => { - const res = isActivityPubUrlValid(value) - if (res === false) throw new Error('Shared inbox URL is not valid.') - } - } - }, - followersUrl: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), - allowNull: false, - validate: { - followersUrlValid: value => { - const res = isActivityPubUrlValid(value) - if (res === false) throw new Error('Followers URL is not valid.') - } - } - }, - followingUrl: { - type: DataTypes.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max), - allowNull: false, - validate: { - followingUrlValid: value => { - const res = isActivityPubUrlValid(value) - if (res === false) throw new Error('Following URL is not valid.') - } - } - } + model: () => ActorModel, // Default scope includes avatar and server + required: true + } + ] +}) +@Table({ + tableName: 'account', + indexes: [ + { + fields: [ 'actorId' ], + unique: true }, { - indexes: [ - { - fields: [ 'name' ] - }, - { - fields: [ 'serverId' ] - }, - { - fields: [ 'userId' ], - unique: true - }, - { - fields: [ 'applicationId' ], - unique: true - }, - { - fields: [ 'name', 'serverId', 'applicationId' ], - unique: true - } - ], - hooks: { afterDestroy } + fields: [ 'applicationId' ] + }, + { + fields: [ 'userId' ] } - ) - - const classMethods = [ - associate, - loadApplication, - load, - loadByUUID, - loadByUrl, - loadLocalByName, - loadByNameAndHost, - listByFollowersUrls ] - const instanceMethods = [ - isOwned, - toActivityPubObject, - toFormattedJSON, - getFollowerSharedInboxUrls, - getFollowingUrl, - getFollowersUrl, - getPublicKeyUrl - ] - addMethodsToModel(Account, classMethods, instanceMethods) +}) +export class AccountModel extends Model { - return Account -} + @AllowNull(false) + @Column + name: string + + @AllowNull(true) + @Default(null) + @Is('AccountDescription', value => throwIfNotValid(value, isAccountDescriptionValid, 'description')) + @Column + description: string + + @CreatedAt + createdAt: Date -// --------------------------------------------------------------------------- + @UpdatedAt + updatedAt: Date -function associate (models) { - Account.belongsTo(models.Server, { + @ForeignKey(() => ActorModel) + @Column + actorId: number + + @BelongsTo(() => ActorModel, { foreignKey: { - name: 'serverId', - allowNull: true + allowNull: false }, onDelete: 'cascade' }) + Actor: ActorModel + + @ForeignKey(() => UserModel) + @Column + userId: number - Account.belongsTo(models.User, { + @BelongsTo(() => UserModel, { foreignKey: { - name: 'userId', allowNull: true }, onDelete: 'cascade' }) + User: UserModel - Account.belongsTo(models.Application, { + @ForeignKey(() => ApplicationModel) + @Column + applicationId: number + + @BelongsTo(() => ApplicationModel, { foreignKey: { - name: 'applicationId', allowNull: true }, onDelete: 'cascade' }) + Application: ApplicationModel - Account.hasMany(models.VideoChannel, { + @HasMany(() => VideoChannelModel, { foreignKey: { - name: 'accountId', allowNull: false }, onDelete: 'cascade', hooks: true }) + VideoChannels: VideoChannelModel[] - Account.hasMany(models.AccountFollow, { - foreignKey: { - name: 'accountId', - allowNull: false - }, - onDelete: 'cascade' - }) - - Account.hasMany(models.AccountFollow, { + @HasMany(() => VideoCommentModel, { foreignKey: { - name: 'targetAccountId', allowNull: false }, - as: 'followers', - onDelete: 'cascade' - }) - - Account.hasOne(models.Avatar, { - foreignKey: { - name: 'avatarId', - allowNull: true - }, - onDelete: 'cascade' + onDelete: 'cascade', + hooks: true }) -} - -function afterDestroy (account: AccountInstance) { - if (account.isOwned()) { - return sendDeleteAccount(account, undefined) - } - - return undefined -} + VideoComments: VideoCommentModel[] -toFormattedJSON = function (this: AccountInstance) { - let host = CONFIG.WEBSERVER.HOST - let score: number - let avatar: Avatar = null + @BeforeDestroy + static async sendDeleteIfOwned (instance: AccountModel, options) { + if (!instance.Actor) { + instance.Actor = await instance.$get('Actor', { transaction: options.transaction }) as ActorModel + } - if (this.Avatar) { - avatar = { - path: join(AVATARS_DIR.ACCOUNT, this.Avatar.filename), - createdAt: this.Avatar.createdAt, - updatedAt: this.Avatar.updatedAt + if (instance.isOwned()) { + return sendDeleteActor(instance.Actor, options.transaction) } - } - if (this.Server) { - host = this.Server.host - score = this.Server.score as number + return undefined } - const json = { - id: this.id, - uuid: this.uuid, - host, - score, - name: this.name, - followingCount: this.followingCount, - followersCount: this.followersCount, - createdAt: this.createdAt, - updatedAt: this.updatedAt, - avatar + static load (id: number, transaction?: Sequelize.Transaction) { + return AccountModel.findById(id, { transaction }) } - return json -} - -toActivityPubObject = function (this: AccountInstance) { - const type = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person' - - const json = { - type, - id: this.url, - following: this.getFollowingUrl(), - followers: this.getFollowersUrl(), - inbox: this.inboxUrl, - outbox: this.outboxUrl, - preferredUsername: this.name, - url: this.url, - name: this.name, - endpoints: { - sharedInbox: this.sharedInboxUrl - }, - uuid: this.uuid, - publicKey: { - id: this.getPublicKeyUrl(), - owner: this.url, - publicKeyPem: this.publicKey + static loadByUUID (uuid: string) { + const query = { + include: [ + { + model: ActorModel, + required: true, + where: { + uuid + } + } + ] } - } - - return activityPubContextify(json) -} -isOwned = function (this: AccountInstance) { - return this.serverId === null -} - -getFollowerSharedInboxUrls = function (this: AccountInstance, t: Sequelize.Transaction) { - const query: Sequelize.FindOptions = { - attributes: [ 'sharedInboxUrl' ], - include: [ - { - model: Account['sequelize'].models.AccountFollow, - required: true, - as: 'followers', - where: { - targetAccountId: this.id - } - } - ], - transaction: t + return AccountModel.findOne(query) } - return Account.findAll(query) - .then(accounts => accounts.map(a => a.sharedInboxUrl)) -} - -getFollowingUrl = function (this: AccountInstance) { - return this.url + '/following' -} - -getFollowersUrl = function (this: AccountInstance) { - return this.url + '/followers' -} - -getPublicKeyUrl = function (this: AccountInstance) { - return this.url + '#main-key' -} - -// ------------------------------ STATICS ------------------------------ - -loadApplication = function () { - return Account.findOne({ - include: [ - { - model: Account['sequelize'].models.Application, - required: true - } - ] - }) -} + static loadLocalByName (name: string) { + const query = { + where: { + [ Sequelize.Op.or ]: [ + { + userId: { + [ Sequelize.Op.ne ]: null + } + }, + { + applicationId: { + [ Sequelize.Op.ne ]: null + } + } + ] + }, + include: [ + { + model: ActorModel, + required: true, + where: { + preferredUsername: name + } + } + ] + } -load = function (id: number) { - return Account.findById(id) -} + return AccountModel.findOne(query) + } -loadByUUID = function (uuid: string) { - const query: Sequelize.FindOptions = { - where: { - uuid + static loadByNameAndHost (name: string, host: string) { + const query = { + include: [ + { + model: ActorModel, + required: true, + where: { + preferredUsername: name + }, + include: [ + { + model: ServerModel, + required: true, + where: { + host + } + } + ] + } + ] } - } - return Account.findOne(query) -} + return AccountModel.findOne(query) + } -loadLocalByName = function (name: string) { - const query: Sequelize.FindOptions = { - where: { - name, - [Sequelize.Op.or]: [ + static loadByUrl (url: string, transaction?: Sequelize.Transaction) { + const query = { + include: [ { - userId: { - [Sequelize.Op.ne]: null - } - }, - { - applicationId: { - [Sequelize.Op.ne]: null + model: ActorModel, + required: true, + where: { + url } } - ] + ], + transaction } + + return AccountModel.findOne(query) } - return Account.findOne(query) -} + static listForApi (start: number, count: number, sort: string) { + const query = { + offset: start, + limit: count, + order: getSort(sort) + } -loadByNameAndHost = function (name: string, host: string) { - const query: Sequelize.FindOptions = { - where: { - name - }, - include: [ - { - model: Account['sequelize'].models.Server, - required: true, - where: { - host + return AccountModel.findAndCountAll(query) + .then(({ rows, count }) => { + return { + data: rows, + total: count } - } - ] + }) } - return Account.findOne(query) -} + toFormattedJSON (): Account { + const actor = this.Actor.toFormattedJSON() + const account = { + id: this.id, + displayName: this.getDisplayName(), + description: this.description, + createdAt: this.createdAt, + updatedAt: this.updatedAt, + userId: this.userId ? this.userId : undefined + } -loadByUrl = function (url: string, transaction?: Sequelize.Transaction) { - const query: Sequelize.FindOptions = { - where: { - url - }, - transaction + return Object.assign(actor, account) } - return Account.findOne(query) -} + toActivityPubObject () { + const obj = this.Actor.toActivityPubObject(this.name, 'Account') -listByFollowersUrls = function (followersUrls: string[], transaction?: Sequelize.Transaction) { - const query: Sequelize.FindOptions = { - where: { - followersUrl: { - [Sequelize.Op.in]: followersUrls - } - }, - transaction + return Object.assign(obj, { + summary: this.description + }) + } + + isOwned () { + return this.Actor.isOwned() } - return Account.findAll(query) + getDisplayName () { + return this.name + } }