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