1 import { Op, Transaction } from 'sequelize'
2 import { sequelizeTypescript } from '@server/initializers/database'
3 import { AccountModel } from '@server/models/account/account'
4 import { ActorModel } from '@server/models/actor/actor'
5 import { ServerModel } from '@server/models/server/server'
6 import { VideoChannelModel } from '@server/models/video/video-channel'
7 import { MAccount, MAccountDefault, MActor, MActorFullActor, MActorId, MActorImages, MChannel, MServer } from '@server/types/models'
8 import { ActivityPubActor, ActorImageType } from '@shared/models'
9 import { updateActorImages } from '../image'
10 import { getActorAttributesFromObject, getActorDisplayNameFromObject, getImagesInfoFromObject } from './object-to-model-attributes'
11 import { fetchActorFollowsCount } from './url-to-object'
13 export class APActorCreator {
16 private readonly actorObject: ActivityPubActor,
17 private readonly ownerActor?: MActorFullActor
22 async create (): Promise<MActorFullActor> {
23 const { followersCount, followingCount } = await fetchActorFollowsCount(this.actorObject)
25 const actorInstance = new ActorModel(getActorAttributesFromObject(this.actorObject, followersCount, followingCount))
27 return sequelizeTypescript.transaction(async t => {
28 const server = await this.setServer(actorInstance, t)
30 const { actorCreated, created } = await this.saveActor(actorInstance, t)
32 await this.setImageIfNeeded(actorCreated, ActorImageType.AVATAR, t)
33 await this.setImageIfNeeded(actorCreated, ActorImageType.BANNER, t)
35 await this.tryToFixActorUrlIfNeeded(actorCreated, actorInstance, created, t)
37 if (actorCreated.type === 'Person' || actorCreated.type === 'Application') { // Account or PeerTube instance
38 actorCreated.Account = await this.saveAccount(actorCreated, t) as MAccountDefault
39 actorCreated.Account.Actor = actorCreated
42 if (actorCreated.type === 'Group') { // Video channel
43 const channel = await this.saveVideoChannel(actorCreated, t)
44 actorCreated.VideoChannel = Object.assign(channel, { Actor: actorCreated, Account: this.ownerActor.Account })
47 actorCreated.Server = server
53 private async setServer (actor: MActor, t: Transaction) {
54 const actorHost = new URL(actor.url).host
56 const serverOptions = {
65 const [ server ] = await ServerModel.findOrCreate(serverOptions)
67 // Save our new account in database
68 actor.serverId = server.id
70 return server as MServer
73 private async setImageIfNeeded (actor: MActor, type: ActorImageType, t: Transaction) {
74 const imagesInfo = getImagesInfoFromObject(this.actorObject, type)
75 if (imagesInfo.length === 0) return
77 return updateActorImages(actor as MActorImages, type, imagesInfo, t)
80 private async saveActor (actor: MActor, t: Transaction) {
81 // Force the actor creation using findOrCreate() instead of save()
82 // Sometimes Sequelize skips the save() when it thinks the instance already exists
83 // (which could be false in a retried query)
84 const [ actorCreated, created ] = await ActorModel.findOrCreate<MActorFullActor>({
85 defaults: actor.toJSON(),
92 serverId: actor.serverId,
93 preferredUsername: actor.preferredUsername
100 return { actorCreated, created }
103 private async tryToFixActorUrlIfNeeded (actorCreated: MActor, newActor: MActor, created: boolean, t: Transaction) {
104 // Try to fix non HTTPS accounts of remote instances that fixed their URL afterwards
105 if (created !== true && actorCreated.url !== newActor.url) {
106 // Only fix http://example.com/account/djidane to https://example.com/account/djidane
107 if (actorCreated.url.replace(/^http:\/\//, '') !== newActor.url.replace(/^https:\/\//, '')) {
108 throw new Error(`Actor from DB with URL ${actorCreated.url} does not correspond to actor ${newActor.url}`)
111 actorCreated.url = newActor.url
112 await actorCreated.save({ transaction: t })
116 private async saveAccount (actor: MActorId, t: Transaction) {
117 const [ accountCreated ] = await AccountModel.findOrCreate({
119 name: getActorDisplayNameFromObject(this.actorObject),
120 description: this.actorObject.summary,
129 return accountCreated as MAccount
132 private async saveVideoChannel (actor: MActorId, t: Transaction) {
133 const [ videoChannelCreated ] = await VideoChannelModel.findOrCreate({
135 name: getActorDisplayNameFromObject(this.actorObject),
136 description: this.actorObject.summary,
137 support: this.actorObject.support,
139 accountId: this.ownerActor.Account.id
147 return videoChannelCreated as MChannel