diff options
author | Chocobozzz <me@florianbigard.com> | 2018-12-26 10:36:24 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-01-09 11:15:15 +0100 |
commit | cef534ed53e4518fe0acf581bfe880788d42fc36 (patch) | |
tree | 115b51ea5136849a2336d44915c7780649f25dc2 /server/models/account | |
parent | 1de1d05f4c61fe059fa5e24e79c92582f0e7e4b3 (diff) | |
download | PeerTube-cef534ed53e4518fe0acf581bfe880788d42fc36.tar.gz PeerTube-cef534ed53e4518fe0acf581bfe880788d42fc36.tar.zst PeerTube-cef534ed53e4518fe0acf581bfe880788d42fc36.zip |
Add user notification base code
Diffstat (limited to 'server/models/account')
-rw-r--r-- | server/models/account/user-notification-setting.ts | 100 | ||||
-rw-r--r-- | server/models/account/user-notification.ts | 256 | ||||
-rw-r--r-- | server/models/account/user.ts | 101 |
3 files changed, 450 insertions, 7 deletions
diff --git a/server/models/account/user-notification-setting.ts b/server/models/account/user-notification-setting.ts new file mode 100644 index 000000000..bc24b1e33 --- /dev/null +++ b/server/models/account/user-notification-setting.ts | |||
@@ -0,0 +1,100 @@ | |||
1 | import { | ||
2 | AfterDestroy, | ||
3 | AfterUpdate, | ||
4 | AllowNull, | ||
5 | BelongsTo, | ||
6 | Column, | ||
7 | CreatedAt, | ||
8 | Default, | ||
9 | ForeignKey, | ||
10 | Is, | ||
11 | Model, | ||
12 | Table, | ||
13 | UpdatedAt | ||
14 | } from 'sequelize-typescript' | ||
15 | import { throwIfNotValid } from '../utils' | ||
16 | import { UserModel } from './user' | ||
17 | import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' | ||
18 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model' | ||
19 | import { clearCacheByUserId } from '../../lib/oauth-model' | ||
20 | |||
21 | @Table({ | ||
22 | tableName: 'userNotificationSetting', | ||
23 | indexes: [ | ||
24 | { | ||
25 | fields: [ 'userId' ], | ||
26 | unique: true | ||
27 | } | ||
28 | ] | ||
29 | }) | ||
30 | export class UserNotificationSettingModel extends Model<UserNotificationSettingModel> { | ||
31 | |||
32 | @AllowNull(false) | ||
33 | @Default(null) | ||
34 | @Is( | ||
35 | 'UserNotificationSettingNewVideoFromSubscription', | ||
36 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'newVideoFromSubscription') | ||
37 | ) | ||
38 | @Column | ||
39 | newVideoFromSubscription: UserNotificationSettingValue | ||
40 | |||
41 | @AllowNull(false) | ||
42 | @Default(null) | ||
43 | @Is( | ||
44 | 'UserNotificationSettingNewCommentOnMyVideo', | ||
45 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'newCommentOnMyVideo') | ||
46 | ) | ||
47 | @Column | ||
48 | newCommentOnMyVideo: UserNotificationSettingValue | ||
49 | |||
50 | @AllowNull(false) | ||
51 | @Default(null) | ||
52 | @Is( | ||
53 | 'UserNotificationSettingVideoAbuseAsModerator', | ||
54 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'videoAbuseAsModerator') | ||
55 | ) | ||
56 | @Column | ||
57 | videoAbuseAsModerator: UserNotificationSettingValue | ||
58 | |||
59 | @AllowNull(false) | ||
60 | @Default(null) | ||
61 | @Is( | ||
62 | 'UserNotificationSettingBlacklistOnMyVideo', | ||
63 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'blacklistOnMyVideo') | ||
64 | ) | ||
65 | @Column | ||
66 | blacklistOnMyVideo: UserNotificationSettingValue | ||
67 | |||
68 | @ForeignKey(() => UserModel) | ||
69 | @Column | ||
70 | userId: number | ||
71 | |||
72 | @BelongsTo(() => UserModel, { | ||
73 | foreignKey: { | ||
74 | allowNull: false | ||
75 | }, | ||
76 | onDelete: 'cascade' | ||
77 | }) | ||
78 | User: UserModel | ||
79 | |||
80 | @CreatedAt | ||
81 | createdAt: Date | ||
82 | |||
83 | @UpdatedAt | ||
84 | updatedAt: Date | ||
85 | |||
86 | @AfterUpdate | ||
87 | @AfterDestroy | ||
88 | static removeTokenCache (instance: UserNotificationSettingModel) { | ||
89 | return clearCacheByUserId(instance.userId) | ||
90 | } | ||
91 | |||
92 | toFormattedJSON (): UserNotificationSetting { | ||
93 | return { | ||
94 | newCommentOnMyVideo: this.newCommentOnMyVideo, | ||
95 | newVideoFromSubscription: this.newVideoFromSubscription, | ||
96 | videoAbuseAsModerator: this.videoAbuseAsModerator, | ||
97 | blacklistOnMyVideo: this.blacklistOnMyVideo | ||
98 | } | ||
99 | } | ||
100 | } | ||
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts new file mode 100644 index 000000000..e22f0d57f --- /dev/null +++ b/server/models/account/user-notification.ts | |||
@@ -0,0 +1,256 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | ||
2 | import { UserNotification, UserNotificationType } from '../../../shared' | ||
3 | import { getSort, throwIfNotValid } from '../utils' | ||
4 | import { isBooleanValid } from '../../helpers/custom-validators/misc' | ||
5 | import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' | ||
6 | import { UserModel } from './user' | ||
7 | import { VideoModel } from '../video/video' | ||
8 | import { VideoCommentModel } from '../video/video-comment' | ||
9 | import { Op } from 'sequelize' | ||
10 | import { VideoChannelModel } from '../video/video-channel' | ||
11 | import { AccountModel } from './account' | ||
12 | import { VideoAbuseModel } from '../video/video-abuse' | ||
13 | import { VideoBlacklistModel } from '../video/video-blacklist' | ||
14 | |||
15 | enum ScopeNames { | ||
16 | WITH_ALL = 'WITH_ALL' | ||
17 | } | ||
18 | |||
19 | @Scopes({ | ||
20 | [ScopeNames.WITH_ALL]: { | ||
21 | include: [ | ||
22 | { | ||
23 | attributes: [ 'id', 'uuid', 'name' ], | ||
24 | model: () => VideoModel.unscoped(), | ||
25 | required: false, | ||
26 | include: [ | ||
27 | { | ||
28 | required: true, | ||
29 | attributes: [ 'id', 'name' ], | ||
30 | model: () => VideoChannelModel.unscoped() | ||
31 | } | ||
32 | ] | ||
33 | }, | ||
34 | { | ||
35 | attributes: [ 'id' ], | ||
36 | model: () => VideoCommentModel.unscoped(), | ||
37 | required: false, | ||
38 | include: [ | ||
39 | { | ||
40 | required: true, | ||
41 | attributes: [ 'id', 'name' ], | ||
42 | model: () => AccountModel.unscoped() | ||
43 | }, | ||
44 | { | ||
45 | required: true, | ||
46 | attributes: [ 'id', 'uuid', 'name' ], | ||
47 | model: () => VideoModel.unscoped() | ||
48 | } | ||
49 | ] | ||
50 | }, | ||
51 | { | ||
52 | attributes: [ 'id' ], | ||
53 | model: () => VideoAbuseModel.unscoped(), | ||
54 | required: false, | ||
55 | include: [ | ||
56 | { | ||
57 | required: true, | ||
58 | attributes: [ 'id', 'uuid', 'name' ], | ||
59 | model: () => VideoModel.unscoped() | ||
60 | } | ||
61 | ] | ||
62 | }, | ||
63 | { | ||
64 | attributes: [ 'id' ], | ||
65 | model: () => VideoBlacklistModel.unscoped(), | ||
66 | required: false, | ||
67 | include: [ | ||
68 | { | ||
69 | required: true, | ||
70 | attributes: [ 'id', 'uuid', 'name' ], | ||
71 | model: () => VideoModel.unscoped() | ||
72 | } | ||
73 | ] | ||
74 | } | ||
75 | ] | ||
76 | } | ||
77 | }) | ||
78 | @Table({ | ||
79 | tableName: 'userNotification', | ||
80 | indexes: [ | ||
81 | { | ||
82 | fields: [ 'videoId' ] | ||
83 | }, | ||
84 | { | ||
85 | fields: [ 'commentId' ] | ||
86 | } | ||
87 | ] | ||
88 | }) | ||
89 | export class UserNotificationModel extends Model<UserNotificationModel> { | ||
90 | |||
91 | @AllowNull(false) | ||
92 | @Default(null) | ||
93 | @Is('UserNotificationType', value => throwIfNotValid(value, isUserNotificationTypeValid, 'type')) | ||
94 | @Column | ||
95 | type: UserNotificationType | ||
96 | |||
97 | @AllowNull(false) | ||
98 | @Default(false) | ||
99 | @Is('UserNotificationRead', value => throwIfNotValid(value, isBooleanValid, 'read')) | ||
100 | @Column | ||
101 | read: boolean | ||
102 | |||
103 | @CreatedAt | ||
104 | createdAt: Date | ||
105 | |||
106 | @UpdatedAt | ||
107 | updatedAt: Date | ||
108 | |||
109 | @ForeignKey(() => UserModel) | ||
110 | @Column | ||
111 | userId: number | ||
112 | |||
113 | @BelongsTo(() => UserModel, { | ||
114 | foreignKey: { | ||
115 | allowNull: false | ||
116 | }, | ||
117 | onDelete: 'cascade' | ||
118 | }) | ||
119 | User: UserModel | ||
120 | |||
121 | @ForeignKey(() => VideoModel) | ||
122 | @Column | ||
123 | videoId: number | ||
124 | |||
125 | @BelongsTo(() => VideoModel, { | ||
126 | foreignKey: { | ||
127 | allowNull: true | ||
128 | }, | ||
129 | onDelete: 'cascade' | ||
130 | }) | ||
131 | Video: VideoModel | ||
132 | |||
133 | @ForeignKey(() => VideoCommentModel) | ||
134 | @Column | ||
135 | commentId: number | ||
136 | |||
137 | @BelongsTo(() => VideoCommentModel, { | ||
138 | foreignKey: { | ||
139 | allowNull: true | ||
140 | }, | ||
141 | onDelete: 'cascade' | ||
142 | }) | ||
143 | Comment: VideoCommentModel | ||
144 | |||
145 | @ForeignKey(() => VideoAbuseModel) | ||
146 | @Column | ||
147 | videoAbuseId: number | ||
148 | |||
149 | @BelongsTo(() => VideoAbuseModel, { | ||
150 | foreignKey: { | ||
151 | allowNull: true | ||
152 | }, | ||
153 | onDelete: 'cascade' | ||
154 | }) | ||
155 | VideoAbuse: VideoAbuseModel | ||
156 | |||
157 | @ForeignKey(() => VideoBlacklistModel) | ||
158 | @Column | ||
159 | videoBlacklistId: number | ||
160 | |||
161 | @BelongsTo(() => VideoBlacklistModel, { | ||
162 | foreignKey: { | ||
163 | allowNull: true | ||
164 | }, | ||
165 | onDelete: 'cascade' | ||
166 | }) | ||
167 | VideoBlacklist: VideoBlacklistModel | ||
168 | |||
169 | static listForApi (userId: number, start: number, count: number, sort: string) { | ||
170 | const query = { | ||
171 | offset: start, | ||
172 | limit: count, | ||
173 | order: getSort(sort), | ||
174 | where: { | ||
175 | userId | ||
176 | } | ||
177 | } | ||
178 | |||
179 | return UserNotificationModel.scope(ScopeNames.WITH_ALL) | ||
180 | .findAndCountAll(query) | ||
181 | .then(({ rows, count }) => { | ||
182 | return { | ||
183 | data: rows, | ||
184 | total: count | ||
185 | } | ||
186 | }) | ||
187 | } | ||
188 | |||
189 | static markAsRead (userId: number, notificationIds: number[]) { | ||
190 | const query = { | ||
191 | where: { | ||
192 | userId, | ||
193 | id: { | ||
194 | [Op.any]: notificationIds | ||
195 | } | ||
196 | } | ||
197 | } | ||
198 | |||
199 | return UserNotificationModel.update({ read: true }, query) | ||
200 | } | ||
201 | |||
202 | toFormattedJSON (): UserNotification { | ||
203 | const video = this.Video ? { | ||
204 | id: this.Video.id, | ||
205 | uuid: this.Video.uuid, | ||
206 | name: this.Video.name, | ||
207 | channel: { | ||
208 | id: this.Video.VideoChannel.id, | ||
209 | displayName: this.Video.VideoChannel.getDisplayName() | ||
210 | } | ||
211 | } : undefined | ||
212 | |||
213 | const comment = this.Comment ? { | ||
214 | id: this.Comment.id, | ||
215 | account: { | ||
216 | id: this.Comment.Account.id, | ||
217 | displayName: this.Comment.Account.getDisplayName() | ||
218 | }, | ||
219 | video: { | ||
220 | id: this.Comment.Video.id, | ||
221 | uuid: this.Comment.Video.uuid, | ||
222 | name: this.Comment.Video.name | ||
223 | } | ||
224 | } : undefined | ||
225 | |||
226 | const videoAbuse = this.VideoAbuse ? { | ||
227 | id: this.VideoAbuse.id, | ||
228 | video: { | ||
229 | id: this.VideoAbuse.Video.id, | ||
230 | uuid: this.VideoAbuse.Video.uuid, | ||
231 | name: this.VideoAbuse.Video.name | ||
232 | } | ||
233 | } : undefined | ||
234 | |||
235 | const videoBlacklist = this.VideoBlacklist ? { | ||
236 | id: this.VideoBlacklist.id, | ||
237 | video: { | ||
238 | id: this.VideoBlacklist.Video.id, | ||
239 | uuid: this.VideoBlacklist.Video.uuid, | ||
240 | name: this.VideoBlacklist.Video.name | ||
241 | } | ||
242 | } : undefined | ||
243 | |||
244 | return { | ||
245 | id: this.id, | ||
246 | type: this.type, | ||
247 | read: this.read, | ||
248 | video, | ||
249 | comment, | ||
250 | videoAbuse, | ||
251 | videoBlacklist, | ||
252 | createdAt: this.createdAt.toISOString(), | ||
253 | updatedAt: this.updatedAt.toISOString() | ||
254 | } | ||
255 | } | ||
256 | } | ||
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 180ced810..55ec14d05 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -32,8 +32,8 @@ import { | |||
32 | isUserUsernameValid, | 32 | isUserUsernameValid, |
33 | isUserVideoQuotaDailyValid, | 33 | isUserVideoQuotaDailyValid, |
34 | isUserVideoQuotaValid, | 34 | isUserVideoQuotaValid, |
35 | isUserWebTorrentEnabledValid, | 35 | isUserVideosHistoryEnabledValid, |
36 | isUserVideosHistoryEnabledValid | 36 | isUserWebTorrentEnabledValid |
37 | } from '../../helpers/custom-validators/users' | 37 | } from '../../helpers/custom-validators/users' |
38 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' | 38 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' |
39 | import { OAuthTokenModel } from '../oauth/oauth-token' | 39 | import { OAuthTokenModel } from '../oauth/oauth-token' |
@@ -44,6 +44,10 @@ import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type' | |||
44 | import { values } from 'lodash' | 44 | import { values } from 'lodash' |
45 | import { NSFW_POLICY_TYPES } from '../../initializers' | 45 | import { NSFW_POLICY_TYPES } from '../../initializers' |
46 | import { clearCacheByUserId } from '../../lib/oauth-model' | 46 | import { clearCacheByUserId } from '../../lib/oauth-model' |
47 | import { UserNotificationSettingModel } from './user-notification-setting' | ||
48 | import { VideoModel } from '../video/video' | ||
49 | import { ActorModel } from '../activitypub/actor' | ||
50 | import { ActorFollowModel } from '../activitypub/actor-follow' | ||
47 | 51 | ||
48 | enum ScopeNames { | 52 | enum ScopeNames { |
49 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' | 53 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' |
@@ -54,6 +58,10 @@ enum ScopeNames { | |||
54 | { | 58 | { |
55 | model: () => AccountModel, | 59 | model: () => AccountModel, |
56 | required: true | 60 | required: true |
61 | }, | ||
62 | { | ||
63 | model: () => UserNotificationSettingModel, | ||
64 | required: true | ||
57 | } | 65 | } |
58 | ] | 66 | ] |
59 | }) | 67 | }) |
@@ -64,6 +72,10 @@ enum ScopeNames { | |||
64 | model: () => AccountModel, | 72 | model: () => AccountModel, |
65 | required: true, | 73 | required: true, |
66 | include: [ () => VideoChannelModel ] | 74 | include: [ () => VideoChannelModel ] |
75 | }, | ||
76 | { | ||
77 | model: () => UserNotificationSettingModel, | ||
78 | required: true | ||
67 | } | 79 | } |
68 | ] | 80 | ] |
69 | } | 81 | } |
@@ -167,6 +179,13 @@ export class UserModel extends Model<UserModel> { | |||
167 | }) | 179 | }) |
168 | Account: AccountModel | 180 | Account: AccountModel |
169 | 181 | ||
182 | @HasOne(() => UserNotificationSettingModel, { | ||
183 | foreignKey: 'userId', | ||
184 | onDelete: 'cascade', | ||
185 | hooks: true | ||
186 | }) | ||
187 | NotificationSetting: UserNotificationSettingModel | ||
188 | |||
170 | @HasMany(() => OAuthTokenModel, { | 189 | @HasMany(() => OAuthTokenModel, { |
171 | foreignKey: 'userId', | 190 | foreignKey: 'userId', |
172 | onDelete: 'cascade' | 191 | onDelete: 'cascade' |
@@ -249,13 +268,12 @@ export class UserModel extends Model<UserModel> { | |||
249 | }) | 268 | }) |
250 | } | 269 | } |
251 | 270 | ||
252 | static listEmailsWithRight (right: UserRight) { | 271 | static listWithRight (right: UserRight) { |
253 | const roles = Object.keys(USER_ROLE_LABELS) | 272 | const roles = Object.keys(USER_ROLE_LABELS) |
254 | .map(k => parseInt(k, 10) as UserRole) | 273 | .map(k => parseInt(k, 10) as UserRole) |
255 | .filter(role => hasUserRight(role, right)) | 274 | .filter(role => hasUserRight(role, right)) |
256 | 275 | ||
257 | const query = { | 276 | const query = { |
258 | attribute: [ 'email' ], | ||
259 | where: { | 277 | where: { |
260 | role: { | 278 | role: { |
261 | [Sequelize.Op.in]: roles | 279 | [Sequelize.Op.in]: roles |
@@ -263,9 +281,46 @@ export class UserModel extends Model<UserModel> { | |||
263 | } | 281 | } |
264 | } | 282 | } |
265 | 283 | ||
266 | return UserModel.unscoped() | 284 | return UserModel.findAll(query) |
267 | .findAll(query) | 285 | } |
268 | .then(u => u.map(u => u.email)) | 286 | |
287 | static listUserSubscribersOf (actorId: number) { | ||
288 | const query = { | ||
289 | include: [ | ||
290 | { | ||
291 | model: UserNotificationSettingModel.unscoped(), | ||
292 | required: true | ||
293 | }, | ||
294 | { | ||
295 | attributes: [ 'userId' ], | ||
296 | model: AccountModel.unscoped(), | ||
297 | required: true, | ||
298 | include: [ | ||
299 | { | ||
300 | attributes: [ ], | ||
301 | model: ActorModel.unscoped(), | ||
302 | required: true, | ||
303 | where: { | ||
304 | serverId: null | ||
305 | }, | ||
306 | include: [ | ||
307 | { | ||
308 | attributes: [ ], | ||
309 | as: 'ActorFollowings', | ||
310 | model: ActorFollowModel.unscoped(), | ||
311 | required: true, | ||
312 | where: { | ||
313 | targetActorId: actorId | ||
314 | } | ||
315 | } | ||
316 | ] | ||
317 | } | ||
318 | ] | ||
319 | } | ||
320 | ] | ||
321 | } | ||
322 | |||
323 | return UserModel.unscoped().findAll(query) | ||
269 | } | 324 | } |
270 | 325 | ||
271 | static loadById (id: number) { | 326 | static loadById (id: number) { |
@@ -314,6 +369,37 @@ export class UserModel extends Model<UserModel> { | |||
314 | return UserModel.findOne(query) | 369 | return UserModel.findOne(query) |
315 | } | 370 | } |
316 | 371 | ||
372 | static loadByVideoId (videoId: number) { | ||
373 | const query = { | ||
374 | include: [ | ||
375 | { | ||
376 | required: true, | ||
377 | attributes: [ 'id' ], | ||
378 | model: AccountModel.unscoped(), | ||
379 | include: [ | ||
380 | { | ||
381 | required: true, | ||
382 | attributes: [ 'id' ], | ||
383 | model: VideoChannelModel.unscoped(), | ||
384 | include: [ | ||
385 | { | ||
386 | required: true, | ||
387 | attributes: [ 'id' ], | ||
388 | model: VideoModel.unscoped(), | ||
389 | where: { | ||
390 | id: videoId | ||
391 | } | ||
392 | } | ||
393 | ] | ||
394 | } | ||
395 | ] | ||
396 | } | ||
397 | ] | ||
398 | } | ||
399 | |||
400 | return UserModel.findOne(query) | ||
401 | } | ||
402 | |||
317 | static getOriginalVideoFileTotalFromUser (user: UserModel) { | 403 | static getOriginalVideoFileTotalFromUser (user: UserModel) { |
318 | // Don't use sequelize because we need to use a sub query | 404 | // Don't use sequelize because we need to use a sub query |
319 | const query = UserModel.generateUserQuotaBaseSQL() | 405 | const query = UserModel.generateUserQuotaBaseSQL() |
@@ -380,6 +466,7 @@ export class UserModel extends Model<UserModel> { | |||
380 | blocked: this.blocked, | 466 | blocked: this.blocked, |
381 | blockedReason: this.blockedReason, | 467 | blockedReason: this.blockedReason, |
382 | account: this.Account.toFormattedJSON(), | 468 | account: this.Account.toFormattedJSON(), |
469 | notificationSettings: this.NotificationSetting ? this.NotificationSetting.toFormattedJSON() : undefined, | ||
383 | videoChannels: [], | 470 | videoChannels: [], |
384 | videoQuotaUsed: videoQuotaUsed !== undefined | 471 | videoQuotaUsed: videoQuotaUsed !== undefined |
385 | ? parseInt(videoQuotaUsed, 10) | 472 | ? parseInt(videoQuotaUsed, 10) |