diff options
author | kontrollanten <6680299+kontrollanten@users.noreply.github.com> | 2022-02-28 08:34:43 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-28 08:34:43 +0100 |
commit | d0800f7661f13fabe7bb6f4aa0ea50764f106405 (patch) | |
tree | d43e6b0b6f4a5a32e03487e6464edbcaf288be2a /server/models/user | |
parent | 5cad2ca9db9b9d138f8a33058d10b94a9fd50c69 (diff) | |
download | PeerTube-d0800f7661f13fabe7bb6f4aa0ea50764f106405.tar.gz PeerTube-d0800f7661f13fabe7bb6f4aa0ea50764f106405.tar.zst PeerTube-d0800f7661f13fabe7bb6f4aa0ea50764f106405.zip |
Implement avatar miniatures (#4639)
* client: remove unused file
* refactor(client/my-actor-avatar): size from input
Read size from component input instead of scss, to make it possible to
use smaller avatar images when implemented.
* implement avatar miniatures
close #4560
* fix(test): max file size
* fix(search-index): normalize res acc to avatarMini
* refactor avatars to an array
* client/search: resize channel avatar to 120
* refactor(client/videos): remove unused function
* client(actor-avatar): set default size
* fix tests and avatars full result
When findOne is used only an array containting one avatar is returned.
* update migration version and version notations
* server/search: harmonize normalizing
* Cleanup avatar miniature PR
Co-authored-by: Chocobozzz <me@florianbigard.com>
Diffstat (limited to 'server/models/user')
-rw-r--r-- | server/models/user/sql/user-notitication-list-query-builder.ts | 269 | ||||
-rw-r--r-- | server/models/user/user-notification.ts | 275 | ||||
-rw-r--r-- | server/models/user/user.ts | 13 |
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 @@ | |||
1 | import { QueryTypes, Sequelize } from 'sequelize' | ||
2 | import { ModelBuilder } from '@server/models/shared' | ||
3 | import { getSort } from '@server/models/utils' | ||
4 | import { UserNotificationModelForApi } from '@server/types/models' | ||
5 | import { ActorImageType } from '@shared/models' | ||
6 | |||
7 | export interface ListNotificationsOptions { | ||
8 | userId: number | ||
9 | unread?: boolean | ||
10 | sort: string | ||
11 | offset: number | ||
12 | limit: number | ||
13 | sequelize: Sequelize | ||
14 | } | ||
15 | |||
16 | export 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 @@ | |||
1 | import { FindOptions, ModelIndexesOptions, Op, WhereOptions } from 'sequelize' | 1 | import { ModelIndexesOptions, Op, WhereOptions } from 'sequelize' |
2 | import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' | 2 | import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
3 | import { getBiggestActorImage } from '@server/lib/actor-image' | ||
3 | import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' | 4 | import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/types/models/user' |
4 | import { uuidToShort } from '@shared/extra-utils' | 5 | import { uuidToShort } from '@shared/extra-utils' |
5 | import { UserNotification, UserNotificationType } from '@shared/models' | 6 | import { UserNotification, UserNotificationType } from '@shared/models' |
@@ -7,207 +8,18 @@ import { AttributesOnly } from '@shared/typescript-utils' | |||
7 | import { isBooleanValid } from '../../helpers/custom-validators/misc' | 8 | import { isBooleanValid } from '../../helpers/custom-validators/misc' |
8 | import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' | 9 | import { isUserNotificationTypeValid } from '../../helpers/custom-validators/user-notifications' |
9 | import { AbuseModel } from '../abuse/abuse' | 10 | import { AbuseModel } from '../abuse/abuse' |
10 | import { VideoAbuseModel } from '../abuse/video-abuse' | ||
11 | import { VideoCommentAbuseModel } from '../abuse/video-comment-abuse' | ||
12 | import { AccountModel } from '../account/account' | 11 | import { AccountModel } from '../account/account' |
13 | import { ActorModel } from '../actor/actor' | ||
14 | import { ActorFollowModel } from '../actor/actor-follow' | 12 | import { ActorFollowModel } from '../actor/actor-follow' |
15 | import { ActorImageModel } from '../actor/actor-image' | ||
16 | import { ApplicationModel } from '../application/application' | 13 | import { ApplicationModel } from '../application/application' |
17 | import { PluginModel } from '../server/plugin' | 14 | import { PluginModel } from '../server/plugin' |
18 | import { ServerModel } from '../server/server' | 15 | import { throwIfNotValid } from '../utils' |
19 | import { getSort, throwIfNotValid } from '../utils' | ||
20 | import { VideoModel } from '../video/video' | 16 | import { VideoModel } from '../video/video' |
21 | import { VideoBlacklistModel } from '../video/video-blacklist' | 17 | import { VideoBlacklistModel } from '../video/video-blacklist' |
22 | import { VideoChannelModel } from '../video/video-channel' | ||
23 | import { VideoCommentModel } from '../video/video-comment' | 18 | import { VideoCommentModel } from '../video/video-comment' |
24 | import { VideoImportModel } from '../video/video-import' | 19 | import { VideoImportModel } from '../video/video-import' |
20 | import { UserNotificationListQueryBuilder } from './sql/user-notitication-list-query-builder' | ||
25 | import { UserModel } from './user' | 21 | import { UserModel } from './user' |
26 | 22 | ||
27 | enum ScopeNames { | ||
28 | WITH_ALL = 'WITH_ALL' | ||
29 | } | ||
30 | |||
31 | function 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 | |||
52 | function buildVideoInclude (required: boolean) { | ||
53 | return { | ||
54 | attributes: [ 'id', 'uuid', 'name' ], | ||
55 | model: VideoModel.unscoped(), | ||
56 | required | ||
57 | } | ||
58 | } | ||
59 | |||
60 | function 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 | |||
69 | function 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[]> { |