aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/user
diff options
context:
space:
mode:
Diffstat (limited to 'server/models/user')
-rw-r--r--server/models/user/sql/user-notitication-list-query-builder.ts264
-rw-r--r--server/models/user/user-notification.ts272
-rw-r--r--server/models/user/user.ts13
3 files changed, 324 insertions, 225 deletions
diff --git a/server/models/user/sql/user-notitication-list-query-builder.ts b/server/models/user/sql/user-notitication-list-query-builder.ts
new file mode 100644
index 000000000..6a6a71e3a
--- /dev/null
+++ b/server/models/user/sql/user-notitication-list-query-builder.ts
@@ -0,0 +1,264 @@
1import { Sequelize } from 'sequelize'
2import { AbstractRunQuery, ModelBuilder } from '@server/models/shared'
3import { getSort } from '@server/models/utils'
4import { UserNotificationModelForApi } from '@server/types/models'
5import { ActorImageType } from '@shared/models'
6
7export interface ListNotificationsOptions {
8 userId: number
9 unread?: boolean
10 sort: string
11 offset: number
12 limit: number
13}
14
15export class UserNotificationListQueryBuilder extends AbstractRunQuery {
16 private innerQuery: string
17
18 constructor (
19 protected readonly sequelize: Sequelize,
20 private readonly options: ListNotificationsOptions
21 ) {
22 super(sequelize)
23 }
24
25 async listNotifications () {
26 this.buildQuery()
27
28 const results = await this.runQuery({ nest: true })
29 const modelBuilder = new ModelBuilder<UserNotificationModelForApi>(this.sequelize)
30
31 return modelBuilder.createModels(results, 'UserNotification')
32 }
33
34 private buildInnerQuery () {
35 this.innerQuery = `SELECT * FROM "userNotification" AS "UserNotificationModel" ` +
36 `${this.getWhere()} ` +
37 `${this.getOrder()} ` +
38 `LIMIT :limit OFFSET :offset `
39
40 this.replacements.limit = this.options.limit
41 this.replacements.offset = this.options.offset
42 }
43
44 private buildQuery () {
45 this.buildInnerQuery()
46
47 this.query = `
48 ${this.getSelect()}
49 FROM (${this.innerQuery}) "UserNotificationModel"
50 ${this.getJoins()}
51 ${this.getOrder()}`
52 }
53
54 private getWhere () {
55 let base = '"UserNotificationModel"."userId" = :userId '
56 this.replacements.userId = this.options.userId
57
58 if (this.options.unread === true) {
59 base += 'AND "UserNotificationModel"."read" IS FALSE '
60 } else if (this.options.unread === false) {
61 base += 'AND "UserNotificationModel"."read" IS TRUE '
62 }
63
64 return `WHERE ${base}`
65 }
66
67 private getOrder () {
68 const orders = getSort(this.options.sort)
69
70 return 'ORDER BY ' + orders.map(o => `"UserNotificationModel"."${o[0]}" ${o[1]}`).join(', ')
71 }
72
73 private getSelect () {
74 return `SELECT
75 "UserNotificationModel"."id",
76 "UserNotificationModel"."type",
77 "UserNotificationModel"."read",
78 "UserNotificationModel"."createdAt",
79 "UserNotificationModel"."updatedAt",
80 "Video"."id" AS "Video.id",
81 "Video"."uuid" AS "Video.uuid",
82 "Video"."name" AS "Video.name",
83 "Video->VideoChannel"."id" AS "Video.VideoChannel.id",
84 "Video->VideoChannel"."name" AS "Video.VideoChannel.name",
85 "Video->VideoChannel->Actor"."id" AS "Video.VideoChannel.Actor.id",
86 "Video->VideoChannel->Actor"."preferredUsername" AS "Video.VideoChannel.Actor.preferredUsername",
87 "Video->VideoChannel->Actor->Avatars"."id" AS "Video.VideoChannel.Actor.Avatars.id",
88 "Video->VideoChannel->Actor->Avatars"."width" AS "Video.VideoChannel.Actor.Avatars.width",
89 "Video->VideoChannel->Actor->Avatars"."filename" AS "Video.VideoChannel.Actor.Avatars.filename",
90 "Video->VideoChannel->Actor->Server"."id" AS "Video.VideoChannel.Actor.Server.id",
91 "Video->VideoChannel->Actor->Server"."host" AS "Video.VideoChannel.Actor.Server.host",
92 "VideoComment"."id" AS "VideoComment.id",
93 "VideoComment"."originCommentId" AS "VideoComment.originCommentId",
94 "VideoComment->Account"."id" AS "VideoComment.Account.id",
95 "VideoComment->Account"."name" AS "VideoComment.Account.name",
96 "VideoComment->Account->Actor"."id" AS "VideoComment.Account.Actor.id",
97 "VideoComment->Account->Actor"."preferredUsername" AS "VideoComment.Account.Actor.preferredUsername",
98 "VideoComment->Account->Actor->Avatars"."id" AS "VideoComment.Account.Actor.Avatars.id",
99 "VideoComment->Account->Actor->Avatars"."width" AS "VideoComment.Account.Actor.Avatars.width",
100 "VideoComment->Account->Actor->Avatars"."filename" AS "VideoComment.Account.Actor.Avatars.filename",
101 "VideoComment->Account->Actor->Server"."id" AS "VideoComment.Account.Actor.Server.id",
102 "VideoComment->Account->Actor->Server"."host" AS "VideoComment.Account.Actor.Server.host",
103 "VideoComment->Video"."id" AS "VideoComment.Video.id",
104 "VideoComment->Video"."uuid" AS "VideoComment.Video.uuid",
105 "VideoComment->Video"."name" AS "VideoComment.Video.name",
106 "Abuse"."id" AS "Abuse.id",
107 "Abuse"."state" AS "Abuse.state",
108 "Abuse->VideoAbuse"."id" AS "Abuse.VideoAbuse.id",
109 "Abuse->VideoAbuse->Video"."id" AS "Abuse.VideoAbuse.Video.id",
110 "Abuse->VideoAbuse->Video"."uuid" AS "Abuse.VideoAbuse.Video.uuid",
111 "Abuse->VideoAbuse->Video"."name" AS "Abuse.VideoAbuse.Video.name",
112 "Abuse->VideoCommentAbuse"."id" AS "Abuse.VideoCommentAbuse.id",
113 "Abuse->VideoCommentAbuse->VideoComment"."id" AS "Abuse.VideoCommentAbuse.VideoComment.id",
114 "Abuse->VideoCommentAbuse->VideoComment"."originCommentId" AS "Abuse.VideoCommentAbuse.VideoComment.originCommentId",
115 "Abuse->VideoCommentAbuse->VideoComment->Video"."id" AS "Abuse.VideoCommentAbuse.VideoComment.Video.id",
116 "Abuse->VideoCommentAbuse->VideoComment->Video"."name" AS "Abuse.VideoCommentAbuse.VideoComment.Video.name",
117 "Abuse->VideoCommentAbuse->VideoComment->Video"."uuid" AS "Abuse.VideoCommentAbuse.VideoComment.Video.uuid",
118 "Abuse->FlaggedAccount"."id" AS "Abuse.FlaggedAccount.id",
119 "Abuse->FlaggedAccount"."name" AS "Abuse.FlaggedAccount.name",
120 "Abuse->FlaggedAccount"."description" AS "Abuse.FlaggedAccount.description",
121 "Abuse->FlaggedAccount"."actorId" AS "Abuse.FlaggedAccount.actorId",
122 "Abuse->FlaggedAccount"."userId" AS "Abuse.FlaggedAccount.userId",
123 "Abuse->FlaggedAccount"."applicationId" AS "Abuse.FlaggedAccount.applicationId",
124 "Abuse->FlaggedAccount"."createdAt" AS "Abuse.FlaggedAccount.createdAt",
125 "Abuse->FlaggedAccount"."updatedAt" AS "Abuse.FlaggedAccount.updatedAt",
126 "Abuse->FlaggedAccount->Actor"."id" AS "Abuse.FlaggedAccount.Actor.id",
127 "Abuse->FlaggedAccount->Actor"."preferredUsername" AS "Abuse.FlaggedAccount.Actor.preferredUsername",
128 "Abuse->FlaggedAccount->Actor->Avatars"."id" AS "Abuse.FlaggedAccount.Actor.Avatars.id",
129 "Abuse->FlaggedAccount->Actor->Avatars"."width" AS "Abuse.FlaggedAccount.Actor.Avatars.width",
130 "Abuse->FlaggedAccount->Actor->Avatars"."filename" AS "Abuse.FlaggedAccount.Actor.Avatars.filename",
131 "Abuse->FlaggedAccount->Actor->Server"."id" AS "Abuse.FlaggedAccount.Actor.Server.id",
132 "Abuse->FlaggedAccount->Actor->Server"."host" AS "Abuse.FlaggedAccount.Actor.Server.host",
133 "VideoBlacklist"."id" AS "VideoBlacklist.id",
134 "VideoBlacklist->Video"."id" AS "VideoBlacklist.Video.id",
135 "VideoBlacklist->Video"."uuid" AS "VideoBlacklist.Video.uuid",
136 "VideoBlacklist->Video"."name" AS "VideoBlacklist.Video.name",
137 "VideoImport"."id" AS "VideoImport.id",
138 "VideoImport"."magnetUri" AS "VideoImport.magnetUri",
139 "VideoImport"."targetUrl" AS "VideoImport.targetUrl",
140 "VideoImport"."torrentName" AS "VideoImport.torrentName",
141 "VideoImport->Video"."id" AS "VideoImport.Video.id",
142 "VideoImport->Video"."uuid" AS "VideoImport.Video.uuid",
143 "VideoImport->Video"."name" AS "VideoImport.Video.name",
144 "Plugin"."id" AS "Plugin.id",
145 "Plugin"."name" AS "Plugin.name",
146 "Plugin"."type" AS "Plugin.type",
147 "Plugin"."latestVersion" AS "Plugin.latestVersion",
148 "Application"."id" AS "Application.id",
149 "Application"."latestPeerTubeVersion" AS "Application.latestPeerTubeVersion",
150 "ActorFollow"."id" AS "ActorFollow.id",
151 "ActorFollow"."state" AS "ActorFollow.state",
152 "ActorFollow->ActorFollower"."id" AS "ActorFollow.ActorFollower.id",
153 "ActorFollow->ActorFollower"."preferredUsername" AS "ActorFollow.ActorFollower.preferredUsername",
154 "ActorFollow->ActorFollower->Account"."id" AS "ActorFollow.ActorFollower.Account.id",
155 "ActorFollow->ActorFollower->Account"."name" AS "ActorFollow.ActorFollower.Account.name",
156 "ActorFollow->ActorFollower->Avatars"."id" AS "ActorFollow.ActorFollower.Avatars.id",
157 "ActorFollow->ActorFollower->Avatars"."width" AS "ActorFollow.ActorFollower.Avatars.width",
158 "ActorFollow->ActorFollower->Avatars"."filename" AS "ActorFollow.ActorFollower.Avatars.filename",
159 "ActorFollow->ActorFollower->Server"."id" AS "ActorFollow.ActorFollower.Server.id",
160 "ActorFollow->ActorFollower->Server"."host" AS "ActorFollow.ActorFollower.Server.host",
161 "ActorFollow->ActorFollowing"."id" AS "ActorFollow.ActorFollowing.id",
162 "ActorFollow->ActorFollowing"."preferredUsername" AS "ActorFollow.ActorFollowing.preferredUsername",
163 "ActorFollow->ActorFollowing"."type" AS "ActorFollow.ActorFollowing.type",
164 "ActorFollow->ActorFollowing->VideoChannel"."id" AS "ActorFollow.ActorFollowing.VideoChannel.id",
165 "ActorFollow->ActorFollowing->VideoChannel"."name" AS "ActorFollow.ActorFollowing.VideoChannel.name",
166 "ActorFollow->ActorFollowing->Account"."id" AS "ActorFollow.ActorFollowing.Account.id",
167 "ActorFollow->ActorFollowing->Account"."name" AS "ActorFollow.ActorFollowing.Account.name",
168 "ActorFollow->ActorFollowing->Server"."id" AS "ActorFollow.ActorFollowing.Server.id",
169 "ActorFollow->ActorFollowing->Server"."host" AS "ActorFollow.ActorFollowing.Server.host",
170 "Account"."id" AS "Account.id",
171 "Account"."name" AS "Account.name",
172 "Account->Actor"."id" AS "Account.Actor.id",
173 "Account->Actor"."preferredUsername" AS "Account.Actor.preferredUsername",
174 "Account->Actor->Avatars"."id" AS "Account.Actor.Avatars.id",
175 "Account->Actor->Avatars"."width" AS "Account.Actor.Avatars.width",
176 "Account->Actor->Avatars"."filename" AS "Account.Actor.Avatars.filename",
177 "Account->Actor->Server"."id" AS "Account.Actor.Server.id",
178 "Account->Actor->Server"."host" AS "Account.Actor.Server.host"`
179 }
180
181 private getJoins () {
182 return `
183 LEFT JOIN (
184 "video" AS "Video"
185 INNER JOIN "videoChannel" AS "Video->VideoChannel" ON "Video"."channelId" = "Video->VideoChannel"."id"
186 INNER JOIN "actor" AS "Video->VideoChannel->Actor" ON "Video->VideoChannel"."actorId" = "Video->VideoChannel->Actor"."id"
187 LEFT JOIN "actorImage" AS "Video->VideoChannel->Actor->Avatars"
188 ON "Video->VideoChannel->Actor"."id" = "Video->VideoChannel->Actor->Avatars"."actorId"
189 AND "Video->VideoChannel->Actor->Avatars"."type" = ${ActorImageType.AVATAR}
190 LEFT JOIN "server" AS "Video->VideoChannel->Actor->Server"
191 ON "Video->VideoChannel->Actor"."serverId" = "Video->VideoChannel->Actor->Server"."id"
192 ) ON "UserNotificationModel"."videoId" = "Video"."id"
193
194 LEFT JOIN (
195 "videoComment" AS "VideoComment"
196 INNER JOIN "account" AS "VideoComment->Account" ON "VideoComment"."accountId" = "VideoComment->Account"."id"
197 INNER JOIN "actor" AS "VideoComment->Account->Actor" ON "VideoComment->Account"."actorId" = "VideoComment->Account->Actor"."id"
198 LEFT JOIN "actorImage" AS "VideoComment->Account->Actor->Avatars"
199 ON "VideoComment->Account->Actor"."id" = "VideoComment->Account->Actor->Avatars"."actorId"
200 AND "VideoComment->Account->Actor->Avatars"."type" = ${ActorImageType.AVATAR}
201 LEFT JOIN "server" AS "VideoComment->Account->Actor->Server"
202 ON "VideoComment->Account->Actor"."serverId" = "VideoComment->Account->Actor->Server"."id"
203 INNER JOIN "video" AS "VideoComment->Video" ON "VideoComment"."videoId" = "VideoComment->Video"."id"
204 ) ON "UserNotificationModel"."commentId" = "VideoComment"."id"
205
206 LEFT JOIN "abuse" AS "Abuse" ON "UserNotificationModel"."abuseId" = "Abuse"."id"
207 LEFT JOIN "videoAbuse" AS "Abuse->VideoAbuse" ON "Abuse"."id" = "Abuse->VideoAbuse"."abuseId"
208 LEFT JOIN "video" AS "Abuse->VideoAbuse->Video" ON "Abuse->VideoAbuse"."videoId" = "Abuse->VideoAbuse->Video"."id"
209 LEFT JOIN "commentAbuse" AS "Abuse->VideoCommentAbuse" ON "Abuse"."id" = "Abuse->VideoCommentAbuse"."abuseId"
210 LEFT JOIN "videoComment" AS "Abuse->VideoCommentAbuse->VideoComment"
211 ON "Abuse->VideoCommentAbuse"."videoCommentId" = "Abuse->VideoCommentAbuse->VideoComment"."id"
212 LEFT JOIN "video" AS "Abuse->VideoCommentAbuse->VideoComment->Video"
213 ON "Abuse->VideoCommentAbuse->VideoComment"."videoId" = "Abuse->VideoCommentAbuse->VideoComment->Video"."id"
214 LEFT JOIN (
215 "account" AS "Abuse->FlaggedAccount"
216 INNER JOIN "actor" AS "Abuse->FlaggedAccount->Actor" ON "Abuse->FlaggedAccount"."actorId" = "Abuse->FlaggedAccount->Actor"."id"
217 LEFT JOIN "actorImage" AS "Abuse->FlaggedAccount->Actor->Avatars"
218 ON "Abuse->FlaggedAccount->Actor"."id" = "Abuse->FlaggedAccount->Actor->Avatars"."actorId"
219 AND "Abuse->FlaggedAccount->Actor->Avatars"."type" = ${ActorImageType.AVATAR}
220 LEFT JOIN "server" AS "Abuse->FlaggedAccount->Actor->Server"
221 ON "Abuse->FlaggedAccount->Actor"."serverId" = "Abuse->FlaggedAccount->Actor->Server"."id"
222 ) ON "Abuse"."flaggedAccountId" = "Abuse->FlaggedAccount"."id"
223
224 LEFT JOIN (
225 "videoBlacklist" AS "VideoBlacklist"
226 INNER JOIN "video" AS "VideoBlacklist->Video" ON "VideoBlacklist"."videoId" = "VideoBlacklist->Video"."id"
227 ) ON "UserNotificationModel"."videoBlacklistId" = "VideoBlacklist"."id"
228
229 LEFT JOIN "videoImport" AS "VideoImport" ON "UserNotificationModel"."videoImportId" = "VideoImport"."id"
230 LEFT JOIN "video" AS "VideoImport->Video" ON "VideoImport"."videoId" = "VideoImport->Video"."id"
231
232 LEFT JOIN "plugin" AS "Plugin" ON "UserNotificationModel"."pluginId" = "Plugin"."id"
233
234 LEFT JOIN "application" AS "Application" ON "UserNotificationModel"."applicationId" = "Application"."id"
235
236 LEFT JOIN (
237 "actorFollow" AS "ActorFollow"
238 INNER JOIN "actor" AS "ActorFollow->ActorFollower" ON "ActorFollow"."actorId" = "ActorFollow->ActorFollower"."id"
239 INNER JOIN "account" AS "ActorFollow->ActorFollower->Account"
240 ON "ActorFollow->ActorFollower"."id" = "ActorFollow->ActorFollower->Account"."actorId"
241 LEFT JOIN "actorImage" AS "ActorFollow->ActorFollower->Avatars"
242 ON "ActorFollow->ActorFollower"."id" = "ActorFollow->ActorFollower->Avatars"."actorId"
243 AND "ActorFollow->ActorFollower->Avatars"."type" = ${ActorImageType.AVATAR}
244 LEFT JOIN "server" AS "ActorFollow->ActorFollower->Server"
245 ON "ActorFollow->ActorFollower"."serverId" = "ActorFollow->ActorFollower->Server"."id"
246 INNER JOIN "actor" AS "ActorFollow->ActorFollowing" ON "ActorFollow"."targetActorId" = "ActorFollow->ActorFollowing"."id"
247 LEFT JOIN "videoChannel" AS "ActorFollow->ActorFollowing->VideoChannel"
248 ON "ActorFollow->ActorFollowing"."id" = "ActorFollow->ActorFollowing->VideoChannel"."actorId"
249 LEFT JOIN "account" AS "ActorFollow->ActorFollowing->Account"
250 ON "ActorFollow->ActorFollowing"."id" = "ActorFollow->ActorFollowing->Account"."actorId"
251 LEFT JOIN "server" AS "ActorFollow->ActorFollowing->Server"
252 ON "ActorFollow->ActorFollowing"."serverId" = "ActorFollow->ActorFollowing->Server"."id"
253 ) ON "UserNotificationModel"."actorFollowId" = "ActorFollow"."id"
254
255 LEFT JOIN (
256 "account" AS "Account"
257 INNER JOIN "actor" AS "Account->Actor" ON "Account"."actorId" = "Account->Actor"."id"
258 LEFT JOIN "actorImage" AS "Account->Actor->Avatars"
259 ON "Account->Actor"."id" = "Account->Actor->Avatars"."actorId"
260 AND "Account->Actor->Avatars"."type" = ${ActorImageType.AVATAR}
261 LEFT JOIN "server" AS "Account->Actor->Server" ON "Account->Actor"."serverId" = "Account->Actor->Server"."id"
262 ) ON "UserNotificationModel"."accountId" = "Account"."id"`
263 }
264}
diff --git a/server/models/user/user-notification.ts b/server/models/user/user-notification.ts
index edad10a55..6209cb4bf 100644
--- a/server/models/user/user-notification.ts
+++ b/server/models/user/user-notification.ts
@@ -1,5 +1,6 @@
1import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' 1import { ModelIndexesOptions, Op, WhereOptions } from 'sequelize'
2import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' 2import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript'
3import { getBiggestActorImage } from '@server/lib/actor-image'
3import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' 4import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user'
4import { uuidToShort } from '@shared/extra-utils' 5import { uuidToShort } from '@shared/extra-utils'
5import { UserNotification, UserNotificationType } from '@shared/models' 6import { UserNotification, UserNotificationType } from '@shared/models'
@@ -7,207 +8,18 @@ import { AttributesOnly } from '@shared/typescript-utils'
7import { isBooleanValid } from '../../helpers/custom-validators/misc' 8import { isBooleanValid } from '../../helpers/custom-validators/misc'
8import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' 9import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications'
9import { AbuseModel } from '../abuse/abuse' 10import { AbuseModel } from '../abuse/abuse'
10import { VideoAbuseModel } from '../abuse/video-abuse'
11import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse'
12import { AccountModel } from '../account/account' 11import { AccountModel } from '../account/account'
13import { ActorModel } from '../actor/actor'
14import { ActorFollowModel } from '../actor/actor-follow' 12import { ActorFollowModel } from '../actor/actor-follow'
15import { ActorImageModel } from '../actor/actor-image'
16import { ApplicationModel } from '../application/application' 13import { ApplicationModel } from '../application/application'
17import { PluginModel } from '../server/plugin' 14import { PluginModel } from '../server/plugin'
18import { ServerModel } from '../server/server' 15import { throwIfNotValid } from '../utils'
19import { getSort, throwIfNotValid } from '../utils'
20import { VideoModel } from '../video/video' 16import { VideoModel } from '../video/video'
21import { VideoBlacklistModel } from '../video/video-blacklist' 17import { VideoBlacklistModel } from '../video/video-blacklist'
22import { VideoChannelModel } from '../video/video-channel'
23import { VideoCommentModel } from '../video/video-comment' 18import { VideoCommentModel } from '../video/video-comment'
24import { VideoImportModel } from '../video/video-import' 19import { VideoImportModel } from '../video/video-import'
20import { UserNotificationListQueryBuilder } from './sql/user-notitication-list-query-builder'
25import { UserModel } from './user' 21import { UserModel } from './user'
26 22
27enum ScopeNames {
28 WITH_ALL = 'WITH_ALL'
29}
30
31function buildActorWithAvatarInclude () {
32 return {
33 attributes: [ 'preferredUsername' ],
34 model: ActorModel.unscoped(),
35 required: true,
36 include: [
37 {
38 attributes: [ 'filename' ],
39 as: 'Avatar',
40 model: ActorImageModel.unscoped(),
41 required: false
42 },
43 {
44 attributes: [ 'host' ],
45 model: ServerModel.unscoped(),
46 required: false
47 }
48 ]
49 }
50}
51
52function buildVideoInclude (required: boolean) {
53 return {
54 attributes: [ 'id', 'uuid', 'name' ],
55 model: VideoModel.unscoped(),
56 required
57 }
58}
59
60function buildChannelInclude (required: boolean, withActor = false) {
61 return {
62 required,
63 attributes: [ 'id', 'name' ],
64 model: VideoChannelModel.unscoped(),
65 include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
66 }
67}
68
69function buildAccountInclude (required: boolean, withActor = false) {
70 return {
71 required,
72 attributes: [ 'id', 'name' ],
73 model: AccountModel.unscoped(),
74 include: withActor === true ? [ buildActorWithAvatarInclude() ] : []
75 }
76}
77
78@Scopes(() => ({
79 [ScopeNames.WITH_ALL]: {
80 include: [
81 Object.assign(buildVideoInclude(false), {
82 include: [ buildChannelInclude(true, true) ]
83 }),
84
85 {
86 attributes: [ 'id', 'originCommentId' ],
87 model: VideoCommentModel.unscoped(),
88 required: false,
89 include: [
90 buildAccountInclude(true, true),
91 buildVideoInclude(true)
92 ]
93 },
94
95 {
96 attributes: [ 'id', 'state' ],
97 model: AbuseModel.unscoped(),
98 required: false,
99 include: [
100 {
101 attributes: [ 'id' ],
102 model: VideoAbuseModel.unscoped(),
103 required: false,
104 include: [ buildVideoInclude(false) ]
105 },
106 {
107 attributes: [ 'id' ],
108 model: VideoCommentAbuseModel.unscoped(),
109 required: false,
110 include: [
111 {
112 attributes: [ 'id', 'originCommentId' ],
113 model: VideoCommentModel.unscoped(),
114 required: false,
115 include: [
116 {
117 attributes: [ 'id', 'name', 'uuid' ],
118 model: VideoModel.unscoped(),
119 required: false
120 }
121 ]
122 }
123 ]
124 },
125 {
126 model: AccountModel,
127 as: 'FlaggedAccount',
128 required: false,
129 include: [ buildActorWithAvatarInclude() ]
130 }
131 ]
132 },
133
134 {
135 attributes: [ 'id' ],
136 model: VideoBlacklistModel.unscoped(),
137 required: false,
138 include: [ buildVideoInclude(true) ]
139 },
140
141 {
142 attributes: [ 'id', 'magnetUri', 'targetUrl', 'torrentName' ],
143 model: VideoImportModel.unscoped(),
144 required: false,
145 include: [ buildVideoInclude(false) ]
146 },
147
148 {
149 attributes: [ 'id', 'name', 'type', 'latestVersion' ],
150 model: PluginModel.unscoped(),
151 required: false
152 },
153
154 {
155 attributes: [ 'id', 'latestPeerTubeVersion' ],
156 model: ApplicationModel.unscoped(),
157 required: false
158 },
159
160 {
161 attributes: [ 'id', 'state' ],
162 model: ActorFollowModel.unscoped(),
163 required: false,
164 include: [
165 {
166 attributes: [ 'preferredUsername' ],
167 model: ActorModel.unscoped(),
168 required: true,
169 as: 'ActorFollower',
170 include: [
171 {
172 attributes: [ 'id', 'name' ],
173 model: AccountModel.unscoped(),
174 required: true
175 },
176 {
177 attributes: [ 'filename' ],
178 as: 'Avatar',
179 model: ActorImageModel.unscoped(),
180 required: false
181 },
182 {
183 attributes: [ 'host' ],
184 model: ServerModel.unscoped(),
185 required: false
186 }
187 ]
188 },
189 {
190 attributes: [ 'preferredUsername', 'type' ],
191 model: ActorModel.unscoped(),
192 required: true,
193 as: 'ActorFollowing',
194 include: [
195 buildChannelInclude(false),
196 buildAccountInclude(false),
197 {
198 attributes: [ 'host' ],
199 model: ServerModel.unscoped(),
200 required: false
201 }
202 ]
203 }
204 ]
205 },
206
207 buildAccountInclude(false, true)
208 ]
209 }
210}))
211@Table({ 23@Table({
212 tableName: 'userNotification', 24 tableName: 'userNotification',
213 indexes: [ 25 indexes: [
@@ -342,7 +154,7 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
342 }, 154 },
343 onDelete: 'cascade' 155 onDelete: 'cascade'
344 }) 156 })
345 Comment: VideoCommentModel 157 VideoComment: VideoCommentModel
346 158
347 @ForeignKey(() => AbuseModel) 159 @ForeignKey(() => AbuseModel)
348 @Column 160 @Column
@@ -431,10 +243,12 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
431 static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) { 243 static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) {
432 const where = { userId } 244 const where = { userId }
433 245
434 const query: FindOptions = { 246 const query = {
247 userId,
248 unread,
435 offset: start, 249 offset: start,
436 limit: count, 250 limit: count,
437 order: getSort(sort), 251 sort,
438 where 252 where
439 } 253 }
440 254
@@ -445,8 +259,8 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
445 .then(count => count || 0), 259 .then(count => count || 0),
446 260
447 count === 0 261 count === 0
448 ? [] 262 ? [] as UserNotificationModelForApi[]
449 : UserNotificationModel.scope(ScopeNames.WITH_ALL).findAll(query) 263 : new UserNotificationListQueryBuilder(this.sequelize, query).listNotifications()
450 ]).then(([ total, data ]) => ({ total, data })) 264 ]).then(([ total, data ]) => ({ total, data }))
451 } 265 }
452 266
@@ -524,25 +338,31 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
524 338
525 toFormattedJSON (this: UserNotificationModelForApi): UserNotification { 339 toFormattedJSON (this: UserNotificationModelForApi): UserNotification {
526 const video = this.Video 340 const video = this.Video
527 ? Object.assign(this.formatVideo(this.Video), { channel: this.formatActor(this.Video.VideoChannel) }) 341 ? {
342 ...this.formatVideo(this.Video),
343
344 channel: this.formatActor(this.Video.VideoChannel)
345 }
528 : undefined 346 : undefined
529 347
530 const videoImport = this.VideoImport 348 const videoImport = this.VideoImport
531 ? { 349 ? {
532 id: this.VideoImport.id, 350 id: this.VideoImport.id,
533 video: this.VideoImport.Video ? this.formatVideo(this.VideoImport.Video) : undefined, 351 video: this.VideoImport.Video
352 ? this.formatVideo(this.VideoImport.Video)
353 : undefined,
534 torrentName: this.VideoImport.torrentName, 354 torrentName: this.VideoImport.torrentName,
535 magnetUri: this.VideoImport.magnetUri, 355 magnetUri: this.VideoImport.magnetUri,
536 targetUrl: this.VideoImport.targetUrl 356 targetUrl: this.VideoImport.targetUrl
537 } 357 }
538 : undefined 358 : undefined
539 359
540 const comment = this.Comment 360 const comment = this.VideoComment
541 ? { 361 ? {
542 id: this.Comment.id, 362 id: this.VideoComment.id,
543 threadId: this.Comment.getThreadId(), 363 threadId: this.VideoComment.getThreadId(),
544 account: this.formatActor(this.Comment.Account), 364 account: this.formatActor(this.VideoComment.Account),
545 video: this.formatVideo(this.Comment.Video) 365 video: this.formatVideo(this.VideoComment.Video)
546 } 366 }
547 : undefined 367 : undefined
548 368
@@ -570,8 +390,9 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
570 id: this.ActorFollow.ActorFollower.Account.id, 390 id: this.ActorFollow.ActorFollower.Account.id,
571 displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(), 391 displayName: this.ActorFollow.ActorFollower.Account.getDisplayName(),
572 name: this.ActorFollow.ActorFollower.preferredUsername, 392 name: this.ActorFollow.ActorFollower.preferredUsername,
573 avatar: this.ActorFollow.ActorFollower.Avatar ? { path: this.ActorFollow.ActorFollower.Avatar.getStaticPath() } : undefined, 393 host: this.ActorFollow.ActorFollower.getHost(),
574 host: this.ActorFollow.ActorFollower.getHost() 394
395 ...this.formatAvatars(this.ActorFollow.ActorFollower.Avatars)
575 }, 396 },
576 following: { 397 following: {
577 type: actorFollowingType[this.ActorFollow.ActorFollowing.type], 398 type: actorFollowingType[this.ActorFollow.ActorFollowing.type],
@@ -612,7 +433,7 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
612 } 433 }
613 } 434 }
614 435
615 formatVideo (this: UserNotificationModelForApi, video: UserNotificationIncludes.VideoInclude) { 436 formatVideo (video: UserNotificationIncludes.VideoInclude) {
616 return { 437 return {
617 id: video.id, 438 id: video.id,
618 uuid: video.uuid, 439 uuid: video.uuid,
@@ -621,7 +442,7 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
621 } 442 }
622 } 443 }
623 444
624 formatAbuse (this: UserNotificationModelForApi, abuse: UserNotificationIncludes.AbuseInclude) { 445 formatAbuse (abuse: UserNotificationIncludes.AbuseInclude) {
625 const commentAbuse = abuse.VideoCommentAbuse?.VideoComment 446 const commentAbuse = abuse.VideoCommentAbuse?.VideoComment
626 ? { 447 ? {
627 threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(), 448 threadId: abuse.VideoCommentAbuse.VideoComment.getThreadId(),
@@ -637,9 +458,13 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
637 } 458 }
638 : undefined 459 : undefined
639 460
640 const videoAbuse = abuse.VideoAbuse?.Video ? this.formatVideo(abuse.VideoAbuse.Video) : undefined 461 const videoAbuse = abuse.VideoAbuse?.Video
462 ? this.formatVideo(abuse.VideoAbuse.Video)
463 : undefined
641 464
642 const accountAbuse = (!commentAbuse && !videoAbuse && abuse.FlaggedAccount) ? this.formatActor(abuse.FlaggedAccount) : undefined 465 const accountAbuse = (!commentAbuse && !videoAbuse && abuse.FlaggedAccount)
466 ? this.formatActor(abuse.FlaggedAccount)
467 : undefined
643 468
644 return { 469 return {
645 id: abuse.id, 470 id: abuse.id,
@@ -651,19 +476,32 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti
651 } 476 }
652 477
653 formatActor ( 478 formatActor (
654 this: UserNotificationModelForApi,
655 accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor 479 accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor
656 ) { 480 ) {
657 const avatar = accountOrChannel.Actor.Avatar
658 ? { path: accountOrChannel.Actor.Avatar.getStaticPath() }
659 : undefined
660
661 return { 481 return {
662 id: accountOrChannel.id, 482 id: accountOrChannel.id,
663 displayName: accountOrChannel.getDisplayName(), 483 displayName: accountOrChannel.getDisplayName(),
664 name: accountOrChannel.Actor.preferredUsername, 484 name: accountOrChannel.Actor.preferredUsername,
665 host: accountOrChannel.Actor.getHost(), 485 host: accountOrChannel.Actor.getHost(),
666 avatar 486
487 ...this.formatAvatars(accountOrChannel.Actor.Avatars)
488 }
489 }
490
491 formatAvatars (avatars: UserNotificationIncludes.ActorImageInclude[]) {
492 if (!avatars || avatars.length === 0) return { avatar: undefined, avatars: [] }
493
494 return {
495 avatar: this.formatAvatar(getBiggestActorImage(avatars)),
496
497 avatars: avatars.map(a => this.formatAvatar(a))
498 }
499 }
500
501 formatAvatar (a: UserNotificationIncludes.ActorImageInclude) {
502 return {
503 path: a.getStaticPath(),
504 width: a.width
667 } 505 }
668 } 506 }
669} 507}
diff --git a/server/models/user/user.ts b/server/models/user/user.ts
index ad8ce08cb..bcf56dfa1 100644
--- a/server/models/user/user.ts
+++ b/server/models/user/user.ts
@@ -106,7 +106,7 @@ enum ScopeNames {
106 include: [ 106 include: [
107 { 107 {
108 model: ActorImageModel, 108 model: ActorImageModel,
109 as: 'Banner', 109 as: 'Banners',
110 required: false 110 required: false
111 } 111 }
112 ] 112 ]
@@ -495,13 +495,10 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> {
495 where 495 where
496 } 496 }
497 497
498 return UserModel.findAndCountAll(query) 498 return Promise.all([
499 .then(({ rows, count }) => { 499 UserModel.unscoped().count(query),
500 return { 500 UserModel.findAll(query)
501 data: rows, 501 ]).then(([ total, data ]) => ({ total, data }))
502 total: count
503 }
504 })
505 } 502 }
506 503
507 static listWithRight (right: UserRight): Promise<MUserDefault[]> { 504 static listWithRight (right: UserRight): Promise<MUserDefault[]> {