import * as Sequelize from 'sequelize'
-
-import {
- isUserUsernameValid,
- isAccountPublicKeyValid,
- isAccountUrlValid,
- isAccountPrivateKeyValid,
- isAccountFollowersCountValid,
- isAccountFollowingCountValid,
- isAccountInboxValid,
- isAccountOutboxValid,
- isAccountSharedInboxValid,
- isAccountFollowersValid,
- isAccountFollowingValid,
- activityPubContextify
-} from '../../helpers'
-
-import { addMethodsToModel, getSort } from '../utils'
import {
- AccountInstance,
- AccountAttributes,
-
- AccountMethods
-} from './account-interface'
-import { sendDeleteAccount } from '../../lib/activitypub/send-request'
-import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers/constants'
-
-let Account: Sequelize.Model<AccountInstance, AccountAttributes>
-let loadAccountByServerAndUUID: AccountMethods.LoadAccountByServerAndUUID
-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 listOwned: AccountMethods.ListOwned
-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<AccountInstance, AccountAttributes>('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 { AvatarModel } from '../avatar/avatar'
+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 = isAccountUrlValid(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 = isAccountInboxValid(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 = isAccountOutboxValid(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 = isAccountSharedInboxValid(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 = isAccountFollowersValid(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 = isAccountFollowingValid(value)
- if (res === false) throw new Error('Following URL is not valid.')
- }
- }
- }
- },
- {
- indexes: [
+ model: () => ActorModel,
+ required: true,
+ include: [
{
- fields: [ 'name' ]
+ model: () => ServerModel,
+ required: false
},
{
- fields: [ 'serverId' ]
- },
- {
- fields: [ 'userId' ],
- unique: true
- },
- {
- fields: [ 'applicationId' ],
- unique: true
- },
- {
- fields: [ 'name', 'serverId', 'applicationId' ],
- unique: true
+ model: () => AvatarModel,
+ required: false
}
- ],
- hooks: { afterDestroy }
+ ]
}
- )
-
- const classMethods = [
- associate,
- loadAccountByServerAndUUID,
- loadApplication,
- load,
- loadByUUID,
- loadByUrl,
- loadLocalByName,
- loadByNameAndHost,
- listOwned
]
- const instanceMethods = [
- isOwned,
- toActivityPubObject,
- toFormattedJSON,
- getFollowerSharedInboxUrls,
- getFollowingUrl,
- getFollowersUrl,
- getPublicKeyUrl
+})
+@Table({
+ tableName: 'account',
+ indexes: [
+ {
+ fields: [ 'actorId' ],
+ unique: true
+ },
+ {
+ fields: [ 'applicationId' ]
+ },
+ {
+ fields: [ 'userId' ]
+ }
]
- addMethodsToModel(Account, classMethods, instanceMethods)
+})
+export class AccountModel extends Model<AccountModel> {
- 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'
+ onDelete: 'cascade',
+ hooks: true
})
-}
-
-function afterDestroy (account: AccountInstance) {
- if (account.isOwned()) {
- return sendDeleteAccount(account, undefined)
- }
+ VideoComments: VideoCommentModel[]
- return undefined
-}
+ @BeforeDestroy
+ static async sendDeleteIfOwned (instance: AccountModel, options) {
+ if (!instance.Actor) {
+ instance.Actor = await instance.$get('Actor', { transaction: options.transaction }) as ActorModel
+ }
-toFormattedJSON = function (this: AccountInstance) {
- let host = CONFIG.WEBSERVER.HOST
- let score: number
+ 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,
- host,
- score,
- name: this.name,
- createdAt: this.createdAt,
- updatedAt: this.updatedAt
+ static load (id: number) {
+ return AccountModel.findById(id)
}
- 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) {
- const query: Sequelize.FindOptions<AccountAttributes> = {
- attributes: [ 'sharedInboxUrl' ],
- include: [
- {
- model: Account['sequelize'].models.AccountFollow,
- required: true,
- as: 'followers',
- where: {
- targetAccountId: this.id
- }
- }
- ]
+ 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 ------------------------------
-
-listOwned = function () {
- const query: Sequelize.FindOptions<AccountAttributes> = {
- where: {
- serverId: null
+ 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
+ }
+ }
+ ]
}
- }
-
- return Account.findAll(query)
-}
-loadApplication = function () {
- return Account.findOne({
- include: [
- {
- model: Account['sequelize'].models.Application,
- required: true
- }
- ]
- })
-}
-
-load = function (id: number) {
- return Account.findById(id)
-}
+ return AccountModel.findOne(query)
+ }
-loadByUUID = function (uuid: string) {
- const query: Sequelize.FindOptions<AccountAttributes> = {
- where: {
- uuid
+ static loadLocalByNameAndHost (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<AccountAttributes> = {
- 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<AccountAttributes> = {
- 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
+ }
-loadByUrl = function (url: string, transaction?: Sequelize.Transaction) {
- const query: Sequelize.FindOptions<AccountAttributes> = {
- where: {
- url
- },
- transaction
+ return Object.assign(actor, account)
}
- return Account.findOne(query)
-}
+ toActivityPubObject () {
+ const obj = this.Actor.toActivityPubObject(this.name, 'Account')
-loadAccountByServerAndUUID = function (uuid: string, serverId: number, transaction: Sequelize.Transaction) {
- const query: Sequelize.FindOptions<AccountAttributes> = {
- where: {
- serverId,
- uuid
- },
- transaction
+ return Object.assign(obj, {
+ summary: this.description
+ })
+ }
+
+ isOwned () {
+ return this.Actor.isOwned()
}
- return Account.find(query)
+ getDisplayName () {
+ return this.name
+ }
}