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