aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/+about/about-instance/about-instance.component.html8
-rw-r--r--client/src/app/+about/about-instance/about-instance.component.scss16
-rw-r--r--client/src/app/+about/about-instance/about-instance.component.ts17
-rw-r--r--client/src/app/+about/about-instance/contact-admin-modal.component.html50
-rw-r--r--client/src/app/+about/about-instance/contact-admin-modal.component.scss11
-rw-r--r--client/src/app/+about/about-instance/contact-admin-modal.component.ts72
-rw-r--r--client/src/app/+about/about.module.ts4
-rw-r--r--client/src/app/core/server/server.service.ts5
-rw-r--r--client/src/app/shared/forms/form-validators/index.ts1
-rw-r--r--client/src/app/shared/forms/form-validators/instance-validators.service.ts48
-rw-r--r--client/src/app/shared/instance/instance.service.ts36
-rw-r--r--client/src/app/shared/shared.module.ts4
-rwxr-xr-xscripts/clean/server/test.sh2
-rw-r--r--server/controllers/api/config.ts2
-rw-r--r--server/initializers/checker-after-init.ts14
-rw-r--r--server/initializers/checker-before-init.ts2
-rw-r--r--server/lib/emailer.ts11
-rw-r--r--server/middlewares/validators/server.ts2
18 files changed, 281 insertions, 24 deletions
diff --git a/client/src/app/+about/about-instance/about-instance.component.html b/client/src/app/+about/about-instance/about-instance.component.html
index 37ff795f5..8c700752e 100644
--- a/client/src/app/+about/about-instance/about-instance.component.html
+++ b/client/src/app/+about/about-instance/about-instance.component.html
@@ -1,7 +1,9 @@
1<div class="row"> 1<div class="row">
2 <div class="col-md-12 col-xl-6"> 2 <div class="col-md-12 col-xl-6">
3 <div i18n class="about-instance-title"> 3 <div class="about-instance-title">
4 About {{ instanceName }} instance 4 <div i18n>About {{ instanceName }} instance</div>
5
6 <div *ngIf="isContactFormEnabled" (click)="openContactModal()" i18n role="button" class="contact-admin">Contact administrator</div>
5 </div> 7 </div>
6 8
7 <div class="short-description"> 9 <div class="short-description">
@@ -46,3 +48,5 @@
46 <my-instance-features-table></my-instance-features-table> 48 <my-instance-features-table></my-instance-features-table>
47 </div> 49 </div>
48</div> 50</div>
51
52<my-contact-admin-modal #contactAdminModal></my-contact-admin-modal>
diff --git a/client/src/app/+about/about-instance/about-instance.component.scss b/client/src/app/+about/about-instance/about-instance.component.scss
index b451e85aa..75cf57322 100644
--- a/client/src/app/+about/about-instance/about-instance.component.scss
+++ b/client/src/app/+about/about-instance/about-instance.component.scss
@@ -2,9 +2,19 @@
2@import '_mixins'; 2@import '_mixins';
3 3
4.about-instance-title { 4.about-instance-title {
5 font-size: 20px; 5 display: flex;
6 font-weight: bold; 6 justify-content: space-between;
7 margin-bottom: 15px; 7
8 & > div {
9 font-size: 20px;
10 font-weight: bold;
11 margin-bottom: 15px;
12 }
13
14 & > .contact-admin {
15 @include peertube-button;
16 @include orange-button;
17 }
8} 18}
9 19
10.section-title { 20.section-title {
diff --git a/client/src/app/+about/about-instance/about-instance.component.ts b/client/src/app/+about/about-instance/about-instance.component.ts
index 36e7a8e5b..d3ee8a1e4 100644
--- a/client/src/app/+about/about-instance/about-instance.component.ts
+++ b/client/src/app/+about/about-instance/about-instance.component.ts
@@ -1,7 +1,9 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit, ViewChild } from '@angular/core'
2import { Notifier, ServerService } from '@app/core' 2import { Notifier, ServerService } from '@app/core'
3import { MarkdownService } from '@app/videos/shared' 3import { MarkdownService } from '@app/videos/shared'
4import { I18n } from '@ngx-translate/i18n-polyfill' 4import { I18n } from '@ngx-translate/i18n-polyfill'
5import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component'
6import { InstanceService } from '@app/shared/instance/instance.service'
5 7
6@Component({ 8@Component({
7 selector: 'my-about-instance', 9 selector: 'my-about-instance',
@@ -9,6 +11,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
9 styleUrls: [ './about-instance.component.scss' ] 11 styleUrls: [ './about-instance.component.scss' ]
10}) 12})
11export class AboutInstanceComponent implements OnInit { 13export class AboutInstanceComponent implements OnInit {
14 @ViewChild('contactAdminModal') contactAdminModal: ContactAdminModalComponent
15
12 shortDescription = '' 16 shortDescription = ''
13 descriptionHTML = '' 17 descriptionHTML = ''
14 termsHTML = '' 18 termsHTML = ''
@@ -16,6 +20,7 @@ export class AboutInstanceComponent implements OnInit {
16 constructor ( 20 constructor (
17 private notifier: Notifier, 21 private notifier: Notifier,
18 private serverService: ServerService, 22 private serverService: ServerService,
23 private instanceService: InstanceService,
19 private markdownService: MarkdownService, 24 private markdownService: MarkdownService,
20 private i18n: I18n 25 private i18n: I18n
21 ) {} 26 ) {}
@@ -32,8 +37,12 @@ export class AboutInstanceComponent implements OnInit {
32 return this.serverService.getConfig().signup.allowed 37 return this.serverService.getConfig().signup.allowed
33 } 38 }
34 39
40 get isContactFormEnabled () {
41 return this.serverService.getConfig().email.enabled && this.serverService.getConfig().contactForm.enabled
42 }
43
35 ngOnInit () { 44 ngOnInit () {
36 this.serverService.getAbout() 45 this.instanceService.getAbout()
37 .subscribe( 46 .subscribe(
38 res => { 47 res => {
39 this.shortDescription = res.instance.shortDescription 48 this.shortDescription = res.instance.shortDescription
@@ -45,4 +54,8 @@ export class AboutInstanceComponent implements OnInit {
45 ) 54 )
46 } 55 }
47 56
57 openContactModal () {
58 return this.contactAdminModal.show()
59 }
60
48} 61}
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.html b/client/src/app/+about/about-instance/contact-admin-modal.component.html
new file mode 100644
index 000000000..2b3fb32f3
--- /dev/null
+++ b/client/src/app/+about/about-instance/contact-admin-modal.component.html
@@ -0,0 +1,50 @@
1<ng-template #modal>
2 <div class="modal-header">
3 <h4 i18n class="modal-title">Contact {{ instanceName }} administrator</h4>
4 <span class="close" aria-label="Close" role="button" (click)="hide()"></span>
5 </div>
6
7 <div class="modal-body">
8
9 <form novalidate [formGroup]="form" (ngSubmit)="sendForm()">
10 <div class="form-group">
11 <label i18n for="fromName">Your name</label>
12 <input
13 type="text" id="fromName"
14 formControlName="fromName" [ngClass]="{ 'input-error': formErrors.fromName }"
15 >
16 <div *ngIf="formErrors.fromName" class="form-error">{{ formErrors.fromName }}</div>
17 </div>
18
19 <div class="form-group">
20 <label i18n for="fromEmail">Your email</label>
21 <input
22 type="text" id="fromEmail"
23 formControlName="fromEmail" [ngClass]="{ 'input-error': formErrors['fromEmail'] }"
24 >
25 <div *ngIf="formErrors.fromEmail" class="form-error">{{ formErrors.fromEmail }}</div>
26 </div>
27
28 <div class="form-group">
29 <label i18n for="body">Your message</label>
30 <textarea id="body" formControlName="body" [ngClass]="{ 'input-error': formErrors['body'] }">
31 </textarea>
32 <div *ngIf="formErrors.body" class="form-error">{{ formErrors.body }}</div>
33 </div>
34
35 <div *ngIf="error" class="alert alert-danger">{{ error }}</div>
36
37 <div class="form-group inputs">
38 <span i18n class="action-button action-button-cancel" (click)="hide()">
39 Cancel
40 </span>
41
42 <input
43 type="submit" i18n-value value="Submit" class="action-button-submit"
44 [disabled]="!form.valid"
45 >
46 </div>
47 </form>
48
49 </div>
50</ng-template>
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.scss b/client/src/app/+about/about-instance/contact-admin-modal.component.scss
new file mode 100644
index 000000000..260d77888
--- /dev/null
+++ b/client/src/app/+about/about-instance/contact-admin-modal.component.scss
@@ -0,0 +1,11 @@
1@import 'variables';
2@import 'mixins';
3
4input[type=text] {
5 @include peertube-input-text(340px);
6 display: block;
7}
8
9textarea {
10 @include peertube-textarea(100%, 200px);
11}
diff --git a/client/src/app/+about/about-instance/contact-admin-modal.component.ts b/client/src/app/+about/about-instance/contact-admin-modal.component.ts
new file mode 100644
index 000000000..2f707bd53
--- /dev/null
+++ b/client/src/app/+about/about-instance/contact-admin-modal.component.ts
@@ -0,0 +1,72 @@
1import { Component, OnInit, ViewChild } from '@angular/core'
2import { Notifier } from '@app/core'
3import { I18n } from '@ngx-translate/i18n-polyfill'
4import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
5import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
6import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref'
7import { FormReactive, InstanceValidatorsService } from '@app/shared'
8import { InstanceService } from '@app/shared/instance/instance.service'
9
10@Component({
11 selector: 'my-contact-admin-modal',
12 templateUrl: './contact-admin-modal.component.html',
13 styleUrls: [ './contact-admin-modal.component.scss' ]
14})
15export class ContactAdminModalComponent extends FormReactive implements OnInit {
16 @ViewChild('modal') modal: NgbModal
17
18 error: string
19
20 private openedModal: NgbModalRef
21
22 constructor (
23 protected formValidatorService: FormValidatorService,
24 private modalService: NgbModal,
25 private instanceValidatorsService: InstanceValidatorsService,
26 private instanceService: InstanceService,
27 private notifier: Notifier,
28 private i18n: I18n
29 ) {
30 super()
31 }
32
33 ngOnInit () {
34 this.buildForm({
35 fromName: this.instanceValidatorsService.FROM_NAME,
36 fromEmail: this.instanceValidatorsService.FROM_EMAIL,
37 body: this.instanceValidatorsService.BODY
38 })
39 }
40
41 show () {
42 this.openedModal = this.modalService.open(this.modal, { keyboard: false })
43 }
44
45 hide () {
46 this.form.reset()
47 this.error = undefined
48
49 this.openedModal.close()
50 this.openedModal = null
51 }
52
53 sendForm () {
54 const fromName = this.form.value['fromName']
55 const fromEmail = this.form.value[ 'fromEmail' ]
56 const body = this.form.value[ 'body' ]
57
58 this.instanceService.contactAdministrator(fromEmail, fromName, body)
59 .subscribe(
60 () => {
61 this.notifier.success(this.i18n('Your message has been sent.'))
62 this.hide()
63 },
64
65 err => {
66 this.error = err.status === 403
67 ? this.i18n('You already sent this form recently')
68 : err.message
69 }
70 )
71 }
72}
diff --git a/client/src/app/+about/about.module.ts b/client/src/app/+about/about.module.ts
index ff6e8ef41..9c6b29740 100644
--- a/client/src/app/+about/about.module.ts
+++ b/client/src/app/+about/about.module.ts
@@ -5,6 +5,7 @@ import { AboutComponent } from './about.component'
5import { SharedModule } from '../shared' 5import { SharedModule } from '../shared'
6import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component' 6import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component'
7import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component' 7import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component'
8import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component'
8 9
9@NgModule({ 10@NgModule({
10 imports: [ 11 imports: [
@@ -15,7 +16,8 @@ import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertub
15 declarations: [ 16 declarations: [
16 AboutComponent, 17 AboutComponent,
17 AboutInstanceComponent, 18 AboutInstanceComponent,
18 AboutPeertubeComponent 19 AboutPeertubeComponent,
20 ContactAdminModalComponent
19 ], 21 ],
20 22
21 exports: [ 23 exports: [
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts
index 5351f18d5..f33e6f20c 100644
--- a/client/src/app/core/server/server.service.ts
+++ b/client/src/app/core/server/server.service.ts
@@ -13,6 +13,7 @@ import { sortBy } from '@app/shared/misc/utils'
13 13
14@Injectable() 14@Injectable()
15export class ServerService { 15export class ServerService {
16 private static BASE_SERVER_URL = environment.apiUrl + '/api/v1/server/'
16 private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config/' 17 private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config/'
17 private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/' 18 private static BASE_VIDEO_URL = environment.apiUrl + '/api/v1/videos/'
18 private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/' 19 private static BASE_LOCALE_URL = environment.apiUrl + '/client/locales/'
@@ -147,10 +148,6 @@ export class ServerService {
147 return this.videoPrivacies 148 return this.videoPrivacies
148 } 149 }
149 150
150 getAbout () {
151 return this.http.get<About>(ServerService.BASE_CONFIG_URL + '/about')
152 }
153
154 private loadVideoAttributeEnum ( 151 private loadVideoAttributeEnum (
155 attributeName: 'categories' | 'licences' | 'languages' | 'privacies', 152 attributeName: 'categories' | 'licences' | 'languages' | 'privacies',
156 hashToPopulate: VideoConstant<string | number>[], 153 hashToPopulate: VideoConstant<string | number>[],
diff --git a/client/src/app/shared/forms/form-validators/index.ts b/client/src/app/shared/forms/form-validators/index.ts
index 74e385b3d..fdcbedb71 100644
--- a/client/src/app/shared/forms/form-validators/index.ts
+++ b/client/src/app/shared/forms/form-validators/index.ts
@@ -1,6 +1,7 @@
1export * from './custom-config-validators.service' 1export * from './custom-config-validators.service'
2export * from './form-validator.service' 2export * from './form-validator.service'
3export * from './host' 3export * from './host'
4export * from './instance-validators.service'
4export * from './login-validators.service' 5export * from './login-validators.service'
5export * from './reset-password-validators.service' 6export * from './reset-password-validators.service'
6export * from './user-validators.service' 7export * from './user-validators.service'
diff --git a/client/src/app/shared/forms/form-validators/instance-validators.service.ts b/client/src/app/shared/forms/form-validators/instance-validators.service.ts
new file mode 100644
index 000000000..5bb852858
--- /dev/null
+++ b/client/src/app/shared/forms/form-validators/instance-validators.service.ts
@@ -0,0 +1,48 @@
1import { I18n } from '@ngx-translate/i18n-polyfill'
2import { Validators } from '@angular/forms'
3import { BuildFormValidator } from '@app/shared'
4import { Injectable } from '@angular/core'
5
6@Injectable()
7export class InstanceValidatorsService {
8 readonly FROM_EMAIL: BuildFormValidator
9 readonly FROM_NAME: BuildFormValidator
10 readonly BODY: BuildFormValidator
11
12 constructor (private i18n: I18n) {
13
14 this.FROM_EMAIL = {
15 VALIDATORS: [ Validators.required, Validators.email ],
16 MESSAGES: {
17 'required': this.i18n('Email is required.'),
18 'email': this.i18n('Email must be valid.')
19 }
20 }
21
22 this.FROM_NAME = {
23 VALIDATORS: [
24 Validators.required,
25 Validators.minLength(1),
26 Validators.maxLength(120)
27 ],
28 MESSAGES: {
29 'required': this.i18n('Your name is required.'),
30 'minlength': this.i18n('Your name must be at least 1 character long.'),
31 'maxlength': this.i18n('Your name cannot be more than 120 characters long.')
32 }
33 }
34
35 this.BODY = {
36 VALIDATORS: [
37 Validators.required,
38 Validators.minLength(3),
39 Validators.maxLength(5000)
40 ],
41 MESSAGES: {
42 'required': this.i18n('A message is required.'),
43 'minlength': this.i18n('The message must be at least 3 characters long.'),
44 'maxlength': this.i18n('The message cannot be more than 5000 characters long.')
45 }
46 }
47 }
48}
diff --git a/client/src/app/shared/instance/instance.service.ts b/client/src/app/shared/instance/instance.service.ts
new file mode 100644
index 000000000..61321ecce
--- /dev/null
+++ b/client/src/app/shared/instance/instance.service.ts
@@ -0,0 +1,36 @@
1import { catchError } from 'rxjs/operators'
2import { HttpClient } from '@angular/common/http'
3import { Injectable } from '@angular/core'
4import { environment } from '../../../environments/environment'
5import { RestExtractor, RestService } from '../rest'
6import { About } from '../../../../../shared/models/server'
7
8@Injectable()
9export class InstanceService {
10 private static BASE_CONFIG_URL = environment.apiUrl + '/api/v1/config'
11 private static BASE_SERVER_URL = environment.apiUrl + '/api/v1/server'
12
13 constructor (
14 private authHttp: HttpClient,
15 private restService: RestService,
16 private restExtractor: RestExtractor
17 ) {
18 }
19
20 getAbout () {
21 return this.authHttp.get<About>(InstanceService.BASE_CONFIG_URL + '/about')
22 .pipe(catchError(res => this.restExtractor.handleError(res)))
23 }
24
25 contactAdministrator (fromEmail: string, fromName: string, message: string) {
26 const body = {
27 fromEmail,
28 fromName,
29 body: message
30 }
31
32 return this.authHttp.post(InstanceService.BASE_SERVER_URL + '/contact', body)
33 .pipe(catchError(res => this.restExtractor.handleError(res)))
34
35 }
36}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index c99c87c00..d1320aeec 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -37,6 +37,7 @@ import {
37 LoginValidatorsService, 37 LoginValidatorsService,
38 ReactiveFileComponent, 38 ReactiveFileComponent,
39 ResetPasswordValidatorsService, 39 ResetPasswordValidatorsService,
40 InstanceValidatorsService,
40 TextareaAutoResizeDirective, 41 TextareaAutoResizeDirective,
41 UserValidatorsService, 42 UserValidatorsService,
42 VideoAbuseValidatorsService, 43 VideoAbuseValidatorsService,
@@ -65,6 +66,7 @@ import { TopMenuDropdownComponent } from '@app/shared/menu/top-menu-dropdown.com
65import { UserHistoryService } from '@app/shared/users/user-history.service' 66import { UserHistoryService } from '@app/shared/users/user-history.service'
66import { UserNotificationService } from '@app/shared/users/user-notification.service' 67import { UserNotificationService } from '@app/shared/users/user-notification.service'
67import { UserNotificationsComponent } from '@app/shared/users/user-notifications.component' 68import { UserNotificationsComponent } from '@app/shared/users/user-notifications.component'
69import { InstanceService } from '@app/shared/instance/instance.service'
68 70
69@NgModule({ 71@NgModule({
70 imports: [ 72 imports: [
@@ -185,8 +187,10 @@ import { UserNotificationsComponent } from '@app/shared/users/user-notifications
185 OverviewService, 187 OverviewService,
186 VideoChangeOwnershipValidatorsService, 188 VideoChangeOwnershipValidatorsService,
187 VideoAcceptOwnershipValidatorsService, 189 VideoAcceptOwnershipValidatorsService,
190 InstanceValidatorsService,
188 BlocklistService, 191 BlocklistService,
189 UserHistoryService, 192 UserHistoryService,
193 InstanceService,
190 194
191 I18nPrimengCalendarService, 195 I18nPrimengCalendarService,
192 ScreenService, 196 ScreenService,
diff --git a/scripts/clean/server/test.sh b/scripts/clean/server/test.sh
index 75ad491bf..b897c30ba 100755
--- a/scripts/clean/server/test.sh
+++ b/scripts/clean/server/test.sh
@@ -13,7 +13,7 @@ recreateDB () {
13} 13}
14 14
15removeFiles () { 15removeFiles () {
16 rm -rf "./test$1" "./config/local-test-$1.json" 16 rm -rf "./test$1" "./config/local-test.json" "./config/local-test-$1.json"
17} 17}
18 18
19dropRedis () { 19dropRedis () {
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index 43b20e078..dd06a0597 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -65,7 +65,7 @@ async function getConfig (req: express.Request, res: express.Response) {
65 } 65 }
66 }, 66 },
67 email: { 67 email: {
68 enabled: Emailer.Instance.isEnabled() 68 enabled: Emailer.isEnabled()
69 }, 69 },
70 contactForm: { 70 contactForm: {
71 enabled: CONFIG.CONTACT_FORM.ENABLED 71 enabled: CONFIG.CONTACT_FORM.ENABLED
diff --git a/server/initializers/checker-after-init.ts b/server/initializers/checker-after-init.ts
index 72d846957..955d55206 100644
--- a/server/initializers/checker-after-init.ts
+++ b/server/initializers/checker-after-init.ts
@@ -10,6 +10,7 @@ import { getServerActor } from '../helpers/utils'
10import { RecentlyAddedStrategy } from '../../shared/models/redundancy' 10import { RecentlyAddedStrategy } from '../../shared/models/redundancy'
11import { isArray } from '../helpers/custom-validators/misc' 11import { isArray } from '../helpers/custom-validators/misc'
12import { uniq } from 'lodash' 12import { uniq } from 'lodash'
13import { Emailer } from '../lib/emailer'
13 14
14async function checkActivityPubUrls () { 15async function checkActivityPubUrls () {
15 const actor = await getServerActor() 16 const actor = await getServerActor()
@@ -32,9 +33,19 @@ async function checkActivityPubUrls () {
32// Some checks on configuration files 33// Some checks on configuration files
33// Return an error message, or null if everything is okay 34// Return an error message, or null if everything is okay
34function checkConfig () { 35function checkConfig () {
35 const defaultNSFWPolicy = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY 36
37 if (!Emailer.isEnabled()) {
38 if (CONFIG.SIGNUP.ENABLED && CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) {
39 return 'Emailer is disabled but you require signup email verification.'
40 }
41
42 if (CONFIG.CONTACT_FORM.ENABLED) {
43 logger.warn('Emailer is disabled so the contact form will not work.')
44 }
45 }
36 46
37 // NSFW policy 47 // NSFW policy
48 const defaultNSFWPolicy = CONFIG.INSTANCE.DEFAULT_NSFW_POLICY
38 { 49 {
39 const available = [ 'do_not_list', 'blur', 'display' ] 50 const available = [ 'do_not_list', 'blur', 'display' ]
40 if (available.indexOf(defaultNSFWPolicy) === -1) { 51 if (available.indexOf(defaultNSFWPolicy) === -1) {
@@ -68,6 +79,7 @@ function checkConfig () {
68 } 79 }
69 } 80 }
70 81
82 // Check storage directory locations
71 if (isProdInstance()) { 83 if (isProdInstance()) {
72 const configStorage = config.get('storage') 84 const configStorage = config.get('storage')
73 for (const key of Object.keys(configStorage)) { 85 for (const key of Object.keys(configStorage)) {
diff --git a/server/initializers/checker-before-init.ts b/server/initializers/checker-before-init.ts
index a7bc7eec8..7905d9ffa 100644
--- a/server/initializers/checker-before-init.ts
+++ b/server/initializers/checker-before-init.ts
@@ -15,7 +15,7 @@ function checkMissedConfig () {
15 'storage.redundancy', 'storage.tmp', 15 'storage.redundancy', 'storage.tmp',
16 'log.level', 16 'log.level',
17 'user.video_quota', 'user.video_quota_daily', 17 'user.video_quota', 'user.video_quota_daily',
18 'cache.previews.size', 'admin.email', 18 'cache.previews.size', 'admin.email', 'contact_form.enabled',
19 'signup.enabled', 'signup.limit', 'signup.requires_email_verification', 19 'signup.enabled', 'signup.limit', 'signup.requires_email_verification',
20 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist', 20 'signup.filters.cidr.whitelist', 'signup.filters.cidr.blacklist',
21 'redundancy.videos.strategies', 'redundancy.videos.check_interval', 21 'redundancy.videos.strategies', 'redundancy.videos.check_interval',
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts
index 9b1c5122f..f384a254e 100644
--- a/server/lib/emailer.ts
+++ b/server/lib/emailer.ts
@@ -18,7 +18,6 @@ class Emailer {
18 private static instance: Emailer 18 private static instance: Emailer
19 private initialized = false 19 private initialized = false
20 private transporter: Transporter 20 private transporter: Transporter
21 private enabled = false
22 21
23 private constructor () {} 22 private constructor () {}
24 23
@@ -27,7 +26,7 @@ class Emailer {
27 if (this.initialized === true) return 26 if (this.initialized === true) return
28 this.initialized = true 27 this.initialized = true
29 28
30 if (CONFIG.SMTP.HOSTNAME && CONFIG.SMTP.PORT) { 29 if (Emailer.isEnabled()) {
31 logger.info('Using %s:%s as SMTP server.', CONFIG.SMTP.HOSTNAME, CONFIG.SMTP.PORT) 30 logger.info('Using %s:%s as SMTP server.', CONFIG.SMTP.HOSTNAME, CONFIG.SMTP.PORT)
32 31
33 let tls 32 let tls
@@ -55,8 +54,6 @@ class Emailer {
55 tls, 54 tls,
56 auth 55 auth
57 }) 56 })
58
59 this.enabled = true
60 } else { 57 } else {
61 if (!isTestInstance()) { 58 if (!isTestInstance()) {
62 logger.error('Cannot use SMTP server because of lack of configuration. PeerTube will not be able to send mails!') 59 logger.error('Cannot use SMTP server because of lack of configuration. PeerTube will not be able to send mails!')
@@ -64,8 +61,8 @@ class Emailer {
64 } 61 }
65 } 62 }
66 63
67 isEnabled () { 64 static isEnabled () {
68 return this.enabled 65 return !!CONFIG.SMTP.HOSTNAME && !!CONFIG.SMTP.PORT
69 } 66 }
70 67
71 async checkConnectionOrDie () { 68 async checkConnectionOrDie () {
@@ -374,7 +371,7 @@ class Emailer {
374 } 371 }
375 372
376 sendMail (to: string[], subject: string, text: string, from?: string) { 373 sendMail (to: string[], subject: string, text: string, from?: string) {
377 if (!this.enabled) { 374 if (!Emailer.isEnabled()) {
378 throw new Error('Cannot send mail because SMTP is not configured.') 375 throw new Error('Cannot send mail because SMTP is not configured.')
379 } 376 }
380 377
diff --git a/server/middlewares/validators/server.ts b/server/middlewares/validators/server.ts
index d82e19230..d85afc2ff 100644
--- a/server/middlewares/validators/server.ts
+++ b/server/middlewares/validators/server.ts
@@ -50,7 +50,7 @@ const contactAdministratorValidator = [
50 .end() 50 .end()
51 } 51 }
52 52
53 if (Emailer.Instance.isEnabled() === false) { 53 if (Emailer.isEnabled() === false) {
54 return res 54 return res
55 .status(409) 55 .status(409)
56 .send({ error: 'Emailer is not enabled on this instance.' }) 56 .send({ error: 'Emailer is not enabled on this instance.' })