]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blame_incremental - server/models/account/account.ts
Refactor a little bit client canonical URL
[github/Chocobozzz/PeerTube.git] / server / models / account / account.ts
... / ...
CommitLineData
1import * as Bluebird from 'bluebird'
2import { FindOptions, IncludeOptions, Op, Transaction, WhereOptions } from 'sequelize'
3import {
4 AllowNull,
5 BeforeDestroy,
6 BelongsTo,
7 Column,
8 CreatedAt,
9 DataType,
10 Default,
11 DefaultScope,
12 ForeignKey,
13 HasMany,
14 Is,
15 Model,
16 Scopes,
17 Table,
18 UpdatedAt
19} from 'sequelize-typescript'
20import { ModelCache } from '@server/models/model-cache'
21import { Account, AccountSummary } from '../../../shared/models/actors'
22import { isAccountDescriptionValid } from '../../helpers/custom-validators/accounts'
23import { CONSTRAINTS_FIELDS, SERVER_ACTOR_NAME, WEBSERVER } from '../../initializers/constants'
24import { sendDeleteActor } from '../../lib/activitypub/send'
25import {
26 MAccount,
27 MAccountActor,
28 MAccountAP,
29 MAccountDefault,
30 MAccountFormattable,
31 MAccountSummaryFormattable,
32 MChannelActor
33} from '../../types/models'
34import { ActorModel } from '../activitypub/actor'
35import { ActorFollowModel } from '../activitypub/actor-follow'
36import { ApplicationModel } from '../application/application'
37import { AvatarModel } from '../avatar/avatar'
38import { ServerModel } from '../server/server'
39import { ServerBlocklistModel } from '../server/server-blocklist'
40import { getSort, throwIfNotValid } from '../utils'
41import { VideoModel } from '../video/video'
42import { VideoChannelModel } from '../video/video-channel'
43import { VideoCommentModel } from '../video/video-comment'
44import { VideoPlaylistModel } from '../video/video-playlist'
45import { AccountBlocklistModel } from './account-blocklist'
46import { UserModel } from './user'
47
48export enum ScopeNames {
49 SUMMARY = 'SUMMARY'
50}
51
52export type SummaryOptions = {
53 actorRequired?: boolean // Default: true
54 whereActor?: WhereOptions
55 withAccountBlockerIds?: number[]
56}
57
58@DefaultScope(() => ({
59 include: [
60 {
61 model: ActorModel, // Default scope includes avatar and server
62 required: true
63 }
64 ]
65}))
66@Scopes(() => ({
67 [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => {
68 const whereActor = options.whereActor || undefined
69
70 const serverInclude: IncludeOptions = {
71 attributes: [ 'host' ],
72 model: ServerModel.unscoped(),
73 required: false
74 }
75
76 const query: FindOptions = {
77 attributes: [ 'id', 'name', 'actorId' ],
78 include: [
79 {
80 attributes: [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ],
81 model: ActorModel.unscoped(),
82 required: options.actorRequired ?? true,
83 where: whereActor,
84 include: [
85 serverInclude,
86
87 {
88 model: AvatarModel.unscoped(),
89 required: false
90 }
91 ]
92 }
93 ]
94 }
95
96 if (options.withAccountBlockerIds) {
97 query.include.push({
98 attributes: [ 'id' ],
99 model: AccountBlocklistModel.unscoped(),
100 as: 'BlockedAccounts',
101 required: false,
102 where: {
103 accountId: {
104 [Op.in]: options.withAccountBlockerIds
105 }
106 }
107 })
108
109 serverInclude.include = [
110 {
111 attributes: [ 'id' ],
112 model: ServerBlocklistModel.unscoped(),
113 required: false,
114 where: {
115 accountId: {
116 [Op.in]: options.withAccountBlockerIds
117 }
118 }
119 }
120 ]
121 }
122
123 return query
124 }
125}))
126@Table({
127 tableName: 'account',
128 indexes: [
129 {
130 fields: [ 'actorId' ],
131 unique: true
132 },
133 {
134 fields: [ 'applicationId' ]
135 },
136 {
137 fields: [ 'userId' ]
138 }
139 ]
140})
141export class AccountModel extends Model<AccountModel> {
142
143 @AllowNull(false)
144 @Column
145 name: string
146
147 @AllowNull(true)
148 @Default(null)
149 @Is('AccountDescription', value => throwIfNotValid(value, isAccountDescriptionValid, 'description', true))
150 @Column(DataType.STRING(CONSTRAINTS_FIELDS.USERS.DESCRIPTION.max))
151 description: string
152
153 @CreatedAt
154 createdAt: Date
155
156 @UpdatedAt
157 updatedAt: Date
158
159 @ForeignKey(() => ActorModel)
160 @Column
161 actorId: number
162
163 @BelongsTo(() => ActorModel, {
164 foreignKey: {
165 allowNull: false
166 },
167 onDelete: 'cascade'
168 })
169 Actor: ActorModel
170
171 @ForeignKey(() => UserModel)
172 @Column
173 userId: number
174
175 @BelongsTo(() => UserModel, {
176 foreignKey: {
177 allowNull: true
178 },
179 onDelete: 'cascade'
180 })
181 User: UserModel
182
183 @ForeignKey(() => ApplicationModel)
184 @Column
185 applicationId: number
186
187 @BelongsTo(() => ApplicationModel, {
188 foreignKey: {
189 allowNull: true
190 },
191 onDelete: 'cascade'
192 })
193 Application: ApplicationModel
194
195 @HasMany(() => VideoChannelModel, {
196 foreignKey: {
197 allowNull: false
198 },
199 onDelete: 'cascade',
200 hooks: true
201 })
202 VideoChannels: VideoChannelModel[]
203
204 @HasMany(() => VideoPlaylistModel, {
205 foreignKey: {
206 allowNull: false
207 },
208 onDelete: 'cascade',
209 hooks: true
210 })
211 VideoPlaylists: VideoPlaylistModel[]
212
213 @HasMany(() => VideoCommentModel, {
214 foreignKey: {
215 allowNull: true
216 },
217 onDelete: 'cascade',
218 hooks: true
219 })
220 VideoComments: VideoCommentModel[]
221
222 @HasMany(() => AccountBlocklistModel, {
223 foreignKey: {
224 name: 'targetAccountId',
225 allowNull: false
226 },
227 as: 'BlockedAccounts',
228 onDelete: 'CASCADE'
229 })
230 BlockedAccounts: AccountBlocklistModel[]
231
232 @BeforeDestroy
233 static async sendDeleteIfOwned (instance: AccountModel, options) {
234 if (!instance.Actor) {
235 instance.Actor = await instance.$get('Actor', { transaction: options.transaction })
236 }
237
238 await ActorFollowModel.removeFollowsOf(instance.Actor.id, options.transaction)
239
240 if (instance.isOwned()) {
241 return sendDeleteActor(instance.Actor, options.transaction)
242 }
243
244 return undefined
245 }
246
247 static load (id: number, transaction?: Transaction): Bluebird<MAccountDefault> {
248 return AccountModel.findByPk(id, { transaction })
249 }
250
251 static loadByNameWithHost (nameWithHost: string): Bluebird<MAccountDefault> {
252 const [ accountName, host ] = nameWithHost.split('@')
253
254 if (!host || host === WEBSERVER.HOST) return AccountModel.loadLocalByName(accountName)
255
256 return AccountModel.loadByNameAndHost(accountName, host)
257 }
258
259 static loadLocalByName (name: string): Bluebird<MAccountDefault> {
260 const fun = () => {
261 const query = {
262 where: {
263 [Op.or]: [
264 {
265 userId: {
266 [Op.ne]: null
267 }
268 },
269 {
270 applicationId: {
271 [Op.ne]: null
272 }
273 }
274 ]
275 },
276 include: [
277 {
278 model: ActorModel,
279 required: true,
280 where: {
281 preferredUsername: name
282 }
283 }
284 ]
285 }
286
287 return AccountModel.findOne(query)
288 }
289
290 return ModelCache.Instance.doCache({
291 cacheType: 'local-account-name',
292 key: name,
293 fun,
294 // The server actor never change, so we can easily cache it
295 whitelist: () => name === SERVER_ACTOR_NAME
296 })
297 }
298
299 static loadByNameAndHost (name: string, host: string): Bluebird<MAccountDefault> {
300 const query = {
301 include: [
302 {
303 model: ActorModel,
304 required: true,
305 where: {
306 preferredUsername: name
307 },
308 include: [
309 {
310 model: ServerModel,
311 required: true,
312 where: {
313 host
314 }
315 }
316 ]
317 }
318 ]
319 }
320
321 return AccountModel.findOne(query)
322 }
323
324 static loadByUrl (url: string, transaction?: Transaction): Bluebird<MAccountDefault> {
325 const query = {
326 include: [
327 {
328 model: ActorModel,
329 required: true,
330 where: {
331 url
332 }
333 }
334 ],
335 transaction
336 }
337
338 return AccountModel.findOne(query)
339 }
340
341 static listForApi (start: number, count: number, sort: string) {
342 const query = {
343 offset: start,
344 limit: count,
345 order: getSort(sort)
346 }
347
348 return AccountModel.findAndCountAll(query)
349 .then(({ rows, count }) => {
350 return {
351 data: rows,
352 total: count
353 }
354 })
355 }
356
357 static loadAccountIdFromVideo (videoId: number): Bluebird<MAccount> {
358 const query = {
359 include: [
360 {
361 attributes: [ 'id', 'accountId' ],
362 model: VideoChannelModel.unscoped(),
363 required: true,
364 include: [
365 {
366 attributes: [ 'id', 'channelId' ],
367 model: VideoModel.unscoped(),
368 where: {
369 id: videoId
370 }
371 }
372 ]
373 }
374 ]
375 }
376
377 return AccountModel.findOne(query)
378 }
379
380 static listLocalsForSitemap (sort: string): Bluebird<MAccountActor[]> {
381 const query = {
382 attributes: [ ],
383 offset: 0,
384 order: getSort(sort),
385 include: [
386 {
387 attributes: [ 'preferredUsername', 'serverId' ],
388 model: ActorModel.unscoped(),
389 where: {
390 serverId: null
391 }
392 }
393 ]
394 }
395
396 return AccountModel
397 .unscoped()
398 .findAll(query)
399 }
400
401 getClientUrl () {
402 return WEBSERVER.URL + '/accounts/' + this.Actor.getIdentifier()
403 }
404
405 toFormattedJSON (this: MAccountFormattable): Account {
406 const actor = this.Actor.toFormattedJSON()
407 const account = {
408 id: this.id,
409 displayName: this.getDisplayName(),
410 description: this.description,
411 createdAt: this.createdAt,
412 updatedAt: this.updatedAt,
413 userId: this.userId ? this.userId : undefined
414 }
415
416 return Object.assign(actor, account)
417 }
418
419 toFormattedSummaryJSON (this: MAccountSummaryFormattable): AccountSummary {
420 const actor = this.Actor.toFormattedSummaryJSON()
421
422 return {
423 id: this.id,
424 name: actor.name,
425 displayName: this.getDisplayName(),
426 url: actor.url,
427 host: actor.host,
428 avatar: actor.avatar
429 }
430 }
431
432 toActivityPubObject (this: MAccountAP) {
433 const obj = this.Actor.toActivityPubObject(this.name)
434
435 return Object.assign(obj, {
436 summary: this.description
437 })
438 }
439
440 isOwned () {
441 return this.Actor.isOwned()
442 }
443
444 isOutdated () {
445 return this.Actor.isOutdated()
446 }
447
448 getDisplayName () {
449 return this.name
450 }
451
452 getLocalUrl (this: MAccountActor | MChannelActor) {
453 return WEBSERVER.URL + `/accounts/` + this.Actor.preferredUsername
454 }
455
456 isBlocked () {
457 return this.BlockedAccounts && this.BlockedAccounts.length !== 0
458 }
459}