diff options
author | Chocobozzz <me@florianbigard.com> | 2023-01-19 09:27:16 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2023-01-19 13:53:40 +0100 |
commit | e364e31e25bd1d4b8d801c845a96d6be708f0a18 (patch) | |
tree | 220785a42af361706eb8243960c5da9cddf4d2be /server/models/user | |
parent | bc48e33b80f357767b98c1d310b04bdda24c6d46 (diff) | |
download | PeerTube-e364e31e25bd1d4b8d801c845a96d6be708f0a18.tar.gz PeerTube-e364e31e25bd1d4b8d801c845a96d6be708f0a18.tar.zst PeerTube-e364e31e25bd1d4b8d801c845a96d6be708f0a18.zip |
Implement signup approval in server
Diffstat (limited to 'server/models/user')
-rw-r--r-- | server/models/user/sql/user-notitication-list-query-builder.ts | 130 | ||||
-rw-r--r-- | server/models/user/user-notification.ts | 26 | ||||
-rw-r--r-- | server/models/user/user-registration.ts | 259 | ||||
-rw-r--r-- | server/models/user/user.ts | 17 |
4 files changed, 361 insertions, 71 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 index d11546df0..7b29807a3 100644 --- a/server/models/user/sql/user-notitication-list-query-builder.ts +++ b/server/models/user/sql/user-notitication-list-query-builder.ts | |||
@@ -180,7 +180,9 @@ export class UserNotificationListQueryBuilder extends AbstractRunQuery { | |||
180 | "Account->Actor->Avatars"."type" AS "Account.Actor.Avatars.type", | 180 | "Account->Actor->Avatars"."type" AS "Account.Actor.Avatars.type", |
181 | "Account->Actor->Avatars"."filename" AS "Account.Actor.Avatars.filename", | 181 | "Account->Actor->Avatars"."filename" AS "Account.Actor.Avatars.filename", |
182 | "Account->Actor->Server"."id" AS "Account.Actor.Server.id", | 182 | "Account->Actor->Server"."id" AS "Account.Actor.Server.id", |
183 | "Account->Actor->Server"."host" AS "Account.Actor.Server.host"` | 183 | "Account->Actor->Server"."host" AS "Account.Actor.Server.host", |
184 | "UserRegistration"."id" AS "UserRegistration.id", | ||
185 | "UserRegistration"."username" AS "UserRegistration.username"` | ||
184 | } | 186 | } |
185 | 187 | ||
186 | private getJoins () { | 188 | private getJoins () { |
@@ -196,74 +198,76 @@ export class UserNotificationListQueryBuilder extends AbstractRunQuery { | |||
196 | ON "Video->VideoChannel->Actor"."serverId" = "Video->VideoChannel->Actor->Server"."id" | 198 | ON "Video->VideoChannel->Actor"."serverId" = "Video->VideoChannel->Actor->Server"."id" |
197 | ) ON "UserNotificationModel"."videoId" = "Video"."id" | 199 | ) ON "UserNotificationModel"."videoId" = "Video"."id" |
198 | 200 | ||
199 | LEFT JOIN ( | 201 | LEFT JOIN ( |
200 | "videoComment" AS "VideoComment" | 202 | "videoComment" AS "VideoComment" |
201 | INNER JOIN "account" AS "VideoComment->Account" ON "VideoComment"."accountId" = "VideoComment->Account"."id" | 203 | 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" | 204 | INNER JOIN "actor" AS "VideoComment->Account->Actor" ON "VideoComment->Account"."actorId" = "VideoComment->Account->Actor"."id" |
203 | LEFT JOIN "actorImage" AS "VideoComment->Account->Actor->Avatars" | 205 | LEFT JOIN "actorImage" AS "VideoComment->Account->Actor->Avatars" |
204 | ON "VideoComment->Account->Actor"."id" = "VideoComment->Account->Actor->Avatars"."actorId" | 206 | ON "VideoComment->Account->Actor"."id" = "VideoComment->Account->Actor->Avatars"."actorId" |
205 | AND "VideoComment->Account->Actor->Avatars"."type" = ${ActorImageType.AVATAR} | 207 | AND "VideoComment->Account->Actor->Avatars"."type" = ${ActorImageType.AVATAR} |
206 | LEFT JOIN "server" AS "VideoComment->Account->Actor->Server" | 208 | LEFT JOIN "server" AS "VideoComment->Account->Actor->Server" |
207 | ON "VideoComment->Account->Actor"."serverId" = "VideoComment->Account->Actor->Server"."id" | 209 | ON "VideoComment->Account->Actor"."serverId" = "VideoComment->Account->Actor->Server"."id" |
208 | INNER JOIN "video" AS "VideoComment->Video" ON "VideoComment"."videoId" = "VideoComment->Video"."id" | 210 | INNER JOIN "video" AS "VideoComment->Video" ON "VideoComment"."videoId" = "VideoComment->Video"."id" |
209 | ) ON "UserNotificationModel"."commentId" = "VideoComment"."id" | 211 | ) ON "UserNotificationModel"."commentId" = "VideoComment"."id" |
212 | |||
213 | LEFT JOIN "abuse" AS "Abuse" ON "UserNotificationModel"."abuseId" = "Abuse"."id" | ||
214 | LEFT JOIN "videoAbuse" AS "Abuse->VideoAbuse" ON "Abuse"."id" = "Abuse->VideoAbuse"."abuseId" | ||
215 | LEFT JOIN "video" AS "Abuse->VideoAbuse->Video" ON "Abuse->VideoAbuse"."videoId" = "Abuse->VideoAbuse->Video"."id" | ||
216 | LEFT JOIN "commentAbuse" AS "Abuse->VideoCommentAbuse" ON "Abuse"."id" = "Abuse->VideoCommentAbuse"."abuseId" | ||
217 | LEFT JOIN "videoComment" AS "Abuse->VideoCommentAbuse->VideoComment" | ||
218 | ON "Abuse->VideoCommentAbuse"."videoCommentId" = "Abuse->VideoCommentAbuse->VideoComment"."id" | ||
219 | LEFT JOIN "video" AS "Abuse->VideoCommentAbuse->VideoComment->Video" | ||
220 | ON "Abuse->VideoCommentAbuse->VideoComment"."videoId" = "Abuse->VideoCommentAbuse->VideoComment->Video"."id" | ||
221 | LEFT JOIN ( | ||
222 | "account" AS "Abuse->FlaggedAccount" | ||
223 | INNER JOIN "actor" AS "Abuse->FlaggedAccount->Actor" ON "Abuse->FlaggedAccount"."actorId" = "Abuse->FlaggedAccount->Actor"."id" | ||
224 | LEFT JOIN "actorImage" AS "Abuse->FlaggedAccount->Actor->Avatars" | ||
225 | ON "Abuse->FlaggedAccount->Actor"."id" = "Abuse->FlaggedAccount->Actor->Avatars"."actorId" | ||
226 | AND "Abuse->FlaggedAccount->Actor->Avatars"."type" = ${ActorImageType.AVATAR} | ||
227 | LEFT JOIN "server" AS "Abuse->FlaggedAccount->Actor->Server" | ||
228 | ON "Abuse->FlaggedAccount->Actor"."serverId" = "Abuse->FlaggedAccount->Actor->Server"."id" | ||
229 | ) ON "Abuse"."flaggedAccountId" = "Abuse->FlaggedAccount"."id" | ||
210 | 230 | ||
211 | LEFT JOIN "abuse" AS "Abuse" ON "UserNotificationModel"."abuseId" = "Abuse"."id" | 231 | LEFT JOIN ( |
212 | LEFT JOIN "videoAbuse" AS "Abuse->VideoAbuse" ON "Abuse"."id" = "Abuse->VideoAbuse"."abuseId" | 232 | "videoBlacklist" AS "VideoBlacklist" |
213 | LEFT JOIN "video" AS "Abuse->VideoAbuse->Video" ON "Abuse->VideoAbuse"."videoId" = "Abuse->VideoAbuse->Video"."id" | 233 | INNER JOIN "video" AS "VideoBlacklist->Video" ON "VideoBlacklist"."videoId" = "VideoBlacklist->Video"."id" |
214 | LEFT JOIN "commentAbuse" AS "Abuse->VideoCommentAbuse" ON "Abuse"."id" = "Abuse->VideoCommentAbuse"."abuseId" | 234 | ) ON "UserNotificationModel"."videoBlacklistId" = "VideoBlacklist"."id" |
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 | 235 | ||
229 | LEFT JOIN ( | 236 | LEFT JOIN "videoImport" AS "VideoImport" ON "UserNotificationModel"."videoImportId" = "VideoImport"."id" |
230 | "videoBlacklist" AS "VideoBlacklist" | 237 | LEFT JOIN "video" AS "VideoImport->Video" ON "VideoImport"."videoId" = "VideoImport->Video"."id" |
231 | INNER JOIN "video" AS "VideoBlacklist->Video" ON "VideoBlacklist"."videoId" = "VideoBlacklist->Video"."id" | ||
232 | ) ON "UserNotificationModel"."videoBlacklistId" = "VideoBlacklist"."id" | ||
233 | 238 | ||
234 | LEFT JOIN "videoImport" AS "VideoImport" ON "UserNotificationModel"."videoImportId" = "VideoImport"."id" | 239 | LEFT JOIN "plugin" AS "Plugin" ON "UserNotificationModel"."pluginId" = "Plugin"."id" |
235 | LEFT JOIN "video" AS "VideoImport->Video" ON "VideoImport"."videoId" = "VideoImport->Video"."id" | ||
236 | 240 | ||
237 | LEFT JOIN "plugin" AS "Plugin" ON "UserNotificationModel"."pluginId" = "Plugin"."id" | 241 | LEFT JOIN "application" AS "Application" ON "UserNotificationModel"."applicationId" = "Application"."id" |
238 | 242 | ||
239 | LEFT JOIN "application" AS "Application" ON "UserNotificationModel"."applicationId" = "Application"."id" | 243 | LEFT JOIN ( |
244 | "actorFollow" AS "ActorFollow" | ||
245 | INNER JOIN "actor" AS "ActorFollow->ActorFollower" ON "ActorFollow"."actorId" = "ActorFollow->ActorFollower"."id" | ||
246 | INNER JOIN "account" AS "ActorFollow->ActorFollower->Account" | ||
247 | ON "ActorFollow->ActorFollower"."id" = "ActorFollow->ActorFollower->Account"."actorId" | ||
248 | LEFT JOIN "actorImage" AS "ActorFollow->ActorFollower->Avatars" | ||
249 | ON "ActorFollow->ActorFollower"."id" = "ActorFollow->ActorFollower->Avatars"."actorId" | ||
250 | AND "ActorFollow->ActorFollower->Avatars"."type" = ${ActorImageType.AVATAR} | ||
251 | LEFT JOIN "server" AS "ActorFollow->ActorFollower->Server" | ||
252 | ON "ActorFollow->ActorFollower"."serverId" = "ActorFollow->ActorFollower->Server"."id" | ||
253 | INNER JOIN "actor" AS "ActorFollow->ActorFollowing" ON "ActorFollow"."targetActorId" = "ActorFollow->ActorFollowing"."id" | ||
254 | LEFT JOIN "videoChannel" AS "ActorFollow->ActorFollowing->VideoChannel" | ||
255 | ON "ActorFollow->ActorFollowing"."id" = "ActorFollow->ActorFollowing->VideoChannel"."actorId" | ||
256 | LEFT JOIN "account" AS "ActorFollow->ActorFollowing->Account" | ||
257 | ON "ActorFollow->ActorFollowing"."id" = "ActorFollow->ActorFollowing->Account"."actorId" | ||
258 | LEFT JOIN "server" AS "ActorFollow->ActorFollowing->Server" | ||
259 | ON "ActorFollow->ActorFollowing"."serverId" = "ActorFollow->ActorFollowing->Server"."id" | ||
260 | ) ON "UserNotificationModel"."actorFollowId" = "ActorFollow"."id" | ||
240 | 261 | ||
241 | LEFT JOIN ( | 262 | LEFT JOIN ( |
242 | "actorFollow" AS "ActorFollow" | 263 | "account" AS "Account" |
243 | INNER JOIN "actor" AS "ActorFollow->ActorFollower" ON "ActorFollow"."actorId" = "ActorFollow->ActorFollower"."id" | 264 | INNER JOIN "actor" AS "Account->Actor" ON "Account"."actorId" = "Account->Actor"."id" |
244 | INNER JOIN "account" AS "ActorFollow->ActorFollower->Account" | 265 | LEFT JOIN "actorImage" AS "Account->Actor->Avatars" |
245 | ON "ActorFollow->ActorFollower"."id" = "ActorFollow->ActorFollower->Account"."actorId" | 266 | ON "Account->Actor"."id" = "Account->Actor->Avatars"."actorId" |
246 | LEFT JOIN "actorImage" AS "ActorFollow->ActorFollower->Avatars" | 267 | AND "Account->Actor->Avatars"."type" = ${ActorImageType.AVATAR} |
247 | ON "ActorFollow->ActorFollower"."id" = "ActorFollow->ActorFollower->Avatars"."actorId" | 268 | LEFT JOIN "server" AS "Account->Actor->Server" ON "Account->Actor"."serverId" = "Account->Actor->Server"."id" |
248 | AND "ActorFollow->ActorFollower->Avatars"."type" = ${ActorImageType.AVATAR} | 269 | ) ON "UserNotificationModel"."accountId" = "Account"."id" |
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 | 270 | ||
260 | LEFT JOIN ( | 271 | LEFT JOIN "userRegistration" as "UserRegistration" ON "UserNotificationModel"."userRegistrationId" = "UserRegistration"."id"` |
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 | } | 272 | } |
269 | } | 273 | } |
diff --git a/server/models/user/user-notification.ts b/server/models/user/user-notification.ts index 6e134158f..667ee7f5f 100644 --- a/server/models/user/user-notification.ts +++ b/server/models/user/user-notification.ts | |||
@@ -20,6 +20,7 @@ import { VideoCommentModel } from '../video/video-comment' | |||
20 | import { VideoImportModel } from '../video/video-import' | 20 | import { VideoImportModel } from '../video/video-import' |
21 | import { UserNotificationListQueryBuilder } from './sql/user-notitication-list-query-builder' | 21 | import { UserNotificationListQueryBuilder } from './sql/user-notitication-list-query-builder' |
22 | import { UserModel } from './user' | 22 | import { UserModel } from './user' |
23 | import { UserRegistrationModel } from './user-registration' | ||
23 | 24 | ||
24 | @Table({ | 25 | @Table({ |
25 | tableName: 'userNotification', | 26 | tableName: 'userNotification', |
@@ -98,6 +99,14 @@ import { UserModel } from './user' | |||
98 | [Op.ne]: null | 99 | [Op.ne]: null |
99 | } | 100 | } |
100 | } | 101 | } |
102 | }, | ||
103 | { | ||
104 | fields: [ 'userRegistrationId' ], | ||
105 | where: { | ||
106 | userRegistrationId: { | ||
107 | [Op.ne]: null | ||
108 | } | ||
109 | } | ||
101 | } | 110 | } |
102 | ] as (ModelIndexesOptions & { where?: WhereOptions })[] | 111 | ] as (ModelIndexesOptions & { where?: WhereOptions })[] |
103 | }) | 112 | }) |
@@ -241,6 +250,18 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti | |||
241 | }) | 250 | }) |
242 | Application: ApplicationModel | 251 | Application: ApplicationModel |
243 | 252 | ||
253 | @ForeignKey(() => UserRegistrationModel) | ||
254 | @Column | ||
255 | userRegistrationId: number | ||
256 | |||
257 | @BelongsTo(() => UserRegistrationModel, { | ||
258 | foreignKey: { | ||
259 | allowNull: true | ||
260 | }, | ||
261 | onDelete: 'cascade' | ||
262 | }) | ||
263 | UserRegistration: UserRegistrationModel | ||
264 | |||
244 | static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) { | 265 | static listForApi (userId: number, start: number, count: number, sort: string, unread?: boolean) { |
245 | const where = { userId } | 266 | const where = { userId } |
246 | 267 | ||
@@ -416,6 +437,10 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti | |||
416 | ? { latestVersion: this.Application.latestPeerTubeVersion } | 437 | ? { latestVersion: this.Application.latestPeerTubeVersion } |
417 | : undefined | 438 | : undefined |
418 | 439 | ||
440 | const registration = this.UserRegistration | ||
441 | ? { id: this.UserRegistration.id, username: this.UserRegistration.username } | ||
442 | : undefined | ||
443 | |||
419 | return { | 444 | return { |
420 | id: this.id, | 445 | id: this.id, |
421 | type: this.type, | 446 | type: this.type, |
@@ -429,6 +454,7 @@ export class UserNotificationModel extends Model<Partial<AttributesOnly<UserNoti | |||
429 | actorFollow, | 454 | actorFollow, |
430 | plugin, | 455 | plugin, |
431 | peertube, | 456 | peertube, |
457 | registration, | ||
432 | createdAt: this.createdAt.toISOString(), | 458 | createdAt: this.createdAt.toISOString(), |
433 | updatedAt: this.updatedAt.toISOString() | 459 | updatedAt: this.updatedAt.toISOString() |
434 | } | 460 | } |
diff --git a/server/models/user/user-registration.ts b/server/models/user/user-registration.ts new file mode 100644 index 000000000..adda3cc7e --- /dev/null +++ b/server/models/user/user-registration.ts | |||
@@ -0,0 +1,259 @@ | |||
1 | import { FindOptions, Op, WhereOptions } from 'sequelize' | ||
2 | import { | ||
3 | AllowNull, | ||
4 | BeforeCreate, | ||
5 | BelongsTo, | ||
6 | Column, | ||
7 | CreatedAt, | ||
8 | DataType, | ||
9 | ForeignKey, | ||
10 | Is, | ||
11 | IsEmail, | ||
12 | Model, | ||
13 | Table, | ||
14 | UpdatedAt | ||
15 | } from 'sequelize-typescript' | ||
16 | import { | ||
17 | isRegistrationModerationResponseValid, | ||
18 | isRegistrationReasonValid, | ||
19 | isRegistrationStateValid | ||
20 | } from '@server/helpers/custom-validators/user-registration' | ||
21 | import { isVideoChannelDisplayNameValid } from '@server/helpers/custom-validators/video-channels' | ||
22 | import { cryptPassword } from '@server/helpers/peertube-crypto' | ||
23 | import { USER_REGISTRATION_STATES } from '@server/initializers/constants' | ||
24 | import { MRegistration, MRegistrationFormattable } from '@server/types/models' | ||
25 | import { UserRegistration, UserRegistrationState } from '@shared/models' | ||
26 | import { AttributesOnly } from '@shared/typescript-utils' | ||
27 | import { isUserDisplayNameValid, isUserEmailVerifiedValid, isUserPasswordValid } from '../../helpers/custom-validators/users' | ||
28 | import { getSort, throwIfNotValid } from '../shared' | ||
29 | import { UserModel } from './user' | ||
30 | |||
31 | @Table({ | ||
32 | tableName: 'userRegistration', | ||
33 | indexes: [ | ||
34 | { | ||
35 | fields: [ 'username' ], | ||
36 | unique: true | ||
37 | }, | ||
38 | { | ||
39 | fields: [ 'email' ], | ||
40 | unique: true | ||
41 | }, | ||
42 | { | ||
43 | fields: [ 'channelHandle' ], | ||
44 | unique: true | ||
45 | }, | ||
46 | { | ||
47 | fields: [ 'userId' ], | ||
48 | unique: true | ||
49 | } | ||
50 | ] | ||
51 | }) | ||
52 | export class UserRegistrationModel extends Model<Partial<AttributesOnly<UserRegistrationModel>>> { | ||
53 | |||
54 | @AllowNull(false) | ||
55 | @Is('RegistrationState', value => throwIfNotValid(value, isRegistrationStateValid, 'state')) | ||
56 | @Column | ||
57 | state: UserRegistrationState | ||
58 | |||
59 | @AllowNull(false) | ||
60 | @Is('RegistrationReason', value => throwIfNotValid(value, isRegistrationReasonValid, 'registration reason')) | ||
61 | @Column(DataType.TEXT) | ||
62 | registrationReason: string | ||
63 | |||
64 | @AllowNull(true) | ||
65 | @Is('RegistrationModerationResponse', value => throwIfNotValid(value, isRegistrationModerationResponseValid, 'moderation response', true)) | ||
66 | @Column(DataType.TEXT) | ||
67 | moderationResponse: string | ||
68 | |||
69 | @AllowNull(true) | ||
70 | @Is('RegistrationPassword', value => throwIfNotValid(value, isUserPasswordValid, 'registration password', true)) | ||
71 | @Column | ||
72 | password: string | ||
73 | |||
74 | @AllowNull(false) | ||
75 | @Column | ||
76 | username: string | ||
77 | |||
78 | @AllowNull(false) | ||
79 | @IsEmail | ||
80 | @Column(DataType.STRING(400)) | ||
81 | email: string | ||
82 | |||
83 | @AllowNull(true) | ||
84 | @Is('RegistrationEmailVerified', value => throwIfNotValid(value, isUserEmailVerifiedValid, 'email verified boolean', true)) | ||
85 | @Column | ||
86 | emailVerified: boolean | ||
87 | |||
88 | @AllowNull(true) | ||
89 | @Is('RegistrationAccountDisplayName', value => throwIfNotValid(value, isUserDisplayNameValid, 'account display name', true)) | ||
90 | @Column | ||
91 | accountDisplayName: string | ||
92 | |||
93 | @AllowNull(true) | ||
94 | @Is('ChannelHandle', value => throwIfNotValid(value, isVideoChannelDisplayNameValid, 'channel handle', true)) | ||
95 | @Column | ||
96 | channelHandle: string | ||
97 | |||
98 | @AllowNull(true) | ||
99 | @Is('ChannelDisplayName', value => throwIfNotValid(value, isVideoChannelDisplayNameValid, 'channel display name', true)) | ||
100 | @Column | ||
101 | channelDisplayName: string | ||
102 | |||
103 | @CreatedAt | ||
104 | createdAt: Date | ||
105 | |||
106 | @UpdatedAt | ||
107 | updatedAt: Date | ||
108 | |||
109 | @ForeignKey(() => UserModel) | ||
110 | @Column | ||
111 | userId: number | ||
112 | |||
113 | @BelongsTo(() => UserModel, { | ||
114 | foreignKey: { | ||
115 | allowNull: true | ||
116 | }, | ||
117 | onDelete: 'SET NULL' | ||
118 | }) | ||
119 | User: UserModel | ||
120 | |||
121 | @BeforeCreate | ||
122 | static async cryptPasswordIfNeeded (instance: UserRegistrationModel) { | ||
123 | instance.password = await cryptPassword(instance.password) | ||
124 | } | ||
125 | |||
126 | static load (id: number): Promise<MRegistration> { | ||
127 | return UserRegistrationModel.findByPk(id) | ||
128 | } | ||
129 | |||
130 | static loadByEmail (email: string): Promise<MRegistration> { | ||
131 | const query = { | ||
132 | where: { email } | ||
133 | } | ||
134 | |||
135 | return UserRegistrationModel.findOne(query) | ||
136 | } | ||
137 | |||
138 | static loadByEmailOrUsername (emailOrUsername: string): Promise<MRegistration> { | ||
139 | const query = { | ||
140 | where: { | ||
141 | [Op.or]: [ | ||
142 | { email: emailOrUsername }, | ||
143 | { username: emailOrUsername } | ||
144 | ] | ||
145 | } | ||
146 | } | ||
147 | |||
148 | return UserRegistrationModel.findOne(query) | ||
149 | } | ||
150 | |||
151 | static loadByEmailOrHandle (options: { | ||
152 | email: string | ||
153 | username: string | ||
154 | channelHandle?: string | ||
155 | }): Promise<MRegistration> { | ||
156 | const { email, username, channelHandle } = options | ||
157 | |||
158 | let or: WhereOptions = [ | ||
159 | { email }, | ||
160 | { channelHandle: username }, | ||
161 | { username } | ||
162 | ] | ||
163 | |||
164 | if (channelHandle) { | ||
165 | or = or.concat([ | ||
166 | { username: channelHandle }, | ||
167 | { channelHandle } | ||
168 | ]) | ||
169 | } | ||
170 | |||
171 | const query = { | ||
172 | where: { | ||
173 | [Op.or]: or | ||
174 | } | ||
175 | } | ||
176 | |||
177 | return UserRegistrationModel.findOne(query) | ||
178 | } | ||
179 | |||
180 | // --------------------------------------------------------------------------- | ||
181 | |||
182 | static listForApi (options: { | ||
183 | start: number | ||
184 | count: number | ||
185 | sort: string | ||
186 | search?: string | ||
187 | }) { | ||
188 | const { start, count, sort, search } = options | ||
189 | |||
190 | const where: WhereOptions = {} | ||
191 | |||
192 | if (search) { | ||
193 | Object.assign(where, { | ||
194 | [Op.or]: [ | ||
195 | { | ||
196 | email: { | ||
197 | [Op.iLike]: '%' + search + '%' | ||
198 | } | ||
199 | }, | ||
200 | { | ||
201 | username: { | ||
202 | [Op.iLike]: '%' + search + '%' | ||
203 | } | ||
204 | } | ||
205 | ] | ||
206 | }) | ||
207 | } | ||
208 | |||
209 | const query: FindOptions = { | ||
210 | offset: start, | ||
211 | limit: count, | ||
212 | order: getSort(sort), | ||
213 | where, | ||
214 | include: [ | ||
215 | { | ||
216 | model: UserModel.unscoped(), | ||
217 | required: false | ||
218 | } | ||
219 | ] | ||
220 | } | ||
221 | |||
222 | return Promise.all([ | ||
223 | UserRegistrationModel.count(query), | ||
224 | UserRegistrationModel.findAll<MRegistrationFormattable>(query) | ||
225 | ]).then(([ total, data ]) => ({ total, data })) | ||
226 | } | ||
227 | |||
228 | // --------------------------------------------------------------------------- | ||
229 | |||
230 | toFormattedJSON (this: MRegistrationFormattable): UserRegistration { | ||
231 | return { | ||
232 | id: this.id, | ||
233 | |||
234 | state: { | ||
235 | id: this.state, | ||
236 | label: USER_REGISTRATION_STATES[this.state] | ||
237 | }, | ||
238 | |||
239 | registrationReason: this.registrationReason, | ||
240 | moderationResponse: this.moderationResponse, | ||
241 | |||
242 | username: this.username, | ||
243 | email: this.email, | ||
244 | emailVerified: this.emailVerified, | ||
245 | |||
246 | accountDisplayName: this.accountDisplayName, | ||
247 | |||
248 | channelHandle: this.channelHandle, | ||
249 | channelDisplayName: this.channelDisplayName, | ||
250 | |||
251 | createdAt: this.createdAt, | ||
252 | updatedAt: this.updatedAt, | ||
253 | |||
254 | user: this.User | ||
255 | ? { id: this.User.id } | ||
256 | : null | ||
257 | } | ||
258 | } | ||
259 | } | ||
diff --git a/server/models/user/user.ts b/server/models/user/user.ts index 0932a367a..c5c8a1b30 100644 --- a/server/models/user/user.ts +++ b/server/models/user/user.ts | |||
@@ -441,16 +441,17 @@ export class UserModel extends Model<Partial<AttributesOnly<UserModel>>> { | |||
441 | }) | 441 | }) |
442 | OAuthTokens: OAuthTokenModel[] | 442 | OAuthTokens: OAuthTokenModel[] |
443 | 443 | ||
444 | // Used if we already set an encrypted password in user model | ||
445 | skipPasswordEncryption = false | ||
446 | |||
444 | @BeforeCreate | 447 | @BeforeCreate |
445 | @BeforeUpdate | 448 | @BeforeUpdate |
446 | static cryptPasswordIfNeeded (instance: UserModel) { | 449 | static async cryptPasswordIfNeeded (instance: UserModel) { |
447 | if (instance.changed('password') && instance.password) { | 450 | if (instance.skipPasswordEncryption) return |
448 | return cryptPassword(instance.password) | 451 | if (!instance.changed('password')) return |
449 | .then(hash => { | 452 | if (!instance.password) return |
450 | instance.password = hash | 453 | |
451 | return undefined | 454 | instance.password = await cryptPassword(instance.password) |
452 | }) | ||
453 | } | ||
454 | } | 455 | } |
455 | 456 | ||
456 | @AfterUpdate | 457 | @AfterUpdate |