diff options
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 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { Notifier, ServerService } from '@app/core' | 2 | import { Notifier, ServerService } from '@app/core' |
3 | import { MarkdownService } from '@app/videos/shared' | 3 | import { MarkdownService } from '@app/videos/shared' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' | ||
6 | import { 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 | }) |
11 | export class AboutInstanceComponent implements OnInit { | 13 | export 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 | |||
4 | input[type=text] { | ||
5 | @include peertube-input-text(340px); | ||
6 | display: block; | ||
7 | } | ||
8 | |||
9 | textarea { | ||
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 @@ | |||
1 | import { Component, OnInit, ViewChild } from '@angular/core' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
4 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | ||
5 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
6 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | ||
7 | import { FormReactive, InstanceValidatorsService } from '@app/shared' | ||
8 | import { 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 | }) | ||
15 | export 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' | |||
5 | import { SharedModule } from '../shared' | 5 | import { SharedModule } from '../shared' |
6 | import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component' | 6 | import { AboutInstanceComponent } from '@app/+about/about-instance/about-instance.component' |
7 | import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component' | 7 | import { AboutPeertubeComponent } from '@app/+about/about-peertube/about-peertube.component' |
8 | import { 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() |
15 | export class ServerService { | 15 | export 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 @@ | |||
1 | export * from './custom-config-validators.service' | 1 | export * from './custom-config-validators.service' |
2 | export * from './form-validator.service' | 2 | export * from './form-validator.service' |
3 | export * from './host' | 3 | export * from './host' |
4 | export * from './instance-validators.service' | ||
4 | export * from './login-validators.service' | 5 | export * from './login-validators.service' |
5 | export * from './reset-password-validators.service' | 6 | export * from './reset-password-validators.service' |
6 | export * from './user-validators.service' | 7 | export * 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 @@ | |||
1 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
2 | import { Validators } from '@angular/forms' | ||
3 | import { BuildFormValidator } from '@app/shared' | ||
4 | import { Injectable } from '@angular/core' | ||
5 | |||
6 | @Injectable() | ||
7 | export 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 @@ | |||
1 | import { catchError } from 'rxjs/operators' | ||
2 | import { HttpClient } from '@angular/common/http' | ||
3 | import { Injectable } from '@angular/core' | ||
4 | import { environment } from '../../../environments/environment' | ||
5 | import { RestExtractor, RestService } from '../rest' | ||
6 | import { About } from '../../../../../shared/models/server' | ||
7 | |||
8 | @Injectable() | ||
9 | export 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 | |||
65 | import { UserHistoryService } from '@app/shared/users/user-history.service' | 66 | import { UserHistoryService } from '@app/shared/users/user-history.service' |
66 | import { UserNotificationService } from '@app/shared/users/user-notification.service' | 67 | import { UserNotificationService } from '@app/shared/users/user-notification.service' |
67 | import { UserNotificationsComponent } from '@app/shared/users/user-notifications.component' | 68 | import { UserNotificationsComponent } from '@app/shared/users/user-notifications.component' |
69 | import { 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 | ||
15 | removeFiles () { | 15 | removeFiles () { |
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 | ||
19 | dropRedis () { | 19 | dropRedis () { |
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' | |||
10 | import { RecentlyAddedStrategy } from '../../shared/models/redundancy' | 10 | import { RecentlyAddedStrategy } from '../../shared/models/redundancy' |
11 | import { isArray } from '../helpers/custom-validators/misc' | 11 | import { isArray } from '../helpers/custom-validators/misc' |
12 | import { uniq } from 'lodash' | 12 | import { uniq } from 'lodash' |
13 | import { Emailer } from '../lib/emailer' | ||
13 | 14 | ||
14 | async function checkActivityPubUrls () { | 15 | async 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 |
34 | function checkConfig () { | 35 | function 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.' }) |