aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/account
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-12-26 10:36:24 +0100
committerChocobozzz <chocobozzz@cpy.re>2019-01-09 11:15:15 +0100
commitcef534ed53e4518fe0acf581bfe880788d42fc36 (patch)
tree115b51ea5136849a2336d44915c7780649f25dc2 /server/models/account
parent1de1d05f4c61fe059fa5e24e79c92582f0e7e4b3 (diff)
downloadPeerTube-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.ts100
-rw-r--r--server/models/account/user-notification.ts256
-rw-r--r--server/models/account/user.ts101
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 @@
1import {
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'
15import { throwIfNotValid } from '../utils'
16import { UserModel } from './user'
17import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications'
18import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model'
19import { clearCacheByUserId } from '../../lib/oauth-model'
20
21@Table({
22 tableName: 'userNotificationSetting',
23 indexes: [
24 {
25 fields: [ 'userId' ],
26 unique: true
27 }
28 ]
29})
30export 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 @@
1import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript'
2import { UserNotification, UserNotificationType } from '../../../shared'
3import { getSort, throwIfNotValid } from '../utils'
4import { isBooleanValid } from '../../helpers/custom-validators/misc'
5import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications'
6import { UserModel } from './user'
7import { VideoModel } from '../video/video'
8import { VideoCommentModel } from '../video/video-comment'
9import { Op } from 'sequelize'
10import { VideoChannelModel } from '../video/video-channel'
11import { AccountModel } from './account'
12import { VideoAbuseModel } from '../video/video-abuse'
13import { VideoBlacklistModel } from '../video/video-blacklist'
14
15enum 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})
89export 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'
38import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' 38import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto'
39import { OAuthTokenModel } from '../oauth/oauth-token' 39import { OAuthTokenModel } from '../oauth/oauth-token'
@@ -44,6 +44,10 @@ import { NSFWPolicyType } from '../../../shared/models/videos/nsfw-policy.type'
44import { values } from 'lodash' 44import { values } from 'lodash'
45import { NSFW_POLICY_TYPES } from '../../initializers' 45import { NSFW_POLICY_TYPES } from '../../initializers'
46import { clearCacheByUserId } from '../../lib/oauth-model' 46import { clearCacheByUserId } from '../../lib/oauth-model'
47import { UserNotificationSettingModel } from './user-notification-setting'
48import { VideoModel } from '../video/video'
49import { ActorModel } from '../activitypub/actor'
50import { ActorFollowModel } from '../activitypub/actor-follow'
47 51
48enum ScopeNames { 52enum 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)