]>
Commit | Line | Data |
---|---|---|
136d7efd C |
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' | |
d0800f76 | 9 | import { updateActorImages } from '../image' |
10 | import { getActorAttributesFromObject, getActorDisplayNameFromObject, getImagesInfoFromObject } from './object-to-model-attributes' | |
136d7efd C |
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 | ||
136d7efd C |
30 | const { actorCreated, created } = await this.saveActor(actorInstance, t) |
31 | ||
d0800f76 | 32 | await this.setImageIfNeeded(actorCreated, ActorImageType.AVATAR, t) |
33 | await this.setImageIfNeeded(actorCreated, ActorImageType.BANNER, t) | |
34 | ||
136d7efd C |
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) { | |
d0800f76 | 74 | const imagesInfo = getImagesInfoFromObject(this.actorObject, type) |
75 | if (imagesInfo.length === 0) return | |
136d7efd | 76 | |
d0800f76 | 77 | return updateActorImages(actor as MActorImages, type, imagesInfo, t) |
136d7efd C |
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 | } |