diff options
author | Chocobozzz <me@florianbigard.com> | 2019-01-04 08:56:20 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-01-09 11:15:15 +0100 |
commit | f7cc67b455a12ccae9b0ea16876d166720364357 (patch) | |
tree | eac2cdbf2e92a16b3eda5d74371c82bd79ae22cb /server/models | |
parent | dc13348070d808d0ba3feb56a435b835c2e7e791 (diff) | |
download | PeerTube-f7cc67b455a12ccae9b0ea16876d166720364357.tar.gz PeerTube-f7cc67b455a12ccae9b0ea16876d166720364357.tar.zst PeerTube-f7cc67b455a12ccae9b0ea16876d166720364357.zip |
Add new follow, mention and user registered notifs
Diffstat (limited to 'server/models')
-rw-r--r-- | server/models/account/account-blocklist.ts | 24 | ||||
-rw-r--r-- | server/models/account/user-notification-setting.ts | 32 | ||||
-rw-r--r-- | server/models/account/user-notification.ts | 95 | ||||
-rw-r--r-- | server/models/account/user.ts | 51 | ||||
-rw-r--r-- | server/models/video/video-comment.ts | 41 |
5 files changed, 228 insertions, 15 deletions
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts index 54ac290c4..efd6ed59e 100644 --- a/server/models/account/account-blocklist.ts +++ b/server/models/account/account-blocklist.ts | |||
@@ -2,6 +2,7 @@ import { BelongsTo, Column, CreatedAt, ForeignKey, Model, Scopes, Table, Updated | |||
2 | import { AccountModel } from './account' | 2 | import { AccountModel } from './account' |
3 | import { getSort } from '../utils' | 3 | import { getSort } from '../utils' |
4 | import { AccountBlock } from '../../../shared/models/blocklist' | 4 | import { AccountBlock } from '../../../shared/models/blocklist' |
5 | import { Op } from 'sequelize' | ||
5 | 6 | ||
6 | enum ScopeNames { | 7 | enum ScopeNames { |
7 | WITH_ACCOUNTS = 'WITH_ACCOUNTS' | 8 | WITH_ACCOUNTS = 'WITH_ACCOUNTS' |
@@ -73,18 +74,33 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> { | |||
73 | BlockedAccount: AccountModel | 74 | BlockedAccount: AccountModel |
74 | 75 | ||
75 | static isAccountMutedBy (accountId: number, targetAccountId: number) { | 76 | static isAccountMutedBy (accountId: number, targetAccountId: number) { |
77 | return AccountBlocklistModel.isAccountMutedByMulti([ accountId ], targetAccountId) | ||
78 | .then(result => result[accountId]) | ||
79 | } | ||
80 | |||
81 | static isAccountMutedByMulti (accountIds: number[], targetAccountId: number) { | ||
76 | const query = { | 82 | const query = { |
77 | attributes: [ 'id' ], | 83 | attributes: [ 'accountId', 'id' ], |
78 | where: { | 84 | where: { |
79 | accountId, | 85 | accountId: { |
86 | [Op.any]: accountIds | ||
87 | }, | ||
80 | targetAccountId | 88 | targetAccountId |
81 | }, | 89 | }, |
82 | raw: true | 90 | raw: true |
83 | } | 91 | } |
84 | 92 | ||
85 | return AccountBlocklistModel.unscoped() | 93 | return AccountBlocklistModel.unscoped() |
86 | .findOne(query) | 94 | .findAll(query) |
87 | .then(a => !!a) | 95 | .then(rows => { |
96 | const result: { [accountId: number]: boolean } = {} | ||
97 | |||
98 | for (const accountId of accountIds) { | ||
99 | result[accountId] = !!rows.find(r => r.accountId === accountId) | ||
100 | } | ||
101 | |||
102 | return result | ||
103 | }) | ||
88 | } | 104 | } |
89 | 105 | ||
90 | static loadByAccountAndTarget (accountId: number, targetAccountId: number) { | 106 | static loadByAccountAndTarget (accountId: number, targetAccountId: number) { |
diff --git a/server/models/account/user-notification-setting.ts b/server/models/account/user-notification-setting.ts index 6470defa7..f1c3ac223 100644 --- a/server/models/account/user-notification-setting.ts +++ b/server/models/account/user-notification-setting.ts | |||
@@ -83,6 +83,33 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM | |||
83 | @Column | 83 | @Column |
84 | myVideoImportFinished: UserNotificationSettingValue | 84 | myVideoImportFinished: UserNotificationSettingValue |
85 | 85 | ||
86 | @AllowNull(false) | ||
87 | @Default(null) | ||
88 | @Is( | ||
89 | 'UserNotificationSettingNewUserRegistration', | ||
90 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'newUserRegistration') | ||
91 | ) | ||
92 | @Column | ||
93 | newUserRegistration: UserNotificationSettingValue | ||
94 | |||
95 | @AllowNull(false) | ||
96 | @Default(null) | ||
97 | @Is( | ||
98 | 'UserNotificationSettingNewFollow', | ||
99 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'newFollow') | ||
100 | ) | ||
101 | @Column | ||
102 | newFollow: UserNotificationSettingValue | ||
103 | |||
104 | @AllowNull(false) | ||
105 | @Default(null) | ||
106 | @Is( | ||
107 | 'UserNotificationSettingCommentMention', | ||
108 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'commentMention') | ||
109 | ) | ||
110 | @Column | ||
111 | commentMention: UserNotificationSettingValue | ||
112 | |||
86 | @ForeignKey(() => UserModel) | 113 | @ForeignKey(() => UserModel) |
87 | @Column | 114 | @Column |
88 | userId: number | 115 | userId: number |
@@ -114,7 +141,10 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM | |||
114 | videoAbuseAsModerator: this.videoAbuseAsModerator, | 141 | videoAbuseAsModerator: this.videoAbuseAsModerator, |
115 | blacklistOnMyVideo: this.blacklistOnMyVideo, | 142 | blacklistOnMyVideo: this.blacklistOnMyVideo, |
116 | myVideoPublished: this.myVideoPublished, | 143 | myVideoPublished: this.myVideoPublished, |
117 | myVideoImportFinished: this.myVideoImportFinished | 144 | myVideoImportFinished: this.myVideoImportFinished, |
145 | newUserRegistration: this.newUserRegistration, | ||
146 | commentMention: this.commentMention, | ||
147 | newFollow: this.newFollow | ||
118 | } | 148 | } |
119 | } | 149 | } |
120 | } | 150 | } |
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts index 251244374..79afce600 100644 --- a/server/models/account/user-notification.ts +++ b/server/models/account/user-notification.ts | |||
@@ -25,6 +25,8 @@ import { AccountModel } from './account' | |||
25 | import { VideoAbuseModel } from '../video/video-abuse' | 25 | import { VideoAbuseModel } from '../video/video-abuse' |
26 | import { VideoBlacklistModel } from '../video/video-blacklist' | 26 | import { VideoBlacklistModel } from '../video/video-blacklist' |
27 | import { VideoImportModel } from '../video/video-import' | 27 | import { VideoImportModel } from '../video/video-import' |
28 | import { ActorModel } from '../activitypub/actor' | ||
29 | import { ActorFollowModel } from '../activitypub/actor-follow' | ||
28 | 30 | ||
29 | enum ScopeNames { | 31 | enum ScopeNames { |
30 | WITH_ALL = 'WITH_ALL' | 32 | WITH_ALL = 'WITH_ALL' |
@@ -38,17 +40,17 @@ function buildVideoInclude (required: boolean) { | |||
38 | } | 40 | } |
39 | } | 41 | } |
40 | 42 | ||
41 | function buildChannelInclude () { | 43 | function buildChannelInclude (required: boolean) { |
42 | return { | 44 | return { |
43 | required: true, | 45 | required, |
44 | attributes: [ 'id', 'name' ], | 46 | attributes: [ 'id', 'name' ], |
45 | model: () => VideoChannelModel.unscoped() | 47 | model: () => VideoChannelModel.unscoped() |
46 | } | 48 | } |
47 | } | 49 | } |
48 | 50 | ||
49 | function buildAccountInclude () { | 51 | function buildAccountInclude (required: boolean) { |
50 | return { | 52 | return { |
51 | required: true, | 53 | required, |
52 | attributes: [ 'id', 'name' ], | 54 | attributes: [ 'id', 'name' ], |
53 | model: () => AccountModel.unscoped() | 55 | model: () => AccountModel.unscoped() |
54 | } | 56 | } |
@@ -58,14 +60,14 @@ function buildAccountInclude () { | |||
58 | [ScopeNames.WITH_ALL]: { | 60 | [ScopeNames.WITH_ALL]: { |
59 | include: [ | 61 | include: [ |
60 | Object.assign(buildVideoInclude(false), { | 62 | Object.assign(buildVideoInclude(false), { |
61 | include: [ buildChannelInclude() ] | 63 | include: [ buildChannelInclude(true) ] |
62 | }), | 64 | }), |
63 | { | 65 | { |
64 | attributes: [ 'id', 'originCommentId' ], | 66 | attributes: [ 'id', 'originCommentId' ], |
65 | model: () => VideoCommentModel.unscoped(), | 67 | model: () => VideoCommentModel.unscoped(), |
66 | required: false, | 68 | required: false, |
67 | include: [ | 69 | include: [ |
68 | buildAccountInclude(), | 70 | buildAccountInclude(true), |
69 | buildVideoInclude(true) | 71 | buildVideoInclude(true) |
70 | ] | 72 | ] |
71 | }, | 73 | }, |
@@ -86,6 +88,42 @@ function buildAccountInclude () { | |||
86 | model: () => VideoImportModel.unscoped(), | 88 | model: () => VideoImportModel.unscoped(), |
87 | required: false, | 89 | required: false, |
88 | include: [ buildVideoInclude(false) ] | 90 | include: [ buildVideoInclude(false) ] |
91 | }, | ||
92 | { | ||
93 | attributes: [ 'id', 'name' ], | ||
94 | model: () => AccountModel.unscoped(), | ||
95 | required: false, | ||
96 | include: [ | ||
97 | { | ||
98 | attributes: [ 'id', 'preferredUsername' ], | ||
99 | model: () => ActorModel.unscoped(), | ||
100 | required: true | ||
101 | } | ||
102 | ] | ||
103 | }, | ||
104 | { | ||
105 | attributes: [ 'id' ], | ||
106 | model: () => ActorFollowModel.unscoped(), | ||
107 | required: false, | ||
108 | include: [ | ||
109 | { | ||
110 | attributes: [ 'preferredUsername' ], | ||
111 | model: () => ActorModel.unscoped(), | ||
112 | required: true, | ||
113 | as: 'ActorFollower', | ||
114 | include: [ buildAccountInclude(true) ] | ||
115 | }, | ||
116 | { | ||
117 | attributes: [ 'preferredUsername' ], | ||
118 | model: () => ActorModel.unscoped(), | ||
119 | required: true, | ||
120 | as: 'ActorFollowing', | ||
121 | include: [ | ||
122 | buildChannelInclude(false), | ||
123 | buildAccountInclude(false) | ||
124 | ] | ||
125 | } | ||
126 | ] | ||
89 | } | 127 | } |
90 | ] | 128 | ] |
91 | } | 129 | } |
@@ -193,6 +231,30 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
193 | }) | 231 | }) |
194 | VideoImport: VideoImportModel | 232 | VideoImport: VideoImportModel |
195 | 233 | ||
234 | @ForeignKey(() => AccountModel) | ||
235 | @Column | ||
236 | accountId: number | ||
237 | |||
238 | @BelongsTo(() => AccountModel, { | ||
239 | foreignKey: { | ||
240 | allowNull: true | ||
241 | }, | ||
242 | onDelete: 'cascade' | ||
243 | }) | ||
244 | Account: AccountModel | ||
245 | |||
246 | @ForeignKey(() => ActorFollowModel) | ||
247 | @Column | ||
248 | actorFollowId: number | ||
249 | |||
250 | @BelongsTo(() => ActorFollowModel, { | ||
251 | foreignKey: { | ||
252 | allowNull: true | ||
253 | }, | ||
254 | onDelete: 'cascade' | ||
255 | }) | ||
256 | ActorFollow: ActorFollowModel | ||
257 | |||
196 | static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) { | 258 | static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) { |
197 | const query: IFindOptions<UserNotificationModel> = { | 259 | const query: IFindOptions<UserNotificationModel> = { |
198 | offset: start, | 260 | offset: start, |
@@ -264,6 +326,25 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
264 | video: this.formatVideo(this.VideoBlacklist.Video) | 326 | video: this.formatVideo(this.VideoBlacklist.Video) |
265 | } : undefined | 327 | } : undefined |
266 | 328 | ||
329 | const account = this.Account ? { | ||
330 | id: this.Account.id, | ||
331 | displayName: this.Account.getDisplayName(), | ||
332 | name: this.Account.Actor.preferredUsername | ||
333 | } : undefined | ||
334 | |||
335 | const actorFollow = this.ActorFollow ? { | ||
336 | id: this.ActorFollow.id, | ||
337 | follower: { | ||
338 | displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(), | ||
339 | name: this.ActorFollow.ActorFollower.preferredUsername | ||
340 | }, | ||
341 | following: { | ||
342 | type: this.ActorFollow.ActorFollowing.VideoChannel ? 'channel' as 'channel' : 'account' as 'account', | ||
343 | displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(), | ||
344 | name: this.ActorFollow.ActorFollowing.preferredUsername | ||
345 | } | ||
346 | } : undefined | ||
347 | |||
267 | return { | 348 | return { |
268 | id: this.id, | 349 | id: this.id, |
269 | type: this.type, | 350 | type: this.type, |
@@ -273,6 +354,8 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
273 | comment, | 354 | comment, |
274 | videoAbuse, | 355 | videoAbuse, |
275 | videoBlacklist, | 356 | videoBlacklist, |
357 | account, | ||
358 | actorFollow, | ||
276 | createdAt: this.createdAt.toISOString(), | 359 | createdAt: this.createdAt.toISOString(), |
277 | updatedAt: this.updatedAt.toISOString() | 360 | updatedAt: this.updatedAt.toISOString() |
278 | } | 361 | } |
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 33f56f641..017a96657 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -330,6 +330,16 @@ export class UserModel extends Model<UserModel> { | |||
330 | return UserModel.unscoped().findAll(query) | 330 | return UserModel.unscoped().findAll(query) |
331 | } | 331 | } |
332 | 332 | ||
333 | static listByUsernames (usernames: string[]) { | ||
334 | const query = { | ||
335 | where: { | ||
336 | username: usernames | ||
337 | } | ||
338 | } | ||
339 | |||
340 | return UserModel.findAll(query) | ||
341 | } | ||
342 | |||
333 | static loadById (id: number) { | 343 | static loadById (id: number) { |
334 | return UserModel.findById(id) | 344 | return UserModel.findById(id) |
335 | } | 345 | } |
@@ -424,6 +434,47 @@ export class UserModel extends Model<UserModel> { | |||
424 | return UserModel.findOne(query) | 434 | return UserModel.findOne(query) |
425 | } | 435 | } |
426 | 436 | ||
437 | static loadByChannelActorId (videoChannelActorId: number) { | ||
438 | const query = { | ||
439 | include: [ | ||
440 | { | ||
441 | required: true, | ||
442 | attributes: [ 'id' ], | ||
443 | model: AccountModel.unscoped(), | ||
444 | include: [ | ||
445 | { | ||
446 | required: true, | ||
447 | attributes: [ 'id' ], | ||
448 | model: VideoChannelModel.unscoped(), | ||
449 | where: { | ||
450 | actorId: videoChannelActorId | ||
451 | } | ||
452 | } | ||
453 | ] | ||
454 | } | ||
455 | ] | ||
456 | } | ||
457 | |||
458 | return UserModel.findOne(query) | ||
459 | } | ||
460 | |||
461 | static loadByAccountActorId (accountActorId: number) { | ||
462 | const query = { | ||
463 | include: [ | ||
464 | { | ||
465 | required: true, | ||
466 | attributes: [ 'id' ], | ||
467 | model: AccountModel.unscoped(), | ||
468 | where: { | ||
469 | actorId: accountActorId | ||
470 | } | ||
471 | } | ||
472 | ] | ||
473 | } | ||
474 | |||
475 | return UserModel.findOne(query) | ||
476 | } | ||
477 | |||
427 | static getOriginalVideoFileTotalFromUser (user: UserModel) { | 478 | static getOriginalVideoFileTotalFromUser (user: UserModel) { |
428 | // Don't use sequelize because we need to use a sub query | 479 | // Don't use sequelize because we need to use a sub query |
429 | const query = UserModel.generateUserQuotaBaseSQL() | 480 | const query = UserModel.generateUserQuotaBaseSQL() |
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index d8fc2a564..cf6278da7 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -18,7 +18,7 @@ import { ActivityTagObject } from '../../../shared/models/activitypub/objects/co | |||
18 | import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' | 18 | import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' |
19 | import { VideoComment } from '../../../shared/models/videos/video-comment.model' | 19 | import { VideoComment } from '../../../shared/models/videos/video-comment.model' |
20 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 20 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
21 | import { CONSTRAINTS_FIELDS } from '../../initializers' | 21 | import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers' |
22 | import { sendDeleteVideoComment } from '../../lib/activitypub/send' | 22 | import { sendDeleteVideoComment } from '../../lib/activitypub/send' |
23 | import { AccountModel } from '../account/account' | 23 | import { AccountModel } from '../account/account' |
24 | import { ActorModel } from '../activitypub/actor' | 24 | import { ActorModel } from '../activitypub/actor' |
@@ -29,6 +29,9 @@ import { VideoModel } from './video' | |||
29 | import { VideoChannelModel } from './video-channel' | 29 | import { VideoChannelModel } from './video-channel' |
30 | import { getServerActor } from '../../helpers/utils' | 30 | import { getServerActor } from '../../helpers/utils' |
31 | import { UserModel } from '../account/user' | 31 | import { UserModel } from '../account/user' |
32 | import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' | ||
33 | import { regexpCapture } from '../../helpers/regexp' | ||
34 | import { uniq } from 'lodash' | ||
32 | 35 | ||
33 | enum ScopeNames { | 36 | enum ScopeNames { |
34 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 37 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
@@ -370,9 +373,11 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
370 | id: { | 373 | id: { |
371 | [ Sequelize.Op.in ]: Sequelize.literal('(' + | 374 | [ Sequelize.Op.in ]: Sequelize.literal('(' + |
372 | 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' + | 375 | 'WITH RECURSIVE children (id, "inReplyToCommentId") AS ( ' + |
373 | 'SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ' + comment.id + ' UNION ' + | 376 | `SELECT id, "inReplyToCommentId" FROM "videoComment" WHERE id = ${comment.id} ` + |
374 | 'SELECT p.id, p."inReplyToCommentId" from "videoComment" p ' + | 377 | 'UNION ' + |
375 | 'INNER JOIN children c ON c."inReplyToCommentId" = p.id) ' + | 378 | 'SELECT "parent"."id", "parent"."inReplyToCommentId" FROM "videoComment" "parent" ' + |
379 | 'INNER JOIN "children" ON "children"."inReplyToCommentId" = "parent"."id"' + | ||
380 | ') ' + | ||
376 | 'SELECT id FROM children' + | 381 | 'SELECT id FROM children' + |
377 | ')'), | 382 | ')'), |
378 | [ Sequelize.Op.ne ]: comment.id | 383 | [ Sequelize.Op.ne ]: comment.id |
@@ -460,6 +465,34 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
460 | return this.Account.isOwned() | 465 | return this.Account.isOwned() |
461 | } | 466 | } |
462 | 467 | ||
468 | extractMentions () { | ||
469 | if (!this.text) return [] | ||
470 | |||
471 | const localMention = `@(${actorNameAlphabet}+)` | ||
472 | const remoteMention = `${localMention}@${CONFIG.WEBSERVER.HOST}` | ||
473 | |||
474 | const remoteMentionsRegex = new RegExp(' ' + remoteMention + ' ', 'g') | ||
475 | const localMentionsRegex = new RegExp(' ' + localMention + ' ', 'g') | ||
476 | const firstMentionRegex = new RegExp('^(?:(?:' + remoteMention + ')|(?:' + localMention + ')) ', 'g') | ||
477 | const endMentionRegex = new RegExp(' (?:(?:' + remoteMention + ')|(?:' + localMention + '))$', 'g') | ||
478 | |||
479 | return uniq( | ||
480 | [].concat( | ||
481 | regexpCapture(this.text, remoteMentionsRegex) | ||
482 | .map(([ , username ]) => username), | ||
483 | |||
484 | regexpCapture(this.text, localMentionsRegex) | ||
485 | .map(([ , username ]) => username), | ||
486 | |||
487 | regexpCapture(this.text, firstMentionRegex) | ||
488 | .map(([ , username1, username2 ]) => username1 || username2), | ||
489 | |||
490 | regexpCapture(this.text, endMentionRegex) | ||
491 | .map(([ , username1, username2 ]) => username1 || username2) | ||
492 | ) | ||
493 | ) | ||
494 | } | ||
495 | |||
463 | toFormattedJSON () { | 496 | toFormattedJSON () { |
464 | return { | 497 | return { |
465 | id: this.id, | 498 | id: this.id, |