aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2019-01-04 08:56:20 +0100
committerChocobozzz <chocobozzz@cpy.re>2019-01-09 11:15:15 +0100
commitf7cc67b455a12ccae9b0ea16876d166720364357 (patch)
treeeac2cdbf2e92a16b3eda5d74371c82bd79ae22cb /server/models
parentdc13348070d808d0ba3feb56a435b835c2e7e791 (diff)
downloadPeerTube-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.ts24
-rw-r--r--server/models/account/user-notification-setting.ts32
-rw-r--r--server/models/account/user-notification.ts95
-rw-r--r--server/models/account/user.ts51
-rw-r--r--server/models/video/video-comment.ts41
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
2import { AccountModel } from './account' 2import { AccountModel } from './account'
3import { getSort } from '../utils' 3import { getSort } from '../utils'
4import { AccountBlock } from '../../../shared/models/blocklist' 4import { AccountBlock } from '../../../shared/models/blocklist'
5import { Op } from 'sequelize'
5 6
6enum ScopeNames { 7enum 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'
25import { VideoAbuseModel } from '../video/video-abuse' 25import { VideoAbuseModel } from '../video/video-abuse'
26import { VideoBlacklistModel } from '../video/video-blacklist' 26import { VideoBlacklistModel } from '../video/video-blacklist'
27import { VideoImportModel } from '../video/video-import' 27import { VideoImportModel } from '../video/video-import'
28import { ActorModel } from '../activitypub/actor'
29import { ActorFollowModel } from '../activitypub/actor-follow'
28 30
29enum ScopeNames { 31enum 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
41function buildChannelInclude () { 43function 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
49function buildAccountInclude () { 51function 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
18import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' 18import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object'
19import { VideoComment } from '../../../shared/models/videos/video-comment.model' 19import { VideoComment } from '../../../shared/models/videos/video-comment.model'
20import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' 20import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc'
21import { CONSTRAINTS_FIELDS } from '../../initializers' 21import { CONFIG, CONSTRAINTS_FIELDS } from '../../initializers'
22import { sendDeleteVideoComment } from '../../lib/activitypub/send' 22import { sendDeleteVideoComment } from '../../lib/activitypub/send'
23import { AccountModel } from '../account/account' 23import { AccountModel } from '../account/account'
24import { ActorModel } from '../activitypub/actor' 24import { ActorModel } from '../activitypub/actor'
@@ -29,6 +29,9 @@ import { VideoModel } from './video'
29import { VideoChannelModel } from './video-channel' 29import { VideoChannelModel } from './video-channel'
30import { getServerActor } from '../../helpers/utils' 30import { getServerActor } from '../../helpers/utils'
31import { UserModel } from '../account/user' 31import { UserModel } from '../account/user'
32import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor'
33import { regexpCapture } from '../../helpers/regexp'
34import { uniq } from 'lodash'
32 35
33enum ScopeNames { 36enum 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,