|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- import { UserRegistration, UserRegistrationState, type UserRegistrationStateType } from '@peertube/peertube-models'
- import {
- isRegistrationModerationResponseValid,
- isRegistrationReasonValid,
- isRegistrationStateValid
- } from '@server/helpers/custom-validators/user-registration.js'
- import { isVideoChannelDisplayNameValid } from '@server/helpers/custom-validators/video-channels.js'
- import { cryptPassword } from '@server/helpers/peertube-crypto.js'
- import { USER_REGISTRATION_STATES } from '@server/initializers/constants.js'
- import { MRegistration, MRegistrationFormattable } from '@server/types/models/index.js'
- import { FindOptions, Op, QueryTypes, WhereOptions } from 'sequelize'
- import {
- AllowNull,
- BeforeCreate,
- BelongsTo,
- Column,
- CreatedAt,
- DataType,
- ForeignKey,
- Is,
- IsEmail, Table,
- UpdatedAt
- } from 'sequelize-typescript'
- import { isUserDisplayNameValid, isUserEmailVerifiedValid, isUserPasswordValid } from '../../helpers/custom-validators/users.js'
- import { SequelizeModel, getSort, parseAggregateResult, throwIfNotValid } from '../shared/index.js'
- import { UserModel } from './user.js'
- import { forceNumber } from '@peertube/peertube-core-utils'
-
- @Table({
- tableName: 'userRegistration',
- indexes: [
- {
- fields: [ 'username' ],
- unique: true
- },
- {
- fields: [ 'email' ],
- unique: true
- },
- {
- fields: [ 'channelHandle' ],
- unique: true
- },
- {
- fields: [ 'userId' ],
- unique: true
- }
- ]
- })
- export class UserRegistrationModel extends SequelizeModel<UserRegistrationModel> {
-
- @AllowNull(false)
- @Is('RegistrationState', value => throwIfNotValid(value, isRegistrationStateValid, 'state'))
- @Column
- state: UserRegistrationStateType
-
- @AllowNull(false)
- @Is('RegistrationReason', value => throwIfNotValid(value, isRegistrationReasonValid, 'registration reason'))
- @Column(DataType.TEXT)
- registrationReason: string
-
- @AllowNull(true)
- @Is('RegistrationModerationResponse', value => throwIfNotValid(value, isRegistrationModerationResponseValid, 'moderation response', true))
- @Column(DataType.TEXT)
- moderationResponse: string
-
- @AllowNull(true)
- @Is('RegistrationPassword', value => throwIfNotValid(value, isUserPasswordValid, 'registration password', true))
- @Column
- password: string
-
- @AllowNull(false)
- @Column
- username: string
-
- @AllowNull(false)
- @IsEmail
- @Column(DataType.STRING(400))
- email: string
-
- @AllowNull(true)
- @Is('RegistrationEmailVerified', value => throwIfNotValid(value, isUserEmailVerifiedValid, 'email verified boolean', true))
- @Column
- emailVerified: boolean
-
- @AllowNull(true)
- @Is('RegistrationAccountDisplayName', value => throwIfNotValid(value, isUserDisplayNameValid, 'account display name', true))
- @Column
- accountDisplayName: string
-
- @AllowNull(true)
- @Is('ChannelHandle', value => throwIfNotValid(value, isVideoChannelDisplayNameValid, 'channel handle', true))
- @Column
- channelHandle: string
-
- @AllowNull(true)
- @Is('ChannelDisplayName', value => throwIfNotValid(value, isVideoChannelDisplayNameValid, 'channel display name', true))
- @Column
- channelDisplayName: string
-
- @AllowNull(true)
- @Column
- processedAt: Date
-
- @CreatedAt
- createdAt: Date
-
- @UpdatedAt
- updatedAt: Date
-
- @ForeignKey(() => UserModel)
- @Column
- userId: number
-
- @BelongsTo(() => UserModel, {
- foreignKey: {
- allowNull: true
- },
- onDelete: 'SET NULL'
- })
- User: Awaited<UserModel>
-
- @BeforeCreate
- static async cryptPasswordIfNeeded (instance: UserRegistrationModel) {
- instance.password = await cryptPassword(instance.password)
- }
-
- static load (id: number): Promise<MRegistration> {
- return UserRegistrationModel.findByPk(id)
- }
-
- static loadByEmail (email: string): Promise<MRegistration> {
- const query = {
- where: { email }
- }
-
- return UserRegistrationModel.findOne(query)
- }
-
- static loadByEmailOrUsername (emailOrUsername: string): Promise<MRegistration> {
- const query = {
- where: {
- [Op.or]: [
- { email: emailOrUsername },
- { username: emailOrUsername }
- ]
- }
- }
-
- return UserRegistrationModel.findOne(query)
- }
-
- static loadByEmailOrHandle (options: {
- email: string
- username: string
- channelHandle?: string
- }): Promise<MRegistration> {
- const { email, username, channelHandle } = options
-
- let or: WhereOptions = [
- { email },
- { channelHandle: username },
- { username }
- ]
-
- if (channelHandle) {
- or = or.concat([
- { username: channelHandle },
- { channelHandle }
- ])
- }
-
- const query = {
- where: {
- [Op.or]: or
- }
- }
-
- return UserRegistrationModel.findOne(query)
- }
-
- // ---------------------------------------------------------------------------
-
- static listForApi (options: {
- start: number
- count: number
- sort: string
- search?: string
- }) {
- const { start, count, sort, search } = options
-
- const where: WhereOptions = {}
-
- if (search) {
- Object.assign(where, {
- [Op.or]: [
- {
- email: {
- [Op.iLike]: '%' + search + '%'
- }
- },
- {
- username: {
- [Op.iLike]: '%' + search + '%'
- }
- }
- ]
- })
- }
-
- const query: FindOptions = {
- offset: start,
- limit: count,
- order: getSort(sort),
- where,
- include: [
- {
- model: UserModel.unscoped(),
- required: false
- }
- ]
- }
-
- return Promise.all([
- UserRegistrationModel.count(query),
- UserRegistrationModel.findAll<MRegistrationFormattable>(query)
- ]).then(([ total, data ]) => ({ total, data }))
- }
-
- // ---------------------------------------------------------------------------
-
- static getStats () {
- const query = `SELECT ` +
- `AVG(EXTRACT(EPOCH FROM ("processedAt" - "createdAt") * 1000)) ` +
- `FILTER (WHERE "processedAt" IS NOT NULL AND "createdAt" > CURRENT_DATE - INTERVAL '3 months')` +
- `AS "avgResponseTime", ` +
- // "processedAt" has been introduced in PeerTube 6.1 so also check the abuse state to check processed abuses
- `COUNT(*) FILTER (WHERE "processedAt" IS NOT NULL OR "state" != ${UserRegistrationState.PENDING}) AS "processedRequests", ` +
- `COUNT(*) AS "totalRequests" ` +
- `FROM "userRegistration"`
-
- return UserRegistrationModel.sequelize.query<any>(query, {
- type: QueryTypes.SELECT,
- raw: true
- }).then(([ row ]) => {
- return {
- totalRegistrationRequests: parseAggregateResult(row.totalRequests),
-
- totalRegistrationRequestsProcessed: parseAggregateResult(row.processedRequests),
-
- averageRegistrationRequestResponseTimeMs: row?.avgResponseTime
- ? forceNumber(row.avgResponseTime)
- : null
- }
- })
- }
-
- // ---------------------------------------------------------------------------
-
- toFormattedJSON (this: MRegistrationFormattable): UserRegistration {
- return {
- id: this.id,
-
- state: {
- id: this.state,
- label: USER_REGISTRATION_STATES[this.state]
- },
-
- registrationReason: this.registrationReason,
- moderationResponse: this.moderationResponse,
-
- username: this.username,
- email: this.email,
- emailVerified: this.emailVerified,
-
- accountDisplayName: this.accountDisplayName,
-
- channelHandle: this.channelHandle,
- channelDisplayName: this.channelDisplayName,
-
- createdAt: this.createdAt,
- updatedAt: this.updatedAt,
-
- user: this.User
- ? { id: this.User.id }
- : null
- }
- }
- }
|