Browse Source

Add ability to not send an email for registration

Chocobozzz 1 year ago
parent
commit
4115f20084

+ 17 - 5
client/src/app/+admin/moderation/registration-list/admin-registration.service.ts

@@ -5,7 +5,7 @@ import { HttpClient, HttpParams } from '@angular/common/http'
 import { Injectable } from '@angular/core'
 import { RestExtractor, RestPagination, RestService } from '@app/core'
 import { arrayify } from '@shared/core-utils'
-import { ResultList, UserRegistration } from '@shared/models'
+import { ResultList, UserRegistration, UserRegistrationUpdateState } from '@shared/models'
 import { environment } from '../../../../environments/environment'
 
 @Injectable()
@@ -40,17 +40,29 @@ export class AdminRegistrationService {
       )
   }
 
-  acceptRegistration (registration: UserRegistration, moderationResponse: string) {
+  acceptRegistration (options: {
+    registration: UserRegistration
+    moderationResponse: string
+    preventEmailDelivery: boolean
+  }) {
+    const { registration, moderationResponse, preventEmailDelivery } = options
+
     const url = AdminRegistrationService.BASE_REGISTRATION_URL + '/' + registration.id + '/accept'
-    const body = { moderationResponse }
+    const body: UserRegistrationUpdateState = { moderationResponse, preventEmailDelivery }
 
     return this.authHttp.post(url, body)
       .pipe(catchError(res => this.restExtractor.handleError(res)))
   }
 
-  rejectRegistration (registration: UserRegistration, moderationResponse: string) {
+  rejectRegistration (options: {
+    registration: UserRegistration
+    moderationResponse: string
+    preventEmailDelivery: boolean
+  }) {
+    const { registration, moderationResponse, preventEmailDelivery } = options
+
     const url = AdminRegistrationService.BASE_REGISTRATION_URL + '/' + registration.id + '/reject'
-    const body = { moderationResponse }
+    const body: UserRegistrationUpdateState = { moderationResponse, preventEmailDelivery }
 
     return this.authHttp.post(url, body)
       .pipe(catchError(res => this.restExtractor.handleError(res)))

+ 10 - 3
client/src/app/+admin/moderation/registration-list/process-registration-modal.component.html

@@ -12,7 +12,7 @@
     <div class="modal-body mb-3">
 
       <div i18n *ngIf="!registration.emailVerified" class="alert alert-warning">
-        Registration email has not been verified.
+        Registration email has not been verified. Email delivery has been disabled by default.
       </div>
 
       <div class="description">
@@ -21,7 +21,7 @@
             <strong>Accepting</strong>&nbsp;<em>{{ registration.username }}</em> registration will create the account and channel.
           </p>
 
-          <p *ngIf="isEmailEnabled()" i18n>
+          <p *ngIf="isEmailEnabled()" i18n [ngClass]="{ 'text-decoration-line-through': isPreventEmailDeliveryChecked() }">
             An email will be sent to <em>{{ registration.email }}</em> explaining its account has been created with the moderation response you'll write below.
           </p>
 
@@ -31,7 +31,7 @@
         </ng-container>
 
         <ng-container *ngIf="isReject()">
-          <p i18n>
+          <p i18n [ngClass]="{ 'text-decoration-line-through': isPreventEmailDeliveryChecked() }">
             An email will be sent to <em>{{ registration.email }}</em> explaining its registration request has been <strong>rejected</strong> with the moderation response you'll write below.
           </p>
 
@@ -53,6 +53,13 @@
           {{ formErrors.moderationResponse }}
         </div>
       </div>
+
+      <div class="form-group">
+        <my-peertube-checkbox
+          inputName="preventEmailDelivery" formControlName="preventEmailDelivery" [disabled]="!isEmailEnabled()"
+          i18n-labelText labelText="Prevent email from being sent to the user"
+        ></my-peertube-checkbox>
+      </div>
     </div>
 
     <div class="modal-footer inputs">

+ 38 - 23
client/src/app/+admin/moderation/registration-list/process-registration-modal.component.ts

@@ -34,7 +34,8 @@ export class ProcessRegistrationModalComponent extends FormReactive implements O
 
   ngOnInit () {
     this.buildForm({
-      moderationResponse: REGISTRATION_MODERATION_RESPONSE_VALIDATOR
+      moderationResponse: REGISTRATION_MODERATION_RESPONSE_VALIDATOR,
+      preventEmailDelivery: null
     })
   }
 
@@ -50,6 +51,10 @@ export class ProcessRegistrationModalComponent extends FormReactive implements O
     this.processMode = mode
     this.registration = registration
 
+    this.form.patchValue({
+      preventEmailDelivery: !this.isEmailEnabled() || registration.emailVerified !== true
+    })
+
     this.openedModal = this.modalService.open(this.modal, { centered: true })
   }
 
@@ -77,31 +82,41 @@ export class ProcessRegistrationModalComponent extends FormReactive implements O
     return this.server.getHTMLConfig().email.enabled
   }
 
-  private acceptRegistration () {
-    this.registrationService.acceptRegistration(this.registration, this.form.value.moderationResponse)
-      .subscribe({
-        next: () => {
-          this.notifier.success($localize`${this.registration.username} account created`)
-
-          this.registrationProcessed.emit()
-          this.hide()
-        },
+  isPreventEmailDeliveryChecked () {
+    return this.form.value.preventEmailDelivery
+  }
 
-        error: err => this.notifier.error(err.message)
-      })
+  private acceptRegistration () {
+    this.registrationService.acceptRegistration({
+      registration: this.registration,
+      moderationResponse: this.form.value.moderationResponse,
+      preventEmailDelivery: this.form.value.preventEmailDelivery
+    }).subscribe({
+      next: () => {
+        this.notifier.success($localize`${this.registration.username} account created`)
+
+        this.registrationProcessed.emit()
+        this.hide()
+      },
+
+      error: err => this.notifier.error(err.message)
+    })
   }
 
   private rejectRegistration () {
-    this.registrationService.rejectRegistration(this.registration, this.form.value.moderationResponse)
-      .subscribe({
-        next: () => {
-          this.notifier.success($localize`${this.registration.username} registration rejected`)
-
-          this.registrationProcessed.emit()
-          this.hide()
-        },
-
-        error: err => this.notifier.error(err.message)
-      })
+    this.registrationService.rejectRegistration({
+      registration: this.registration,
+      moderationResponse: this.form.value.moderationResponse,
+      preventEmailDelivery: this.form.value.preventEmailDelivery
+    }).subscribe({
+      next: () => {
+        this.notifier.success($localize`${this.registration.username} registration rejected`)
+
+        this.registrationProcessed.emit()
+        this.hide()
+      },
+
+      error: err => this.notifier.error(err.message)
+    })
   }
 }

+ 1 - 1
client/src/app/shared/form-validators/form-validator.model.ts

@@ -12,5 +12,5 @@ export type BuildFormArgument = {
 }
 
 export type BuildFormDefaultValues = {
-  [ name: string ]: number | string | string[] | BuildFormDefaultValues
+  [ name: string ]: boolean | number | string | string[] | BuildFormDefaultValues
 }

+ 18 - 5
server/controllers/api/users/registrations.ts

@@ -3,7 +3,14 @@ import { Emailer } from '@server/lib/emailer'
 import { Hooks } from '@server/lib/plugins/hooks'
 import { UserRegistrationModel } from '@server/models/user/user-registration'
 import { pick } from '@shared/core-utils'
-import { HttpStatusCode, UserRegister, UserRegistrationRequest, UserRegistrationState, UserRight } from '@shared/models'
+import {
+  HttpStatusCode,
+  UserRegister,
+  UserRegistrationRequest,
+  UserRegistrationState,
+  UserRegistrationUpdateState,
+  UserRight
+} from '@shared/models'
 import { auditLoggerFactory, UserAuditView } from '../../../helpers/audit-logger'
 import { logger } from '../../../helpers/logger'
 import { CONFIG } from '../../../initializers/config'
@@ -125,6 +132,7 @@ async function requestRegistration (req: express.Request, res: express.Response)
 
 async function acceptRegistration (req: express.Request, res: express.Response) {
   const registration = res.locals.userRegistration
+  const body: UserRegistrationUpdateState = req.body
 
   const userToCreate = buildUser({
     username: registration.username,
@@ -150,26 +158,31 @@ async function acceptRegistration (req: express.Request, res: express.Response)
 
   registration.userId = user.id
   registration.state = UserRegistrationState.ACCEPTED
-  registration.moderationResponse = req.body.moderationResponse
+  registration.moderationResponse = body.moderationResponse
 
   await registration.save()
 
   logger.info('Registration of %s accepted', registration.username)
 
-  Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
+  if (body.preventEmailDelivery !== true) {
+    Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
+  }
 
   return res.sendStatus(HttpStatusCode.NO_CONTENT_204)
 }
 
 async function rejectRegistration (req: express.Request, res: express.Response) {
   const registration = res.locals.userRegistration
+  const body: UserRegistrationUpdateState = req.body
 
   registration.state = UserRegistrationState.REJECTED
-  registration.moderationResponse = req.body.moderationResponse
+  registration.moderationResponse = body.moderationResponse
 
   await registration.save()
 
-  Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
+  if (body.preventEmailDelivery !== true) {
+    Emailer.Instance.addUserRegistrationRequestProcessedJob(registration)
+  }
 
   logger.info('Registration of %s rejected', registration.username)
 

+ 6 - 1
server/middlewares/validators/user-registrations.ts

@@ -1,6 +1,6 @@
 import express from 'express'
 import { body, param, query, ValidationChain } from 'express-validator'
-import { exists, isIdValid } from '@server/helpers/custom-validators/misc'
+import { exists, isBooleanValid, isIdValid, toBooleanOrNull } from '@server/helpers/custom-validators/misc'
 import { isRegistrationModerationResponseValid, isRegistrationReasonValid } from '@server/helpers/custom-validators/user-registration'
 import { CONFIG } from '@server/initializers/config'
 import { Hooks } from '@server/lib/plugins/hooks'
@@ -91,6 +91,11 @@ const acceptOrRejectRegistrationValidator = [
   body('moderationResponse')
     .custom(isRegistrationModerationResponseValid),
 
+  body('preventEmailDelivery')
+    .optional()
+    .customSanitizer(toBooleanOrNull)
+    .custom(isBooleanValid).withMessage('Should have preventEmailDelivery boolean'),
+
   async (req: express.Request, res: express.Response, next: express.NextFunction) => {
     if (areValidationErrors(req, res)) return
     if (!await checkRegistrationIdExist(req.params.registrationId, res)) return

+ 36 - 0
server/tests/api/users/registrations.ts

@@ -329,6 +329,42 @@ describe('Test registrations', function () {
       }
     })
 
+    it('Should be able to prevent email delivery on accept/reject', async function () {
+      this.timeout(50000)
+
+      let id1: number
+      let id2: number
+
+      {
+        const { id } = await server.registrations.requestRegistration({
+          username: 'user7',
+          email: 'user7@example.com',
+          registrationReason: 'tt'
+        })
+        id1 = id
+      }
+      {
+        const { id } = await server.registrations.requestRegistration({
+          username: 'user8',
+          email: 'user8@example.com',
+          registrationReason: 'tt'
+        })
+        id2 = id
+      }
+
+      await server.registrations.accept({ id: id1, moderationResponse: 'tt', preventEmailDelivery: true })
+      await server.registrations.reject({ id: id2, moderationResponse: 'tt', preventEmailDelivery: true })
+
+      await waitJobs([ server ])
+
+      const filtered = emails.filter(e => {
+        const address = e['to'][0]['address']
+        return address === 'user7@example.com' || address === 'user8@example.com'
+      })
+
+      expect(filtered).to.have.lengthOf(0)
+    })
+
     it('Should request a registration without a channel, that will conflict with an already existing channel', async function () {
       let id1: number
       let id2: number

+ 1 - 0
shared/models/users/registration/user-registration-update-state.model.ts

@@ -1,3 +1,4 @@
 export interface UserRegistrationUpdateState {
   moderationResponse: string
+  preventEmailDelivery?: boolean
 }

+ 7 - 13
shared/server-commands/users/registrations-command.ts

@@ -1,5 +1,5 @@
 import { pick } from '@shared/core-utils'
-import { HttpStatusCode, ResultList, UserRegistration, UserRegistrationRequest } from '@shared/models'
+import { HttpStatusCode, ResultList, UserRegistration, UserRegistrationRequest, UserRegistrationUpdateState } from '@shared/models'
 import { unwrapBody } from '../requests'
 import { AbstractCommand, OverrideCommandOptions } from '../shared'
 
@@ -47,35 +47,29 @@ export class RegistrationsCommand extends AbstractCommand {
 
   // ---------------------------------------------------------------------------
 
-  accept (options: OverrideCommandOptions & {
-    id: number
-    moderationResponse: string
-  }) {
-    const { id, moderationResponse } = options
+  accept (options: OverrideCommandOptions & { id: number } & UserRegistrationUpdateState) {
+    const { id } = options
     const path = '/api/v1/users/registrations/' + id + '/accept'
 
     return this.postBodyRequest({
       ...options,
 
       path,
-      fields: { moderationResponse },
+      fields: pick(options, [ 'moderationResponse', 'preventEmailDelivery' ]),
       implicitToken: true,
       defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
     })
   }
 
-  reject (options: OverrideCommandOptions & {
-    id: number
-    moderationResponse: string
-  }) {
-    const { id, moderationResponse } = options
+  reject (options: OverrideCommandOptions & { id: number } & UserRegistrationUpdateState) {
+    const { id } = options
     const path = '/api/v1/users/registrations/' + id + '/reject'
 
     return this.postBodyRequest({
       ...options,
 
       path,
-      fields: { moderationResponse },
+      fields: pick(options, [ 'moderationResponse', 'preventEmailDelivery' ]),
       implicitToken: true,
       defaultExpectedStatus: HttpStatusCode.NO_CONTENT_204
     })

+ 3 - 0
support/doc/api/openapi.yaml

@@ -7961,6 +7961,9 @@ components:
         moderationResponse:
           type: string
           description: Moderation response to send to the user
+        preventEmailDelivery:
+          type: boolean
+          description: Set it to true if you don't want PeerTube to send an email to the user
       required:
         - moderationResponse