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/user-registration.ts | |
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/user-registration.ts')
-rw-r--r-- | server/models/user/user-registration.ts | 259 |
1 files changed, 259 insertions, 0 deletions
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 | } | ||