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