// This is a shared video
const videoChannel = video.VideoChannel
if (video.VideoShares !== undefined && video.VideoShares.length !== 0) {
- const addActivity = await addActivityData(video.url, videoChannel.Account, video, videoChannel.url, videoObject, undefined)
+ const addActivity = await addActivityData(video.url, videoChannel.Account, video, videoChannel.Actor.url, videoObject, undefined)
const url = getAnnounceActivityPubUrl(video.url, account)
const announceActivity = await announceActivityData(url, account, addActivity, undefined)
activities.push(announceActivity)
} else {
- const addActivity = await addActivityData(video.url, account, video, videoChannel.url, videoObject, undefined)
+ const addActivity = await addActivityData(video.url, account, video, videoChannel.Actor.url, videoObject, undefined)
activities.push(addActivity)
}
// This could be long so don't wait this task
const following = follow.AccountFollowing
following.destroy()
- .catch(err => logger.error('Cannot destroy account that we do not follow anymore %s.', following.url, err))
+ .catch(err => logger.error('Cannot destroy account that we do not follow anymore %s.', following.Actor.url, err))
return res.status(204).end()
}
return res.type('json').status(204).end()
}
-async function addVideoChannel (req: express.Request, res: express.Response) {
+function addVideoChannel (req: express.Request, res: express.Response) {
const videoChannelInfo: VideoChannelCreate = req.body
const account: AccountModel = res.locals.oauth.token.User.Account
- let videoChannelCreated: VideoChannelModel
- await sequelizeTypescript.transaction(async t => {
- videoChannelCreated = await createVideoChannel(videoChannelInfo, account, t)
- })
+ return sequelizeTypescript.transaction(async t => {
+ const videoChannelCreated = await createVideoChannel(videoChannelInfo, account, t)
- logger.info('Video channel with uuid %s created.', videoChannelCreated.uuid)
+ logger.info('Video channel with uuid %s created.', videoChannelCreated.uuid)
+ })
}
async function updateVideoChannelRetryWrapper (req: express.Request, res: express.Response, next: express.NextFunction) {
const json = {
subject: req.query.resource,
- aliases: [ account.url ],
+ aliases: [ account.Actor.url ],
links: [
{
rel: 'self',
type: 'application/activity+json',
- href: account.url
+ href: account.Actor.url
}
]
}
+++ /dev/null
-import * as validator from 'validator'
-import { CONSTRAINTS_FIELDS } from '../../../initializers'
-import { isAccountNameValid } from '../accounts'
-import { exists, isUUIDValid } from '../misc'
-import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
-
-function isAccountEndpointsObjectValid (endpointObject: any) {
- return isActivityPubUrlValid(endpointObject.sharedInbox)
-}
-
-function isAccountPublicKeyObjectValid (publicKeyObject: any) {
- return isActivityPubUrlValid(publicKeyObject.id) &&
- isActivityPubUrlValid(publicKeyObject.owner) &&
- isAccountPublicKeyValid(publicKeyObject.publicKeyPem)
-}
-
-function isAccountTypeValid (type: string) {
- return type === 'Person' || type === 'Application'
-}
-
-function isAccountPublicKeyValid (publicKey: string) {
- return exists(publicKey) &&
- typeof publicKey === 'string' &&
- publicKey.startsWith('-----BEGIN PUBLIC KEY-----') &&
- publicKey.endsWith('-----END PUBLIC KEY-----') &&
- validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY)
-}
-
-function isAccountPreferredUsernameValid (preferredUsername: string) {
- return isAccountNameValid(preferredUsername)
-}
-
-function isAccountPrivateKeyValid (privateKey: string) {
- return exists(privateKey) &&
- typeof privateKey === 'string' &&
- privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') &&
- privateKey.endsWith('-----END RSA PRIVATE KEY-----') &&
- validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY)
-}
-
-function isRemoteAccountValid (remoteAccount: any) {
- return isActivityPubUrlValid(remoteAccount.id) &&
- isUUIDValid(remoteAccount.uuid) &&
- isAccountTypeValid(remoteAccount.type) &&
- isActivityPubUrlValid(remoteAccount.following) &&
- isActivityPubUrlValid(remoteAccount.followers) &&
- isActivityPubUrlValid(remoteAccount.inbox) &&
- isActivityPubUrlValid(remoteAccount.outbox) &&
- isAccountPreferredUsernameValid(remoteAccount.preferredUsername) &&
- isActivityPubUrlValid(remoteAccount.url) &&
- isAccountPublicKeyObjectValid(remoteAccount.publicKey) &&
- isAccountEndpointsObjectValid(remoteAccount.endpoints)
-}
-
-function isAccountFollowingCountValid (value: string) {
- return exists(value) && validator.isInt('' + value, { min: 0 })
-}
-
-function isAccountFollowersCountValid (value: string) {
- return exists(value) && validator.isInt('' + value, { min: 0 })
-}
-
-function isAccountDeleteActivityValid (activity: any) {
- return isBaseActivityValid(activity, 'Delete')
-}
-
-function isAccountFollowActivityValid (activity: any) {
- return isBaseActivityValid(activity, 'Follow') &&
- isActivityPubUrlValid(activity.object)
-}
-
-function isAccountAcceptActivityValid (activity: any) {
- return isBaseActivityValid(activity, 'Accept')
-}
-
-// ---------------------------------------------------------------------------
-
-export {
- isAccountEndpointsObjectValid,
- isAccountPublicKeyObjectValid,
- isAccountTypeValid,
- isAccountPublicKeyValid,
- isAccountPreferredUsernameValid,
- isAccountPrivateKeyValid,
- isRemoteAccountValid,
- isAccountFollowingCountValid,
- isAccountFollowersCountValid,
- isAccountNameValid,
- isAccountFollowActivityValid,
- isAccountAcceptActivityValid,
- isAccountDeleteActivityValid
-}
import * as validator from 'validator'
import { Activity, ActivityType } from '../../../../shared/models/activitypub'
-import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './account'
+import { isAccountAcceptActivityValid, isAccountDeleteActivityValid, isAccountFollowActivityValid } from './actor'
import { isAnnounceActivityValid } from './announce'
import { isActivityPubUrlValid } from './misc'
import { isDislikeActivityValid, isLikeActivityValid } from './rate'
--- /dev/null
+import * as validator from 'validator'
+import { CONSTRAINTS_FIELDS } from '../../../initializers'
+import { isAccountNameValid } from '../accounts'
+import { exists, isUUIDValid } from '../misc'
+import { isActivityPubUrlValid, isBaseActivityValid } from './misc'
+
+function isActorEndpointsObjectValid (endpointObject: any) {
+ return isActivityPubUrlValid(endpointObject.sharedInbox)
+}
+
+function isActorPublicKeyObjectValid (publicKeyObject: any) {
+ return isActivityPubUrlValid(publicKeyObject.id) &&
+ isActivityPubUrlValid(publicKeyObject.owner) &&
+ isActorPublicKeyValid(publicKeyObject.publicKeyPem)
+}
+
+function isActorTypeValid (type: string) {
+ return type === 'Person' || type === 'Application' || type === 'Group'
+}
+
+function isActorPublicKeyValid (publicKey: string) {
+ return exists(publicKey) &&
+ typeof publicKey === 'string' &&
+ publicKey.startsWith('-----BEGIN PUBLIC KEY-----') &&
+ publicKey.endsWith('-----END PUBLIC KEY-----') &&
+ validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTOR.PUBLIC_KEY)
+}
+
+function isActorPreferredUsernameValid (preferredUsername: string) {
+ return isAccountNameValid(preferredUsername)
+}
+
+function isActorPrivateKeyValid (privateKey: string) {
+ return exists(privateKey) &&
+ typeof privateKey === 'string' &&
+ privateKey.startsWith('-----BEGIN RSA PRIVATE KEY-----') &&
+ privateKey.endsWith('-----END RSA PRIVATE KEY-----') &&
+ validator.isLength(privateKey, CONSTRAINTS_FIELDS.ACTOR.PRIVATE_KEY)
+}
+
+function isRemoteActorValid (remoteActor: any) {
+ return isActivityPubUrlValid(remoteActor.id) &&
+ isUUIDValid(remoteActor.uuid) &&
+ isActorTypeValid(remoteActor.type) &&
+ isActivityPubUrlValid(remoteActor.following) &&
+ isActivityPubUrlValid(remoteActor.followers) &&
+ isActivityPubUrlValid(remoteActor.inbox) &&
+ isActivityPubUrlValid(remoteActor.outbox) &&
+ isActorPreferredUsernameValid(remoteActor.preferredUsername) &&
+ isActivityPubUrlValid(remoteActor.url) &&
+ isActorPublicKeyObjectValid(remoteActor.publicKey) &&
+ isActorEndpointsObjectValid(remoteActor.endpoints)
+}
+
+function isActorFollowingCountValid (value: string) {
+ return exists(value) && validator.isInt('' + value, { min: 0 })
+}
+
+function isActorFollowersCountValid (value: string) {
+ return exists(value) && validator.isInt('' + value, { min: 0 })
+}
+
+function isActorDeleteActivityValid (activity: any) {
+ return isBaseActivityValid(activity, 'Delete')
+}
+
+function isActorFollowActivityValid (activity: any) {
+ return isBaseActivityValid(activity, 'Follow') &&
+ isActivityPubUrlValid(activity.object)
+}
+
+function isActorAcceptActivityValid (activity: any) {
+ return isBaseActivityValid(activity, 'Accept')
+}
+
+// ---------------------------------------------------------------------------
+
+export {
+ isActorEndpointsObjectValid,
+ isActorPublicKeyObjectValid,
+ isActorTypeValid,
+ isActorPublicKeyValid,
+ isActorPreferredUsernameValid,
+ isActorPrivateKeyValid,
+ isRemoteActorValid,
+ isActorFollowingCountValid,
+ isActorFollowersCountValid,
+ isActorFollowActivityValid,
+ isActorAcceptActivityValid,
+ isActorDeleteActivityValid
+}
-export * from './account'
+export * from './actor'
export * from './activity'
export * from './misc'
export * from './signature'
-import { isAccountFollowActivityValid } from './account'
+import { isAccountFollowActivityValid } from './actor'
import { isBaseActivityValid } from './misc'
import { isDislikeActivityValid, isLikeActivityValid } from './rate'
FILE_SIZE: { min: 10 },
URL: { min: 3, max: 2000 } // Length
},
- ACCOUNTS: {
+ ACTOR: {
PUBLIC_KEY: { min: 10, max: 5000 }, // Length
PRIVATE_KEY: { min: 10, max: 5000 }, // Length
URL: { min: 3, max: 2000 } // Length
import { CONFIG, sequelizeTypescript } from '../initializers'
import { AccountModel } from '../models/account/account'
import { UserModel } from '../models/account/user'
+import { ActorModel } from '../models/activitypub/actor'
import { getAccountActivityPubUrl } from './activitypub'
import { createVideoChannel } from './video-channel'
// Set account keys, this could be long so process after the account creation and do not block the client
const { publicKey, privateKey } = await createPrivateAndPublicKeys()
- account.set('publicKey', publicKey)
- account.set('privateKey', privateKey)
- account.save().catch(err => logger.error('Cannot set public/private keys of local account %d.', account.id, err))
+ const actor = account.Actor
+ actor.set('publicKey', publicKey)
+ actor.set('privateKey', privateKey)
+ actor.save().catch(err => logger.error('Cannot set public/private keys of actor %d.', actor.uuid, err))
return { account, videoChannel }
}
async function createLocalAccountWithoutKeys (name: string, userId: number, applicationId: number, t: Sequelize.Transaction) {
const url = getAccountActivityPubUrl(name)
- const accountInstance = new AccountModel({
- name,
+ const actorInstance = new ActorModel({
url,
publicKey: null,
privateKey: null,
outboxUrl: url + '/outbox',
sharedInboxUrl: CONFIG.WEBSERVER.URL + '/inbox',
followersUrl: url + '/followers',
- followingUrl: url + '/following',
+ followingUrl: url + '/following'
+ })
+ const actorInstanceCreated = await actorInstance.save({ transaction: t })
+
+ const accountInstance = new AccountModel({
+ name,
userId,
applicationId,
+ actorId: actorInstanceCreated.id,
serverId: null // It is our server
})
- return accountInstance.save({ transaction: t })
+ const accountInstanceCreated = await accountInstance.save({ transaction: t })
+ accountInstanceCreated.Actor = actorInstanceCreated
+
+ return accountInstanceCreated
}
// ---------------------------------------------------------------------------
-import { join } from 'path'
import * as Sequelize from 'sequelize'
import {
AfterDestroy,
Table,
UpdatedAt
} from 'sequelize-typescript'
-import { Avatar } from '../../../shared/models/avatars/avatar.model'
-import { activityPubContextify } from '../../helpers'
-import {
- isAccountFollowersCountValid,
- isAccountFollowingCountValid,
- isAccountPrivateKeyValid,
- isAccountPublicKeyValid,
- isActivityPubUrlValid
-} from '../../helpers/custom-validators/activitypub'
import { isUserUsernameValid } from '../../helpers/custom-validators/users'
-import { AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
import { sendDeleteAccount } 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 { throwIfNotValid } from '../utils'
import { VideoChannelModel } from '../video/video-channel'
-import { AccountFollowModel } from './account-follow'
import { UserModel } from './user'
@Table({
}
]
})
-export class AccountModel extends Model<Account> {
-
- @AllowNull(false)
- @Default(DataType.UUIDV4)
- @IsUUID(4)
- @Column(DataType.UUID)
- uuid: string
-
- @AllowNull(false)
- @Is('AccountName', value => throwIfNotValid(value, isUserUsernameValid, 'account name'))
- @Column
- name: string
-
- @AllowNull(false)
- @Is('AccountUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
- @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
- url: string
-
- @AllowNull(true)
- @Is('AccountPublicKey', value => throwIfNotValid(value, isAccountPublicKeyValid, 'public key'))
- @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PUBLIC_KEY.max))
- publicKey: string
-
- @AllowNull(true)
- @Is('AccountPublicKey', value => throwIfNotValid(value, isAccountPrivateKeyValid, 'private key'))
- @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.PRIVATE_KEY.max))
- privateKey: string
-
- @AllowNull(false)
- @Is('AccountFollowersCount', value => throwIfNotValid(value, isAccountFollowersCountValid, 'followers count'))
- @Column
- followersCount: number
-
- @AllowNull(false)
- @Is('AccountFollowersCount', value => throwIfNotValid(value, isAccountFollowingCountValid, 'following count'))
- @Column
- followingCount: number
-
- @AllowNull(false)
- @Is('AccountInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'inbox url'))
- @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
- inboxUrl: string
-
- @AllowNull(false)
- @Is('AccountOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url'))
- @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
- outboxUrl: string
-
- @AllowNull(false)
- @Is('AccountSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url'))
- @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
- sharedInboxUrl: string
-
- @AllowNull(false)
- @Is('AccountFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url'))
- @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
- followersUrl: string
-
- @AllowNull(false)
- @Is('AccountFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url'))
- @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACCOUNTS.URL.max))
- followingUrl: string
+export class AccountModel extends Model<AccountModel> {
@CreatedAt
createdAt: Date
@UpdatedAt
updatedAt: Date
- @ForeignKey(() => AvatarModel)
- @Column
- avatarId: number
-
- @BelongsTo(() => AvatarModel, {
- foreignKey: {
- allowNull: true
- },
- onDelete: 'cascade'
- })
- Avatar: AvatarModel
-
- @ForeignKey(() => ServerModel)
+ @ForeignKey(() => ActorModel)
@Column
- serverId: number
+ actorId: number
- @BelongsTo(() => ServerModel, {
+ @BelongsTo(() => ActorModel, {
foreignKey: {
- allowNull: true
+ allowNull: false
},
onDelete: 'cascade'
})
- Server: ServerModel
+ Actor: ActorModel
@ForeignKey(() => UserModel)
@Column
})
VideoChannels: VideoChannelModel[]
- @HasMany(() => AccountFollowModel, {
- foreignKey: {
- name: 'accountId',
- allowNull: false
- },
- onDelete: 'cascade'
- })
- AccountFollowing: AccountFollowModel[]
-
- @HasMany(() => AccountFollowModel, {
- foreignKey: {
- name: 'targetAccountId',
- allowNull: false
- },
- as: 'followers',
- onDelete: 'cascade'
- })
- AccountFollowers: AccountFollowModel[]
-
@AfterDestroy
static sendDeleteIfOwned (instance: AccountModel) {
if (instance.isOwned()) {
static loadByUrl (url: string, transaction?: Sequelize.Transaction) {
const query = {
- where: {
- url
- },
+ include: [
+ {
+ model: ActorModel,
+ required: true,
+ where: {
+ url
+ }
+ }
+ ],
transaction
}
static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) {
const query = {
- where: {
- followersUrl: {
- [ Sequelize.Op.in ]: followersUrls
+ include: [
+ {
+ model: ActorModel,
+ required: true,
+ where: {
+ followersUrl: {
+ [ Sequelize.Op.in ]: followersUrls
+ }
+ }
}
- },
+ ],
transaction
}
}
toFormattedJSON () {
- let host = CONFIG.WEBSERVER.HOST
- let score: number
- let avatar: Avatar = null
-
- if (this.Avatar) {
- avatar = {
- path: join(AVATARS_DIR.ACCOUNT, this.Avatar.filename),
- createdAt: this.Avatar.createdAt,
- updatedAt: this.Avatar.updatedAt
- }
- }
-
- if (this.Server) {
- host = this.Server.host
- score = this.Server.score
- }
-
- return {
+ const actor = this.Actor.toFormattedJSON()
+ const account = {
id: this.id,
- uuid: this.uuid,
- host,
- score,
- name: this.name,
- followingCount: this.followingCount,
- followersCount: this.followersCount,
createdAt: this.createdAt,
- updatedAt: this.updatedAt,
- avatar
+ updatedAt: this.updatedAt
}
+
+ return Object.assign(actor, account)
}
toActivityPubObject () {
- 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
- }
- }
-
- return activityPubContextify(json)
+ return this.Actor.toActivityPubObject(this.name, this.uuid, 'Account')
}
isOwned () {
- return this.serverId === null
- }
-
- getFollowerSharedInboxUrls (t: Sequelize.Transaction) {
- const query = {
- attributes: [ 'sharedInboxUrl' ],
- include: [
- {
- model: AccountFollowModel,
- required: true,
- as: 'followers',
- where: {
- targetAccountId: this.id
- }
- }
- ],
- transaction: t
- }
-
- return AccountModel.findAll(query)
- .then(accounts => accounts.map(a => a.sharedInboxUrl))
- }
-
- getFollowingUrl () {
- return this.url + '/following'
- }
-
- getFollowersUrl () {
- return this.url + '/followers'
- }
-
- getPublicKeyUrl () {
- return this.url + '#main-key'
+ return this.Actor.isOwned()
}
}
--- /dev/null
+import { join } from 'path'
+import * as Sequelize from 'sequelize'
+import {
+ AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, HasMany, Is, IsUUID, Model, Table,
+ UpdatedAt
+} from 'sequelize-typescript'
+import { Avatar } from '../../../shared/models/avatars/avatar.model'
+import { activityPubContextify } from '../../helpers'
+import {
+ isActivityPubUrlValid,
+ isActorFollowersCountValid,
+ isActorFollowingCountValid, isActorPreferredUsernameValid,
+ isActorPrivateKeyValid,
+ isActorPublicKeyValid
+} from '../../helpers/custom-validators/activitypub'
+import { isUserUsernameValid } from '../../helpers/custom-validators/users'
+import { AVATARS_DIR, CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
+import { AccountFollowModel } from '../account/account-follow'
+import { AvatarModel } from '../avatar/avatar'
+import { ServerModel } from '../server/server'
+import { throwIfNotValid } from '../utils'
+
+@Table({
+ tableName: 'actor'
+})
+export class ActorModel extends Model<ActorModel> {
+
+ @AllowNull(false)
+ @Default(DataType.UUIDV4)
+ @IsUUID(4)
+ @Column(DataType.UUID)
+ uuid: string
+
+ @AllowNull(false)
+ @Is('ActorName', value => throwIfNotValid(value, isActorPreferredUsernameValid, 'actor name'))
+ @Column
+ name: string
+
+ @AllowNull(false)
+ @Is('ActorUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
+ @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
+ url: string
+
+ @AllowNull(true)
+ @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPublicKeyValid, 'public key'))
+ @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.PUBLIC_KEY.max))
+ publicKey: string
+
+ @AllowNull(true)
+ @Is('ActorPublicKey', value => throwIfNotValid(value, isActorPrivateKeyValid, 'private key'))
+ @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.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.ACTOR.URL.max))
+ inboxUrl: string
+
+ @AllowNull(false)
+ @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url'))
+ @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
+ outboxUrl: string
+
+ @AllowNull(false)
+ @Is('ActorSharedInboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'shared inbox url'))
+ @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
+ sharedInboxUrl: string
+
+ @AllowNull(false)
+ @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url'))
+ @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
+ followersUrl: string
+
+ @AllowNull(false)
+ @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url'))
+ @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTOR.URL.max))
+ followingUrl: string
+
+ @CreatedAt
+ createdAt: Date
+
+ @UpdatedAt
+ updatedAt: Date
+
+ @ForeignKey(() => AvatarModel)
+ @Column
+ avatarId: number
+
+ @BelongsTo(() => AvatarModel, {
+ foreignKey: {
+ allowNull: true
+ },
+ onDelete: 'cascade'
+ })
+ Avatar: AvatarModel
+
+ @HasMany(() => AccountFollowModel, {
+ foreignKey: {
+ name: 'accountId',
+ allowNull: false
+ },
+ onDelete: 'cascade'
+ })
+ AccountFollowing: AccountFollowModel[]
+
+ @HasMany(() => AccountFollowModel, {
+ foreignKey: {
+ name: 'targetAccountId',
+ allowNull: false
+ },
+ as: 'followers',
+ onDelete: 'cascade'
+ })
+ AccountFollowers: AccountFollowModel[]
+
+ @ForeignKey(() => ServerModel)
+ @Column
+ serverId: number
+
+ @BelongsTo(() => ServerModel, {
+ foreignKey: {
+ allowNull: true
+ },
+ onDelete: 'cascade'
+ })
+ Server: ServerModel
+
+ static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) {
+ const query = {
+ where: {
+ followersUrl: {
+ [ Sequelize.Op.in ]: followersUrls
+ }
+ },
+ transaction
+ }
+
+ return ActorModel.findAll(query)
+ }
+
+ toFormattedJSON () {
+ let avatar: Avatar = null
+ if (this.Avatar) {
+ avatar = {
+ path: join(AVATARS_DIR.ACCOUNT, this.Avatar.filename),
+ createdAt: this.Avatar.createdAt,
+ updatedAt: this.Avatar.updatedAt
+ }
+ }
+
+ let host = CONFIG.WEBSERVER.HOST
+ let score: number
+ if (this.Server) {
+ host = this.Server.host
+ score = this.Server.score
+ }
+
+ return {
+ id: this.id,
+ host,
+ score,
+ followingCount: this.followingCount,
+ followersCount: this.followersCount,
+ avatar
+ }
+ }
+
+ toActivityPubObject (name: string, uuid: string, type: 'Account' | 'VideoChannel') {
+ let activityPubType
+ if (type === 'Account') {
+ activityPubType = this.serverId ? 'Application' as 'Application' : 'Person' as 'Person'
+ } else { // VideoChannel
+ activityPubType = 'Group'
+ }
+
+ const json = {
+ type,
+ id: this.url,
+ following: this.getFollowingUrl(),
+ followers: this.getFollowersUrl(),
+ inbox: this.inboxUrl,
+ outbox: this.outboxUrl,
+ preferredUsername: name,
+ url: this.url,
+ name,
+ endpoints: {
+ sharedInbox: this.sharedInboxUrl
+ },
+ uuid,
+ publicKey: {
+ id: this.getPublicKeyUrl(),
+ owner: this.url,
+ publicKeyPem: this.publicKey
+ }
+ }
+
+ return activityPubContextify(json)
+ }
+
+ getFollowerSharedInboxUrls (t: Sequelize.Transaction) {
+ const query = {
+ attributes: [ 'sharedInboxUrl' ],
+ include: [
+ {
+ model: AccountFollowModel,
+ required: true,
+ as: 'followers',
+ where: {
+ targetAccountId: 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'
+ }
+
+ getPublicKeyUrl () {
+ return this.url + '#main-key'
+ }
+
+ isOwned () {
+ return this.serverId === null
+ }
+}
HasMany,
Is,
IsUUID,
- Model, Scopes,
+ Model,
+ Scopes,
Table,
UpdatedAt
} from 'sequelize-typescript'
import { IFindOptions } from 'sequelize-typescript/lib/interfaces/IFindOptions'
-import { activityPubCollection } from '../../helpers'
-import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub'
import { isVideoChannelDescriptionValid, isVideoChannelNameValid } from '../../helpers/custom-validators/video-channels'
-import { CONSTRAINTS_FIELDS } from '../../initializers'
-import { getAnnounceActivityPubUrl } from '../../lib/activitypub'
import { sendDeleteVideoChannel } from '../../lib/activitypub/send'
import { AccountModel } from '../account/account'
+import { ActorModel } from '../activitypub/actor'
import { ServerModel } from '../server/server'
import { getSort, throwIfNotValid } from '../utils'
import { VideoModel } from './video'
@Column
remote: boolean
- @AllowNull(false)
- @Is('VideoChannelUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'url'))
- @Column(DataType.STRING(CONSTRAINTS_FIELDS.VIDEO_CHANNELS.URL.max))
- url: string
-
@CreatedAt
createdAt: Date
@UpdatedAt
updatedAt: Date
+ @ForeignKey(() => ActorModel)
+ @Column
+ actorId: number
+
+ @BelongsTo(() => ActorModel, {
+ foreignKey: {
+ allowNull: false
+ },
+ onDelete: 'cascade'
+ })
+ Actor: ActorModel
+
@ForeignKey(() => AccountModel)
@Column
accountId: number
static loadByUrl (url: string, t?: Sequelize.Transaction) {
const query: IFindOptions<VideoChannelModel> = {
- where: {
- url
- }
+ include: [
+ {
+ model: ActorModel,
+ required: true,
+ where: {
+ url
+ }
+ }
+ ]
}
if (t !== undefined) query.transaction = t
}
toActivityPubObject () {
- let sharesObject
- if (Array.isArray(this.VideoChannelShares)) {
- const shares: string[] = []
-
- for (const videoChannelShare of this.VideoChannelShares) {
- const shareUrl = getAnnounceActivityPubUrl(this.url, videoChannelShare.Account)
- shares.push(shareUrl)
- }
-
- sharesObject = activityPubCollection(shares)
- }
-
- return {
- type: 'VideoChannel' as 'VideoChannel',
- id: this.url,
- uuid: this.uuid,
- content: this.description,
- name: this.name,
- published: this.createdAt.toISOString(),
- updated: this.updatedAt.toISOString(),
- shares: sharesObject
- }
+ return this.Actor.toActivityPubObject(this.name, this.uuid, 'VideoChannel')
}
}
export interface ActivityPubActor {
'@context': any[]
- type: 'Person' | 'Application'
+ type: 'Person' | 'Application' | 'Group'
id: string
following: string
followers: string