aboutsummaryrefslogtreecommitdiffhomepage
path: root/server/models/user
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2023-01-19 09:27:16 +0100
committerChocobozzz <chocobozzz@cpy.re>2023-01-19 13:53:40 +0100
commite364e31e25bd1d4b8d801c845a96d6be708f0a18 (patch)
tree220785a42af361706eb8243960c5da9cddf4d2be /server/models/user
parentbc48e33b80f357767b98c1d310b04bdda24c6d46 (diff)
downloadPeerTube-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.ts130
-rw-r--r--server/models/user/user-notification.ts26
-rw-r--r--server/models/user/user-registration.ts259
-rw-r--r--server/models/user/user.ts17
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'
20import { VideoImportModel } from '../video/video-import' 20import { VideoImportModel } from '../video/video-import'
21import { UserNotificationListQueryBuilder } from './sql/user-notitication-list-query-builder' 21import { UserNotificationListQueryBuilder } from './sql/user-notitication-list-query-builder'
22import { UserModel } from './user' 22import { UserModel } from './user'
23import { 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 @@
1import { FindOptions, Op, WhereOptions } from 'sequelize'
2import {
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'
16import {
17 isRegistrationModerationResponseValid,
18 isRegistrationReasonValid,
19 isRegistrationStateValid
20} from '@server/helpers/custom-validators/user-registration'
21import { isVideoChannelDisplayNameValid } from '@server/helpers/custom-validators/video-channels'
22import { cryptPassword } from '@server/helpers/peertube-crypto'
23import { USER_REGISTRATION_STATES } from '@server/initializers/constants'
24import { MRegistration, MRegistrationFormattable } from '@server/types/models'
25import { UserRegistration, UserRegistrationState } from '@shared/models'
26import { AttributesOnly } from '@shared/typescript-utils'
27import { isUserDisplayNameValid, isUserEmailVerifiedValid, isUserPasswordValid } from '../../helpers/custom-validators/users'
28import { getSort, throwIfNotValid } from '../shared'
29import { 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})
52export 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