]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - server/lib/activitypub/actors/shared/creator.ts
999aed97d862cb1f6463ff9768e51a9fb077ab60
[github/Chocobozzz/PeerTube.git] / server / lib / activitypub / actors / shared / creator.ts
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 { updateActorImageInstance } from '../image'
10 import { getActorAttributesFromObject, getActorDisplayNameFromObject, getImageInfoFromObject } from './object-to-model-attributes'
11 import { fetchActorFollowsCount } from './url-to-object'
12
13 export class APActorCreator {
14
15 constructor (
16 private readonly actorObject: ActivityPubActor,
17 private readonly ownerActor?: MActorFullActor
18 ) {
19
20 }
21
22 async create (): Promise<MActorFullActor> {
23 const { followersCount, followingCount } = await fetchActorFollowsCount(this.actorObject)
24
25 const actorInstance = new ActorModel(getActorAttributesFromObject(this.actorObject, followersCount, followingCount))
26
27 return sequelizeTypescript.transaction(async t => {
28 const server = await this.setServer(actorInstance, t)
29
30 await this.setImageIfNeeded(actorInstance, ActorImageType.AVATAR, t)
31 await this.setImageIfNeeded(actorInstance, ActorImageType.BANNER, t)
32
33 const { actorCreated, created } = await this.saveActor(actorInstance, t)
34
35 await this.tryToFixActorUrlIfNeeded(actorCreated, actorInstance, created, t)
36
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
40 }
41
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 })
45 }
46
47 actorCreated.Server = server
48
49 return actorCreated
50 })
51 }
52
53 private async setServer (actor: MActor, t: Transaction) {
54 const actorHost = new URL(actor.url).host
55
56 const serverOptions = {
57 where: {
58 host: actorHost
59 },
60 defaults: {
61 host: actorHost
62 },
63 transaction: t
64 }
65 const [ server ] = await ServerModel.findOrCreate(serverOptions)
66
67 // Save our new account in database
68 actor.serverId = server.id
69
70 return server as MServer
71 }
72
73 private async setImageIfNeeded (actor: MActor, type: ActorImageType, t: Transaction) {
74 const imageInfo = getImageInfoFromObject(this.actorObject, type)
75 if (!imageInfo) return
76
77 return updateActorImageInstance(actor as MActorImages, type, imageInfo, t)
78 }
79
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(),
86 where: {
87 [Op.or]: [
88 {
89 url: actor.url
90 },
91 {
92 serverId: actor.serverId,
93 preferredUsername: actor.preferredUsername
94 }
95 ]
96 },
97 transaction: t
98 })
99
100 return { actorCreated, created }
101 }
102
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}`)
109 }
110
111 actorCreated.url = newActor.url
112 await actorCreated.save({ transaction: t })
113 }
114 }
115
116 private async saveAccount (actor: MActorId, t: Transaction) {
117 const [ accountCreated ] = await AccountModel.findOrCreate({
118 defaults: {
119 name: getActorDisplayNameFromObject(this.actorObject),
120 description: this.actorObject.summary,
121 actorId: actor.id
122 },
123 where: {
124 actorId: actor.id
125 },
126 transaction: t
127 })
128
129 return accountCreated as MAccount
130 }
131
132 private async saveVideoChannel (actor: MActorId, t: Transaction) {
133 const [ videoChannelCreated ] = await VideoChannelModel.findOrCreate({
134 defaults: {
135 name: getActorDisplayNameFromObject(this.actorObject),
136 description: this.actorObject.summary,
137 support: this.actorObject.support,
138 actorId: actor.id,
139 accountId: this.ownerActor.Account.id
140 },
141 where: {
142 actorId: actor.id
143 },
144 transaction: t
145 })
146
147 return videoChannelCreated as MChannel
148 }
149 }