diff options
286 files changed, 6254 insertions, 1991 deletions
@@ -182,7 +182,7 @@ See the [architecture blueprint](https://docs.joinpeertube.org/#/contribute-arch | |||
182 | 182 | ||
183 | See our REST API documentation: | 183 | See our REST API documentation: |
184 | * OpenAPI 3.0.0 schema: [/support/doc/api/openapi.yaml](/support/doc/api/openapi.yaml) | 184 | * OpenAPI 3.0.0 schema: [/support/doc/api/openapi.yaml](/support/doc/api/openapi.yaml) |
185 | * Spec explorer: [docs.joinpeertube.org/#/api-rest-reference.html](https://docs.joinpeertube.org/#/api-rest-reference.html) | 185 | * Spec explorer: [docs.joinpeertube.org/api-rest-reference.html](https://docs.joinpeertube.org/api-rest-reference.html) |
186 | 186 | ||
187 | See our [ActivityPub documentation](https://docs.joinpeertube.org/#/api-activitypub). | 187 | See our [ActivityPub documentation](https://docs.joinpeertube.org/#/api-activitypub). |
188 | 188 | ||
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 7c27ec760..9499bbe4e 100644 --- a/client/src/app/+about/about-instance/about-instance.component.html +++ b/client/src/app/+about/about-instance/about-instance.component.html | |||
@@ -1,27 +1,92 @@ | |||
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 | |||
3 | <div class="about-instance-title"> | 4 | <div class="about-instance-title"> |
4 | <div i18n>About {{ instanceName }} instance</div> | 5 | <div i18n class="title">About {{ instanceName }} instance</div> |
5 | 6 | ||
6 | <div *ngIf="isContactFormEnabled" (click)="openContactModal()" i18n role="button" class="contact-admin">Contact administrator</div> | 7 | <div *ngIf="isContactFormEnabled" (click)="openContactModal()" i18n role="button" class="contact-admin">Contact administrator</div> |
7 | </div> | 8 | </div> |
8 | 9 | ||
10 | <div class="block instance-badges"> | ||
11 | <span *ngFor="let category of categories" class="badge badge-primary category">{{ category }}</span> | ||
12 | |||
13 | <span *ngFor="let language of languages" class="badge badge-secondary language">{{ language }}</span> | ||
14 | </div> | ||
15 | |||
9 | <div class="short-description"> | 16 | <div class="short-description"> |
10 | <div>{{ shortDescription }}</div> | 17 | <div class="block short-description">{{ shortDescription }}</div> |
18 | |||
19 | <div *ngIf="isNSFW" class="block dedicated-to-nsfw">This instance is dedicated to sensitive/NSFW content.</div> | ||
20 | </div> | ||
21 | |||
22 | <div class="middle-title" *ngIf="html.administrator || maintenanceLifetime || businessModel"> | ||
23 | Administrators & sustainability | ||
24 | </div> | ||
25 | |||
26 | <div class="block administrator" *ngIf="html.administrator"> | ||
27 | <div i18n class="section-title">Who we are</div> | ||
11 | 28 | ||
12 | <div *ngIf="isNSFW" class="dedicated-to-nsfw">This instance is dedicated to sensitive/NSFW content.</div> | 29 | <div [innerHTML]="html.administrator"></div> |
13 | </div> | 30 | </div> |
14 | 31 | ||
15 | <div class="description"> | 32 | <div class="block creation-reason" *ngIf="creationReason"> |
33 | <div i18n class="section-title">Why we created this instance</div> | ||
34 | |||
35 | <p>{{ creationReason }}</p> | ||
36 | </div> | ||
37 | |||
38 | <div class="block maintenance-lifetime" *ngIf="maintenanceLifetime"> | ||
39 | <div i18n class="section-title">How long we plan to maintain this instance</div> | ||
40 | |||
41 | <p>{{ maintenanceLifetime }}</p> | ||
42 | </div> | ||
43 | |||
44 | <div class="block business-model" *ngIf="businessModel"> | ||
45 | <div i18n class="section-title">How we will pay this instance</div> | ||
46 | |||
47 | <p>{{ businessModel }}</p> | ||
48 | </div> | ||
49 | |||
50 | <div class="middle-title" *ngIf="html.description"> | ||
51 | Information | ||
52 | </div> | ||
53 | |||
54 | <div class="block description"> | ||
16 | <div i18n class="section-title">Description</div> | 55 | <div i18n class="section-title">Description</div> |
17 | 56 | ||
18 | <div [innerHTML]="descriptionHTML"></div> | 57 | <div [innerHTML]="html.description"></div> |
58 | </div> | ||
59 | |||
60 | <div class="middle-title" *ngIf="html.moderationInformation || html.codeOfConduct || html.terms"> | ||
61 | Moderation | ||
62 | </div> | ||
63 | |||
64 | <div class="block moderation-information" *ngIf="html.moderationInformation"> | ||
65 | <div i18n class="section-title">Moderation information</div> | ||
66 | |||
67 | <div [innerHTML]="html.moderationInformation"></div> | ||
19 | </div> | 68 | </div> |
20 | 69 | ||
21 | <div class="terms" id="terms-section"> | 70 | <div class="block code-of-conduct" *ngIf="html.codeOfConduct"> |
71 | <div i18n class="section-title">Code of conduct</div> | ||
72 | |||
73 | <div [innerHTML]="html.codeOfConduct"></div> | ||
74 | </div> | ||
75 | |||
76 | <div class="block terms"> | ||
22 | <div i18n class="section-title">Terms</div> | 77 | <div i18n class="section-title">Terms</div> |
23 | 78 | ||
24 | <div [innerHTML]="termsHTML"></div> | 79 | <div [innerHTML]="html.terms"></div> |
80 | </div> | ||
81 | |||
82 | <div class="middle-title" *ngIf="html.hardwareInformation"> | ||
83 | Other information | ||
84 | </div> | ||
85 | |||
86 | <div class="block hardware-information"> | ||
87 | <div i18n class="section-title">Hardware information</div> | ||
88 | |||
89 | <div [innerHTML]="html.hardwareInformation"></div> | ||
25 | </div> | 90 | </div> |
26 | </div> | 91 | </div> |
27 | 92 | ||
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 0296ae8e9..909ae5c21 100644 --- a/client/src/app/+about/about-instance/about-instance.component.scss +++ b/client/src/app/+about/about-instance/about-instance.component.scss | |||
@@ -5,13 +5,12 @@ | |||
5 | display: flex; | 5 | display: flex; |
6 | justify-content: space-between; | 6 | justify-content: space-between; |
7 | 7 | ||
8 | & > div { | 8 | .title { |
9 | font-size: 20px; | 9 | font-size: 20px; |
10 | font-weight: bold; | 10 | font-weight: $font-semibold; |
11 | margin-bottom: 15px; | ||
12 | } | 11 | } |
13 | 12 | ||
14 | & > .contact-admin { | 13 | .contact-admin { |
15 | @include peertube-button; | 14 | @include peertube-button; |
16 | @include orange-button; | 15 | @include orange-button; |
17 | 16 | ||
@@ -19,14 +18,38 @@ | |||
19 | } | 18 | } |
20 | } | 19 | } |
21 | 20 | ||
21 | .instance-badges { | ||
22 | font-size: 16px; | ||
23 | |||
24 | .badge { | ||
25 | font-size: 12px; | ||
26 | font-weight: $font-semibold; | ||
27 | margin-right: 5px; | ||
28 | |||
29 | &.category { | ||
30 | background-color: var(--mainColor); | ||
31 | } | ||
32 | } | ||
33 | } | ||
34 | |||
22 | .section-title { | 35 | .section-title { |
23 | font-weight: $font-semibold; | 36 | font-weight: $font-semibold; |
24 | font-size: 20px; | 37 | font-size: 16px; |
25 | margin-bottom: 5px; | 38 | margin-bottom: 5px; |
39 | display: flex; | ||
40 | align-items: center; | ||
41 | } | ||
42 | |||
43 | .middle-title { | ||
44 | @include in-content-small-title; | ||
45 | |||
46 | margin-top: 45px; | ||
47 | margin-bottom: 25px; | ||
26 | } | 48 | } |
27 | 49 | ||
28 | .short-description, .description, .terms, .signup { | 50 | .block { |
29 | margin-bottom: 30px; | 51 | margin-bottom: 30px; |
52 | font-size: 15px; | ||
30 | } | 53 | } |
31 | 54 | ||
32 | .short-description .dedicated-to-nsfw { | 55 | .short-description .dedicated-to-nsfw { |
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 a5204de27..16ccae2e2 100644 --- a/client/src/app/+about/about-instance/about-instance.component.ts +++ b/client/src/app/+about/about-instance/about-instance.component.ts | |||
@@ -4,6 +4,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
4 | import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' | 4 | import { ContactAdminModalComponent } from '@app/+about/about-instance/contact-admin-modal.component' |
5 | import { InstanceService } from '@app/shared/instance/instance.service' | 5 | import { InstanceService } from '@app/shared/instance/instance.service' |
6 | import { MarkdownService } from '@app/shared/renderer' | 6 | import { MarkdownService } from '@app/shared/renderer' |
7 | import { forkJoin } from 'rxjs' | ||
8 | import { first } from 'rxjs/operators' | ||
7 | 9 | ||
8 | @Component({ | 10 | @Component({ |
9 | selector: 'my-about-instance', | 11 | selector: 'my-about-instance', |
@@ -14,8 +16,22 @@ export class AboutInstanceComponent implements OnInit { | |||
14 | @ViewChild('contactAdminModal', { static: true }) contactAdminModal: ContactAdminModalComponent | 16 | @ViewChild('contactAdminModal', { static: true }) contactAdminModal: ContactAdminModalComponent |
15 | 17 | ||
16 | shortDescription = '' | 18 | shortDescription = '' |
17 | descriptionHTML = '' | 19 | |
18 | termsHTML = '' | 20 | html = { |
21 | description: '', | ||
22 | terms: '', | ||
23 | codeOfConduct: '', | ||
24 | moderationInformation: '', | ||
25 | administrator: '', | ||
26 | hardwareInformation: '' | ||
27 | } | ||
28 | |||
29 | creationReason = '' | ||
30 | maintenanceLifetime = '' | ||
31 | businessModel = '' | ||
32 | |||
33 | languages: string[] = [] | ||
34 | categories: string[] = [] | ||
19 | 35 | ||
20 | constructor ( | 36 | constructor ( |
21 | private notifier: Notifier, | 37 | private notifier: Notifier, |
@@ -38,21 +54,30 @@ export class AboutInstanceComponent implements OnInit { | |||
38 | } | 54 | } |
39 | 55 | ||
40 | ngOnInit () { | 56 | ngOnInit () { |
41 | this.instanceService.getAbout() | 57 | forkJoin([ |
42 | .subscribe( | 58 | this.instanceService.getAbout(), |
43 | async res => { | 59 | this.serverService.localeObservable.pipe(first()), |
44 | this.shortDescription = res.instance.shortDescription | 60 | this.serverService.videoLanguagesLoaded.pipe(first()), |
61 | this.serverService.videoCategoriesLoaded.pipe(first()) | ||
62 | ]).subscribe( | ||
63 | async ([ about, translations ]) => { | ||
64 | this.shortDescription = about.instance.shortDescription | ||
45 | 65 | ||
46 | this.descriptionHTML = await this.markdownService.textMarkdownToHTML(res.instance.description) | 66 | this.creationReason = about.instance.creationReason |
47 | this.termsHTML = await this.markdownService.textMarkdownToHTML(res.instance.terms) | 67 | this.maintenanceLifetime = about.instance.maintenanceLifetime |
48 | }, | 68 | this.businessModel = about.instance.businessModel |
49 | 69 | ||
50 | () => this.notifier.error(this.i18n('Cannot get about information from server')) | 70 | this.html = await this.instanceService.buildHtml(about) |
51 | ) | 71 | |
72 | this.languages = this.instanceService.buildTranslatedLanguages(about, translations) | ||
73 | this.categories = this.instanceService.buildTranslatedCategories(about, translations) | ||
74 | }, | ||
75 | |||
76 | () => this.notifier.error(this.i18n('Cannot get about information from server')) | ||
77 | ) | ||
52 | } | 78 | } |
53 | 79 | ||
54 | openContactModal () { | 80 | openContactModal () { |
55 | return this.contactAdminModal.show() | 81 | return this.contactAdminModal.show() |
56 | } | 82 | } |
57 | |||
58 | } | 83 | } |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html index fe9d856d0..54115055a 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html | |||
@@ -2,12 +2,13 @@ | |||
2 | 2 | ||
3 | <ngb-tabset class="root-tabset bootstrap"> | 3 | <ngb-tabset class="root-tabset bootstrap"> |
4 | 4 | ||
5 | <ngb-tab i18n-title title="Basic configuration"> | 5 | <ngb-tab i18n-title title="Instance information"> |
6 | <ng-template ngbTabContent> | 6 | <ng-template ngbTabContent> |
7 | 7 | ||
8 | <div i18n class="inner-form-title">Instance</div> | ||
9 | |||
10 | <ng-container formGroupName="instance"> | 8 | <ng-container formGroupName="instance"> |
9 | |||
10 | <div i18n class="inner-form-title">Instance</div> | ||
11 | |||
11 | <div class="form-group"> | 12 | <div class="form-group"> |
12 | <label i18n for="instanceName">Name</label> | 13 | <label i18n for="instanceName">Name</label> |
13 | <input | 14 | <input |
@@ -20,7 +21,7 @@ | |||
20 | <div class="form-group"> | 21 | <div class="form-group"> |
21 | <label i18n for="instanceShortDescription">Short description</label> | 22 | <label i18n for="instanceShortDescription">Short description</label> |
22 | <textarea | 23 | <textarea |
23 | id="instanceShortDescription" formControlName="shortDescription" | 24 | id="instanceShortDescription" formControlName="shortDescription" class="small" |
24 | [ngClass]="{ 'input-error': formErrors['instance.shortDescription'] }" | 25 | [ngClass]="{ 'input-error': formErrors['instance.shortDescription'] }" |
25 | ></textarea> | 26 | ></textarea> |
26 | <div *ngIf="formErrors.instance.shortDescription" class="form-error">{{ formErrors.instance.shortDescription }}</div> | 27 | <div *ngIf="formErrors.instance.shortDescription" class="form-error">{{ formErrors.instance.shortDescription }}</div> |
@@ -36,42 +37,56 @@ | |||
36 | </div> | 37 | </div> |
37 | 38 | ||
38 | <div class="form-group"> | 39 | <div class="form-group"> |
39 | <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help> | 40 | <label i18n for="instanceCategories">Main instance categories</label> |
40 | <my-markdown-textarea | 41 | |
41 | id="instanceTerms" formControlName="terms" textareaWidth="500px" [previewColumn]="true" | 42 | <div> |
42 | [ngClass]="{ 'input-error': formErrors['instance.terms'] }" | 43 | <p-multiSelect |
43 | ></my-markdown-textarea> | 44 | inputId="instanceCategories" [options]="categoryItems" formControlName="categories" showToggleAll="false" |
44 | <div *ngIf="formErrors.instance.terms" class="form-error">{{ formErrors.instance.terms }}</div> | 45 | [defaultLabel]="getDefaultCategoryLabel()" [selectedItemsLabel]="getSelectedCategoryLabel()" |
46 | emptyFilterMessage="No results found" i18n-emptyFilterMessage | ||
47 | ></p-multiSelect> | ||
48 | </div> | ||
45 | </div> | 49 | </div> |
46 | 50 | ||
47 | <div class="form-group"> | 51 | <div class="form-group"> |
48 | <my-peertube-checkbox | 52 | <label i18n for="instanceLanguages">Main languages you/your moderators speak</label> |
49 | inputName="instanceIsNSFW" formControlName="isNSFW" | 53 | |
50 | i18n-labelText labelText="Dedicated to sensitive or NSFW content" | 54 | <div> |
51 | i18n-helpHtml helpHtml="Enabling it will allow other administrators to know that you are mainly federating sensitive content.<br /><br /> | 55 | <p-multiSelect |
52 | Moreover, the NSFW checkbox on video upload will be automatically checked by default." | 56 | inputId="instanceLanguages" [options]="languageItems" formControlName="languages" showToggleAll="false" |
53 | ></my-peertube-checkbox> | 57 | [defaultLabel]="getDefaultLanguageLabel()" [selectedItemsLabel]="getSelectedLanguageLabel()" |
58 | emptyFilterMessage="No results found" i18n-emptyFilterMessage | ||
59 | ></p-multiSelect> | ||
60 | </div> | ||
54 | </div> | 61 | </div> |
55 | 62 | ||
63 | <div i18n class="inner-form-title">Moderation & NSFW</div> | ||
64 | |||
56 | <div class="form-group"> | 65 | <div class="form-group"> |
57 | <label i18n for="instanceDefaultClientRoute">Default client route</label> | 66 | <my-peertube-checkbox inputName="instanceIsNSFW" formControlName="isNSFW"> |
58 | <div class="peertube-select-container"> | 67 | <ng-template ptTemplate="label"> |
59 | <select id="instanceDefaultClientRoute" formControlName="defaultClientRoute"> | 68 | <ng-container i18n>This instance is dedicated to sensitive or NSFW content</ng-container> |
60 | <option i18n value="/videos/overview">Videos Overview</option> | 69 | </ng-template> |
61 | <option i18n value="/videos/trending">Videos Trending</option> | 70 | |
62 | <option i18n value="/videos/recently-added">Videos Recently Added</option> | 71 | <ng-template ptTemplate="help"> |
63 | <option i18n value="/videos/local">Local videos</option> | 72 | <ng-container i18n> |
64 | </select> | 73 | Enabling it will allow other administrators to know that you are mainly federating sensitive content.<br /><br /> |
65 | </div> | 74 | Moreover, the NSFW checkbox on video upload will be automatically checked by default. |
66 | <div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div> | 75 | </ng-container> |
76 | </ng-template> | ||
77 | </my-peertube-checkbox> | ||
67 | </div> | 78 | </div> |
68 | 79 | ||
69 | <div class="form-group"> | 80 | <div class="form-group"> |
70 | <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label> | 81 | <label i18n for="instanceDefaultNSFWPolicy">Policy on videos containing sensitive content</label> |
71 | <my-help | 82 | |
72 | helpType="custom" i18n-customHtml | 83 | <my-help> |
73 | customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video." | 84 | <ng-template ptTemplate="customHtml"> |
74 | ></my-help> | 85 | <ng-container i18n> |
86 | With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video. | ||
87 | </ng-container> | ||
88 | </ng-template> | ||
89 | </my-help> | ||
75 | 90 | ||
76 | <div class="peertube-select-container"> | 91 | <div class="peertube-select-container"> |
77 | <select id="instanceDefaultNSFWPolicy" formControlName="defaultNSFWPolicy"> | 92 | <select id="instanceDefaultNSFWPolicy" formControlName="defaultNSFWPolicy"> |
@@ -82,10 +97,105 @@ | |||
82 | </div> | 97 | </div> |
83 | <div *ngIf="formErrors.instance.defaultNSFWPolicy" class="form-error">{{ formErrors.instance.defaultNSFWPolicy }}</div> | 98 | <div *ngIf="formErrors.instance.defaultNSFWPolicy" class="form-error">{{ formErrors.instance.defaultNSFWPolicy }}</div> |
84 | </div> | 99 | </div> |
100 | |||
101 | <div class="form-group"> | ||
102 | <label i18n for="instanceTerms">Terms</label><my-help helpType="markdownText"></my-help> | ||
103 | <my-markdown-textarea | ||
104 | id="instanceTerms" formControlName="terms" textareaWidth="500px" [previewColumn]="true" | ||
105 | [ngClass]="{ 'input-error': formErrors['instance.terms'] }" | ||
106 | ></my-markdown-textarea> | ||
107 | <div *ngIf="formErrors.instance.terms" class="form-error">{{ formErrors.instance.terms }}</div> | ||
108 | </div> | ||
109 | |||
110 | <div class="form-group"> | ||
111 | <label i18n for="instanceCodeOfConduct">Code of conduct</label><my-help helpType="markdownText"></my-help> | ||
112 | <my-markdown-textarea | ||
113 | id="instanceCodeOfConduct" formControlName="codeOfConduct" textareaWidth="500px" [previewColumn]="true" | ||
114 | [ngClass]="{ 'input-error': formErrors['instance.codeOfConduct'] }" | ||
115 | ></my-markdown-textarea> | ||
116 | <div *ngIf="formErrors.instance.codeOfConduct" class="form-error">{{ formErrors.instance.codeOfConduct }}</div> | ||
117 | </div> | ||
118 | |||
119 | <div class="form-group"> | ||
120 | <label i18n for="instanceModerationInformation">Moderation information</label><my-help helpType="markdownText"></my-help> | ||
121 | <div class="label-small-info">Who moderates the instance? What is the policy regarding NSFW videos? Political videos? etc</div> | ||
122 | |||
123 | <my-markdown-textarea | ||
124 | id="instanceModerationInformation" formControlName="moderationInformation" textareaWidth="500px" [previewColumn]="true" | ||
125 | [ngClass]="{ 'input-error': formErrors['instance.moderationInformation'] }" | ||
126 | ></my-markdown-textarea> | ||
127 | <div *ngIf="formErrors.instance.moderationInformation" class="form-error">{{ formErrors.instance.moderationInformation }}</div> | ||
128 | </div> | ||
129 | |||
130 | <div i18n class="inner-form-title">You and your instance</div> | ||
131 | |||
132 | <div class="form-group"> | ||
133 | <label i18n for="instanceAdministrator">Who is behind the instance?</label> | ||
134 | <div class="label-small-info">A single person? A non profit? A company?</div> | ||
135 | |||
136 | <my-markdown-textarea | ||
137 | id="instanceAdministrator" formControlName="administrator" textareaWidth="500px" textareaHeight="75px" [previewColumn]="true" | ||
138 | [classes]="{ 'input-error': formErrors['instance.administrator'] }" | ||
139 | ></my-markdown-textarea> | ||
140 | |||
141 | <div *ngIf="formErrors.instance.administrator" class="form-error">{{ formErrors.instance.administrator }}</div> | ||
142 | </div> | ||
143 | |||
144 | <div class="form-group"> | ||
145 | <label i18n for="instanceCreationReason">Why did you create this instance?</label> | ||
146 | <div class="label-small-info">To share your personal videos? To open registrations and allow people to upload what they want?</div> | ||
147 | |||
148 | <textarea | ||
149 | id="instanceCreationReason" formControlName="creationReason" class="small" | ||
150 | [ngClass]="{ 'input-error': formErrors['instance.creationReason'] }" | ||
151 | ></textarea> | ||
152 | <div *ngIf="formErrors.instance.creationReason" class="form-error">{{ formErrors.instance.creationReason }}</div> | ||
153 | </div> | ||
154 | |||
155 | <div class="form-group"> | ||
156 | <label i18n for="instanceMaintenanceLifetime">How long do you plan to maintain this instance?</label> | ||
157 | <div class="label-small-info">It's important to know for users who want to register on your instance</div> | ||
158 | |||
159 | <textarea | ||
160 | id="instanceMaintenanceLifetime" formControlName="maintenanceLifetime" class="small" | ||
161 | [ngClass]="{ 'input-error': formErrors['instance.maintenanceLifetime'] }" | ||
162 | ></textarea> | ||
163 | <div *ngIf="formErrors.instance.maintenanceLifetime" class="form-error">{{ formErrors.instance.maintenanceLifetime }}</div> | ||
164 | </div> | ||
165 | |||
166 | <div class="form-group"> | ||
167 | <label i18n for="instanceBusinessModel">How will you pay the PeerTube instance server?</label> | ||
168 | <div class="label-small-info">With you own funds? With users donations? Advertising?</div> | ||
169 | |||
170 | <textarea | ||
171 | id="instanceBusinessModel" formControlName="businessModel" class="small" | ||
172 | [ngClass]="{ 'input-error': formErrors['instance.businessModel'] }" | ||
173 | ></textarea> | ||
174 | <div *ngIf="formErrors.instance.businessModel" class="form-error">{{ formErrors.instance.businessModel }}</div> | ||
175 | </div> | ||
176 | |||
177 | <div i18n class="inner-form-title">Other information</div> | ||
178 | |||
179 | <div class="form-group"> | ||
180 | <label i18n for="instanceHardwareInformation">On what server/hardware the instance runs?</label> | ||
181 | <div class="label-small-info">2vCore 2GB RAM/or directly the link to the server you rent etc</div> | ||
182 | |||
183 | <my-markdown-textarea | ||
184 | id="instanceHardwareInformation" formControlName="hardwareInformation" textareaWidth="500px" textareaHeight="75px" [previewColumn]="true" | ||
185 | [classes]="{ 'input-error': formErrors['instance.hardwareInformation'] }" | ||
186 | ></my-markdown-textarea> | ||
187 | |||
188 | <div *ngIf="formErrors.instance.hardwareInformation" class="form-error">{{ formErrors.instance.hardwareInformation }}</div> | ||
189 | </div> | ||
190 | |||
85 | </ng-container> | 191 | </ng-container> |
192 | </ng-template> | ||
193 | </ngb-tab> | ||
86 | 194 | ||
195 | <ngb-tab i18n-title title="Basic configuration"> | ||
196 | <ng-template ngbTabContent> | ||
87 | 197 | ||
88 | <div i18n class="inner-form-title">Theme</div> | 198 | <div i18n class="inner-form-title">Theme & Default route</div> |
89 | 199 | ||
90 | <ng-container formGroupName="theme"> | 200 | <ng-container formGroupName="theme"> |
91 | <div class="form-group"> | 201 | <div class="form-group"> |
@@ -102,6 +212,19 @@ | |||
102 | </ng-container> | 212 | </ng-container> |
103 | 213 | ||
104 | 214 | ||
215 | <div class="form-group" formGroupName="instance"> | ||
216 | <label i18n for="instanceDefaultClientRoute">Default client route</label> | ||
217 | <div class="peertube-select-container"> | ||
218 | <select id="instanceDefaultClientRoute" formControlName="defaultClientRoute"> | ||
219 | <option i18n value="/videos/overview">Videos Discover</option> | ||
220 | <option i18n value="/videos/trending">Videos Trending</option> | ||
221 | <option i18n value="/videos/recently-added">Videos Recently Added</option> | ||
222 | <option i18n value="/videos/local">Local videos</option> | ||
223 | </select> | ||
224 | </div> | ||
225 | <div *ngIf="formErrors.instance.defaultClientRoute" class="form-error">{{ formErrors.instance.defaultClientRoute }}</div> | ||
226 | </div> | ||
227 | |||
105 | <div i18n class="inner-form-title">Signup</div> | 228 | <div i18n class="inner-form-title">Signup</div> |
106 | 229 | ||
107 | <ng-container formGroupName="signup"> | 230 | <ng-container formGroupName="signup"> |
@@ -221,6 +344,41 @@ | |||
221 | </ng-container> | 344 | </ng-container> |
222 | </ng-container> | 345 | </ng-container> |
223 | 346 | ||
347 | <div i18n class="inner-form-title">Instance followings</div> | ||
348 | |||
349 | <ng-container formGroupName="followings"> | ||
350 | <ng-container formGroupName="instance"> | ||
351 | |||
352 | <ng-container formGroupName="autoFollowBack"> | ||
353 | <div class="form-group"> | ||
354 | <my-peertube-checkbox | ||
355 | inputName="followingsInstanceAutoFollowBackEnabled" formControlName="enabled" | ||
356 | i18n-labelText labelText="Automatically follow other instances that follow you" | ||
357 | ></my-peertube-checkbox> | ||
358 | </div> | ||
359 | </ng-container> | ||
360 | |||
361 | <ng-container formGroupName="autoFollowIndex"> | ||
362 | <div class="form-group"> | ||
363 | <my-peertube-checkbox | ||
364 | inputName="followingsInstanceAutoFollowIndexEnabled" formControlName="enabled" | ||
365 | i18n-labelText labelText="Automatically follow instance of the public index (below)" | ||
366 | ></my-peertube-checkbox> | ||
367 | </div> | ||
368 | |||
369 | <div class="form-group"> | ||
370 | <label i18n for="followingsInstanceAutoFollowIndexUrl">Index URL</label> | ||
371 | <input | ||
372 | type="text" id="followingsInstanceAutoFollowIndexUrl" | ||
373 | formControlName="indexUrl" [ngClass]="{ 'input-error': formErrors['followings.instance.autoFollowIndex.indexUrl'] }" | ||
374 | > | ||
375 | <div *ngIf="formErrors.followings.instance.autoFollowIndex.indexUrl" class="form-error">{{ formErrors.followings.instance.autoFollowIndex.indexUrl }}</div> | ||
376 | </div> | ||
377 | |||
378 | </ng-container> | ||
379 | </ng-container> | ||
380 | </ng-container> | ||
381 | |||
224 | 382 | ||
225 | <div i18n class="inner-form-title">Administrator</div> | 383 | <div i18n class="inner-form-title">Administrator</div> |
226 | 384 | ||
@@ -252,10 +410,13 @@ | |||
252 | 410 | ||
253 | <div class="form-group"> | 411 | <div class="form-group"> |
254 | <label i18n for="signupLimit">Your Twitter username</label> | 412 | <label i18n for="signupLimit">Your Twitter username</label> |
255 | <my-help | 413 | |
256 | helpType="custom" i18n-customHtml | 414 | <my-help> |
257 | customHtml="Indicates the Twitter account for the website or platform on which the content was published." | 415 | <ng-template ptTemplate="customHtml"> |
258 | ></my-help> | 416 | <ng-container i18n>Indicates the Twitter account for the website or platform on which the content was published.</ng-container> |
417 | </ng-template> | ||
418 | </my-help> | ||
419 | |||
259 | <input | 420 | <input |
260 | type="text" id="servicesTwitterUsername" | 421 | type="text" id="servicesTwitterUsername" |
261 | formControlName="username" [ngClass]="{ 'input-error': formErrors['services.twitter.username'] }" | 422 | formControlName="username" [ngClass]="{ 'input-error': formErrors['services.twitter.username'] }" |
@@ -264,13 +425,21 @@ | |||
264 | </div> | 425 | </div> |
265 | 426 | ||
266 | <div class="form-group"> | 427 | <div class="form-group"> |
267 | <my-peertube-checkbox | 428 | <my-peertube-checkbox inputName="servicesTwitterWhitelisted" formControlName="whitelisted"> |
268 | inputName="servicesTwitterWhitelisted" formControlName="whitelisted" | 429 | <ng-template ptTemplate="label"> |
269 | i18n-labelText labelText="Instance whitelisted by Twitter" | 430 | <ng-container i18n>Instance whitelisted by Twitter</ng-container> |
270 | i18n-helpHtml helpHtml="If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br /> | 431 | </ng-template> |
271 | If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br /> | 432 | |
272 | Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> to see if you instance is whitelisted." | 433 | <ng-template ptTemplate="help"> |
273 | ></my-peertube-checkbox> | 434 | <ng-container i18n> |
435 | If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br /> | ||
436 | If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br /> | ||
437 | Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on | ||
438 | <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> | ||
439 | to see if you instance is whitelisted. | ||
440 | </ng-container> | ||
441 | </ng-template> | ||
442 | </my-peertube-checkbox> | ||
274 | </div> | 443 | </div> |
275 | 444 | ||
276 | </ng-container> | 445 | </ng-container> |
@@ -286,11 +455,15 @@ | |||
286 | 455 | ||
287 | <ng-container formGroupName="transcoding"> | 456 | <ng-container formGroupName="transcoding"> |
288 | <div class="form-group"> | 457 | <div class="form-group"> |
289 | <my-peertube-checkbox | 458 | <my-peertube-checkbox inputName="transcodingEnabled" formControlName="enabled"> |
290 | inputName="transcodingEnabled" formControlName="enabled" | 459 | <ng-template ptTemplate="label"> |
291 | i18n-labelText labelText="Transcoding enabled" | 460 | <ng-container i18n>Transcoding enabled</ng-container> |
292 | i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!" | 461 | </ng-template> |
293 | ></my-peertube-checkbox> | 462 | |
463 | <ng-template ptTemplate="help"> | ||
464 | <ng-container i18n>If you disable transcoding, many videos from your users will not work!</ng-container> | ||
465 | </ng-template> | ||
466 | </my-peertube-checkbox> | ||
294 | </div> | 467 | </div> |
295 | 468 | ||
296 | <ng-container *ngIf="isTranscodingEnabled()"> | 469 | <ng-container *ngIf="isTranscodingEnabled()"> |
@@ -299,16 +472,22 @@ | |||
299 | <my-peertube-checkbox | 472 | <my-peertube-checkbox |
300 | inputName="transcodingAllowAdditionalExtensions" formControlName="allowAdditionalExtensions" | 473 | inputName="transcodingAllowAdditionalExtensions" formControlName="allowAdditionalExtensions" |
301 | i18n-labelText labelText="Allow additional extensions" | 474 | i18n-labelText labelText="Allow additional extensions" |
302 | i18n-helpHtml helpHtml="Allow your users to upload .mkv, .mov, .avi, .flv videos" | 475 | > |
303 | ></my-peertube-checkbox> | 476 | <ng-template ptTemplate="help"> |
477 | <ng-container i18n>Allow your users to upload .mkv, .mov, .avi, .flv videos</ng-container> | ||
478 | </ng-template> | ||
479 | </my-peertube-checkbox> | ||
304 | </div> | 480 | </div> |
305 | 481 | ||
306 | <div class="form-group"> | 482 | <div class="form-group"> |
307 | <my-peertube-checkbox | 483 | <my-peertube-checkbox |
308 | inputName="transcodingAllowAudioFiles" formControlName="allowAudioFiles" | 484 | inputName="transcodingAllowAudioFiles" formControlName="allowAudioFiles" |
309 | i18n-labelText labelText="Allow audio files upload" | 485 | i18n-labelText labelText="Allow audio files upload" |
310 | i18n-helpHtml helpHtml="Allow your users to upload audio files that will be merged with the preview file on upload" | 486 | > |
311 | ></my-peertube-checkbox> | 487 | <ng-template ptTemplate="help"> |
488 | <ng-container i18n>Allow your users to upload audio files that will be merged with the preview file on upload</ng-container> | ||
489 | </ng-template> | ||
490 | </my-peertube-checkbox> | ||
312 | </div> | 491 | </div> |
313 | 492 | ||
314 | <div class="form-group"> | 493 | <div class="form-group"> |
@@ -338,10 +517,11 @@ | |||
338 | <div i18n class="inner-form-title"> | 517 | <div i18n class="inner-form-title"> |
339 | Cache | 518 | Cache |
340 | 519 | ||
341 | <my-help | 520 | <my-help> |
342 | helpType="custom" i18n-customHtml | 521 | <ng-template ptTemplate="customHtml"> |
343 | customHtml="Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them." | 522 | <ng-container i18n>Some files are not federated (previews, captions). We fetch them directly from the origin instance and cache them.</ng-container> |
344 | ></my-help> | 523 | </ng-template> |
524 | </my-help> | ||
345 | </div> | 525 | </div> |
346 | 526 | ||
347 | <ng-container formGroupName="cache"> | 527 | <ng-container formGroupName="cache"> |
@@ -370,38 +550,45 @@ | |||
370 | <ng-container formGroupName="customizations"> | 550 | <ng-container formGroupName="customizations"> |
371 | <div class="form-group"> | 551 | <div class="form-group"> |
372 | <label i18n for="customizationJavascript">JavaScript</label> | 552 | <label i18n for="customizationJavascript">JavaScript</label> |
373 | <my-help | 553 | <my-help> |
374 | helpType="custom" i18n-customHtml | 554 | <ng-template ptTemplate="customHtml"> |
375 | customHtml="Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre>" | 555 | <ng-container i18n> |
376 | ></my-help> | 556 | Write directly JavaScript code.<br />Example: <pre>console.log('my instance is amazing');</pre> |
557 | </ng-container> | ||
558 | </ng-template> | ||
559 | </my-help> | ||
560 | |||
377 | <textarea | 561 | <textarea |
378 | id="customizationJavascript" formControlName="javascript" | 562 | id="customizationJavascript" formControlName="javascript" |
379 | [ngClass]="{ 'input-error': formErrors['instance.customizations.javascript'] }" | 563 | [ngClass]="{ 'input-error': formErrors['instance.customizations.javascript'] }" |
380 | ></textarea> | 564 | ></textarea> |
565 | |||
381 | <div *ngIf="formErrors.instance.customizations.javascript" class="form-error">{{ formErrors.instance.customizations.javascript }}</div> | 566 | <div *ngIf="formErrors.instance.customizations.javascript" class="form-error">{{ formErrors.instance.customizations.javascript }}</div> |
382 | </div> | 567 | </div> |
383 | 568 | ||
384 | <div class="form-group"> | 569 | <div class="form-group"> |
385 | <label for="customizationCSS">CSS</label> | 570 | <label for="customizationCSS">CSS</label> |
386 | <my-help | 571 | |
387 | helpType="custom" | 572 | <my-help> |
388 | i18n-customHtml | 573 | <ng-template ptTemplate="customHtml"> |
389 | customHtml=" | 574 | <ng-container i18n> |
390 | Write directly CSS code. Example:<br /><br /> | 575 | Write directly CSS code. Example:<br /><br /> |
391 | <pre> | 576 | <pre> |
392 | #custom-css {{ '{' }} | 577 | #custom-css {{ '{' }} |
393 | color: red; | 578 | color: red; |
394 | {{ '}' }} | 579 | {{ '}' }} |
395 | </pre> | 580 | </pre> |
396 | 581 | ||
397 | Prepend with <em>#custom-css</em> to override styles. Example:<br /><br /> | 582 | Prepend with <em>#custom-css</em> to override styles. Example:<br /><br /> |
398 | <pre> | 583 | <pre> |
399 | #custom-css .logged-in-email {{ '{' }} | 584 | #custom-css .logged-in-email {{ '{' }} |
400 | color: red; | 585 | color: red; |
401 | {{ '}' }} | 586 | {{ '}' }} |
402 | </pre> | 587 | </pre> |
403 | " | 588 | </ng-container> |
404 | ></my-help> | 589 | </ng-template> |
590 | </my-help> | ||
591 | |||
405 | <textarea | 592 | <textarea |
406 | id="customizationCSS" formControlName="css" | 593 | id="customizationCSS" formControlName="css" |
407 | [ngClass]="{ 'input-error': formErrors['instance.customizations.css'] }" | 594 | [ngClass]="{ 'input-error': formErrors['instance.customizations.css'] }" |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss index c90bd5141..2b4d0da2c 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss | |||
@@ -1,6 +1,10 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | .form-group { | ||
5 | margin-bottom: 25px; | ||
6 | } | ||
7 | |||
4 | input[type=text] { | 8 | input[type=text] { |
5 | @include peertube-input-text(340px); | 9 | @include peertube-input-text(340px); |
6 | display: block; | 10 | display: block; |
@@ -40,7 +44,12 @@ textarea { | |||
40 | 44 | ||
41 | display: block; | 45 | display: block; |
42 | 46 | ||
43 | &#instanceShortDescription { | 47 | &.small { |
44 | height: 100px; | 48 | height: 75px; |
45 | } | 49 | } |
46 | } | 50 | } |
51 | |||
52 | .label-small-info { | ||
53 | font-style: italic; | ||
54 | margin-bottom: 10px; | ||
55 | } | ||
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts index 8bd7f7cf6..0a69f3481 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts | |||
@@ -6,6 +6,9 @@ import { Notifier } from '@app/core' | |||
6 | import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model' | 6 | import { CustomConfig } from '../../../../../../shared/models/server/custom-config.model' |
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | 7 | import { I18n } from '@ngx-translate/i18n-polyfill' |
8 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 8 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
9 | import { SelectItem } from 'primeng/api' | ||
10 | import { forkJoin } from 'rxjs' | ||
11 | import { first } from 'rxjs/operators' | ||
9 | 12 | ||
10 | @Component({ | 13 | @Component({ |
11 | selector: 'my-edit-custom-config', | 14 | selector: 'my-edit-custom-config', |
@@ -18,6 +21,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
18 | resolutions: { id: string, label: string }[] = [] | 21 | resolutions: { id: string, label: string }[] = [] |
19 | transcodingThreadOptions: { label: string, value: number }[] = [] | 22 | transcodingThreadOptions: { label: string, value: number }[] = [] |
20 | 23 | ||
24 | languageItems: SelectItem[] = [] | ||
25 | categoryItems: SelectItem[] = [] | ||
26 | |||
21 | constructor ( | 27 | constructor ( |
22 | protected formValidatorService: FormValidatorService, | 28 | protected formValidatorService: FormValidatorService, |
23 | private customConfigValidatorsService: CustomConfigValidatorsService, | 29 | private customConfigValidatorsService: CustomConfigValidatorsService, |
@@ -88,10 +94,26 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
88 | name: this.customConfigValidatorsService.INSTANCE_NAME, | 94 | name: this.customConfigValidatorsService.INSTANCE_NAME, |
89 | shortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION, | 95 | shortDescription: this.customConfigValidatorsService.INSTANCE_SHORT_DESCRIPTION, |
90 | description: null, | 96 | description: null, |
91 | terms: null, | 97 | |
92 | defaultClientRoute: null, | ||
93 | isNSFW: false, | 98 | isNSFW: false, |
94 | defaultNSFWPolicy: null, | 99 | defaultNSFWPolicy: null, |
100 | |||
101 | terms: null, | ||
102 | codeOfConduct: null, | ||
103 | |||
104 | creationReason: null, | ||
105 | moderationInformation: null, | ||
106 | administrator: null, | ||
107 | maintenanceLifetime: null, | ||
108 | businessModel: null, | ||
109 | |||
110 | hardwareInformation: null, | ||
111 | |||
112 | categories: null, | ||
113 | languages: null, | ||
114 | |||
115 | defaultClientRoute: null, | ||
116 | |||
95 | customizations: { | 117 | customizations: { |
96 | javascript: null, | 118 | javascript: null, |
97 | css: null | 119 | css: null |
@@ -158,6 +180,17 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
158 | enabled: null, | 180 | enabled: null, |
159 | manualApproval: null | 181 | manualApproval: null |
160 | } | 182 | } |
183 | }, | ||
184 | followings: { | ||
185 | instance: { | ||
186 | autoFollowBack: { | ||
187 | enabled: null | ||
188 | }, | ||
189 | autoFollowIndex: { | ||
190 | enabled: null, | ||
191 | indexUrl: this.customConfigValidatorsService.INDEX_URL | ||
192 | } | ||
193 | } | ||
161 | } | 194 | } |
162 | } | 195 | } |
163 | 196 | ||
@@ -173,18 +206,27 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
173 | 206 | ||
174 | this.buildForm(formGroupData) | 207 | this.buildForm(formGroupData) |
175 | 208 | ||
176 | this.configService.getCustomConfig() | 209 | forkJoin([ |
177 | .subscribe( | 210 | this.configService.getCustomConfig(), |
178 | res => { | 211 | this.serverService.videoLanguagesLoaded.pipe(first()), // First so the observable completes |
179 | this.customConfig = res | 212 | this.serverService.videoCategoriesLoaded.pipe(first()) |
213 | ]).subscribe( | ||
214 | ([ config ]) => { | ||
215 | this.customConfig = config | ||
180 | 216 | ||
181 | this.updateForm() | 217 | const languages = this.serverService.getVideoLanguages() |
182 | // Force form validation | 218 | this.languageItems = languages.map(l => ({ label: l.label, value: l.id })) |
183 | this.forceCheck() | ||
184 | }, | ||
185 | 219 | ||
186 | err => this.notifier.error(err.message) | 220 | const categories = this.serverService.getVideoCategories() |
187 | ) | 221 | this.categoryItems = categories.map(l => ({ label: l.label, value: l.id })) |
222 | |||
223 | this.updateForm() | ||
224 | // Force form validation | ||
225 | this.forceCheck() | ||
226 | }, | ||
227 | |||
228 | err => this.notifier.error(err.message) | ||
229 | ) | ||
188 | } | 230 | } |
189 | 231 | ||
190 | isTranscodingEnabled () { | 232 | isTranscodingEnabled () { |
@@ -213,8 +255,23 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit { | |||
213 | ) | 255 | ) |
214 | } | 256 | } |
215 | 257 | ||
258 | getSelectedLanguageLabel () { | ||
259 | return this.i18n('{{\'{0} languages selected') | ||
260 | } | ||
261 | |||
262 | getDefaultLanguageLabel () { | ||
263 | return this.i18n('No language') | ||
264 | } | ||
265 | |||
266 | getSelectedCategoryLabel () { | ||
267 | return this.i18n('{{\'{0} categories selected') | ||
268 | } | ||
269 | |||
270 | getDefaultCategoryLabel () { | ||
271 | return this.i18n('No category') | ||
272 | } | ||
273 | |||
216 | private updateForm () { | 274 | private updateForm () { |
217 | this.form.patchValue(this.customConfig) | 275 | this.form.patchValue(this.customConfig) |
218 | } | 276 | } |
219 | |||
220 | } | 277 | } |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts index 34febc457..76fabb19d 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-notification-preferences/my-account-notification-preferences.component.ts | |||
@@ -43,7 +43,8 @@ export class MyAccountNotificationPreferencesComponent implements OnInit { | |||
43 | newUserRegistration: this.i18n('A new user registered on your instance'), | 43 | newUserRegistration: this.i18n('A new user registered on your instance'), |
44 | newFollow: this.i18n('You or your channel(s) has a new follower'), | 44 | newFollow: this.i18n('You or your channel(s) has a new follower'), |
45 | commentMention: this.i18n('Someone mentioned you in video comments'), | 45 | commentMention: this.i18n('Someone mentioned you in video comments'), |
46 | newInstanceFollower: this.i18n('Your instance has a new follower') | 46 | newInstanceFollower: this.i18n('Your instance has a new follower'), |
47 | autoInstanceFollowing: this.i18n('Your instance auto followed another instance') | ||
47 | } | 48 | } |
48 | this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[] | 49 | this.notificationSettingKeys = Object.keys(this.labelNotifications) as (keyof UserNotificationSetting)[] |
49 | 50 | ||
@@ -51,7 +52,8 @@ export class MyAccountNotificationPreferencesComponent implements OnInit { | |||
51 | videoAbuseAsModerator: UserRight.MANAGE_VIDEO_ABUSES, | 52 | videoAbuseAsModerator: UserRight.MANAGE_VIDEO_ABUSES, |
52 | videoAutoBlacklistAsModerator: UserRight.MANAGE_VIDEO_BLACKLIST, | 53 | videoAutoBlacklistAsModerator: UserRight.MANAGE_VIDEO_BLACKLIST, |
53 | newUserRegistration: UserRight.MANAGE_USERS, | 54 | newUserRegistration: UserRight.MANAGE_USERS, |
54 | newInstanceFollower: UserRight.MANAGE_SERVER_FOLLOW | 55 | newInstanceFollower: UserRight.MANAGE_SERVER_FOLLOW, |
56 | autoInstanceFollowing: UserRight.MANAGE_CONFIGURATION | ||
55 | } | 57 | } |
56 | 58 | ||
57 | this.emailEnabled = this.serverService.getConfig().email.enabled | 59 | this.emailEnabled = this.serverService.getConfig().email.enabled |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html index 2796dd2db..a11238925 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html +++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html | |||
@@ -1,10 +1,13 @@ | |||
1 | <form role="form" (ngSubmit)="updateDetails()" [formGroup]="form"> | 1 | <form role="form" (ngSubmit)="updateDetails()" [formGroup]="form"> |
2 | <div class="form-group"> | 2 | <div class="form-group"> |
3 | <label i18n for="nsfwPolicy">Default policy on videos containing sensitive content</label> | 3 | <label i18n for="nsfwPolicy">Default policy on videos containing sensitive content</label> |
4 | <my-help | 4 | <my-help> |
5 | helpType="custom" i18n-customHtml | 5 | <ng-template ptTemplate="customHtml"> |
6 | customHtml="With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video." | 6 | <ng-container i18n> |
7 | ></my-help> | 7 | With <strong>Do not list</strong> or <strong>Blur thumbnails</strong>, a confirmation will be requested to watch the video. |
8 | </ng-container> | ||
9 | </ng-template> | ||
10 | </my-help> | ||
8 | 11 | ||
9 | <div class="peertube-select-container"> | 12 | <div class="peertube-select-container"> |
10 | <select id="nsfwPolicy" formControlName="nsfwPolicy"> | 13 | <select id="nsfwPolicy" formControlName="nsfwPolicy"> |
@@ -17,13 +20,15 @@ | |||
17 | 20 | ||
18 | <div class="form-group"> | 21 | <div class="form-group"> |
19 | <label i18n for="videoLanguages">Only display videos in the following languages</label> | 22 | <label i18n for="videoLanguages">Only display videos in the following languages</label> |
20 | <my-help i18n-customHtml | 23 | <my-help> |
21 | customHtml="In Recently added, Trending, Local and Search pages" | 24 | <ng-template ptTemplate="customHtml"> |
22 | ></my-help> | 25 | <ng-container i18n>In Recently added, Trending, Local and Search pages</ng-container> |
26 | </ng-template> | ||
27 | </my-help> | ||
23 | 28 | ||
24 | <div> | 29 | <div> |
25 | <p-multiSelect | 30 | <p-multiSelect |
26 | [options]="languageItems" formControlName="videoLanguages" showToggleAll="true" | 31 | inputId="videoLanguages" [options]="languageItems" formControlName="videoLanguages" showToggleAll="true" |
27 | [defaultLabel]="getDefaultVideoLanguageLabel()" [selectedItemsLabel]="getSelectedVideoLanguageLabel()" | 32 | [defaultLabel]="getDefaultVideoLanguageLabel()" [selectedItemsLabel]="getSelectedVideoLanguageLabel()" |
28 | emptyFilterMessage="No results found" i18n-emptyFilterMessage | 33 | emptyFilterMessage="No results found" i18n-emptyFilterMessage |
29 | ></p-multiSelect> | 34 | ></p-multiSelect> |
diff --git a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts index 77febf179..4fb828082 100644 --- a/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts +++ b/client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.ts | |||
@@ -5,9 +5,9 @@ import { AuthService } from '../../../core' | |||
5 | import { FormReactive, User, UserService } from '../../../shared' | 5 | import { FormReactive, User, UserService } from '../../../shared' |
6 | import { I18n } from '@ngx-translate/i18n-polyfill' | 6 | import { I18n } from '@ngx-translate/i18n-polyfill' |
7 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 7 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
8 | import { Subject } from 'rxjs' | 8 | import { forkJoin, Subject } from 'rxjs' |
9 | import { SelectItem } from 'primeng/api' | 9 | import { SelectItem } from 'primeng/api' |
10 | import { switchMap } from 'rxjs/operators' | 10 | import { first } from 'rxjs/operators' |
11 | 11 | ||
12 | @Component({ | 12 | @Component({ |
13 | selector: 'my-account-video-settings', | 13 | selector: 'my-account-video-settings', |
@@ -39,30 +39,31 @@ export class MyAccountVideoSettingsComponent extends FormReactive implements OnI | |||
39 | videoLanguages: null | 39 | videoLanguages: null |
40 | }) | 40 | }) |
41 | 41 | ||
42 | this.serverService.videoLanguagesLoaded | 42 | forkJoin([ |
43 | .pipe(switchMap(() => this.userInformationLoaded)) | 43 | this.serverService.videoLanguagesLoaded.pipe(first()), |
44 | .subscribe(() => { | 44 | this.userInformationLoaded.pipe(first()) |
45 | const languages = this.serverService.getVideoLanguages() | 45 | ]).subscribe(() => { |
46 | 46 | const languages = this.serverService.getVideoLanguages() | |
47 | this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ] | 47 | |
48 | this.languageItems = this.languageItems | 48 | this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ] |
49 | .concat(languages.map(l => ({ label: l.label, value: l.id }))) | 49 | this.languageItems = this.languageItems |
50 | 50 | .concat(languages.map(l => ({ label: l.label, value: l.id }))) | |
51 | const videoLanguages = this.user.videoLanguages | 51 | |
52 | ? this.user.videoLanguages | 52 | const videoLanguages = this.user.videoLanguages |
53 | : this.languageItems.map(l => l.value) | 53 | ? this.user.videoLanguages |
54 | 54 | : this.languageItems.map(l => l.value) | |
55 | this.form.patchValue({ | 55 | |
56 | nsfwPolicy: this.user.nsfwPolicy, | 56 | this.form.patchValue({ |
57 | webTorrentEnabled: this.user.webTorrentEnabled, | 57 | nsfwPolicy: this.user.nsfwPolicy, |
58 | autoPlayVideo: this.user.autoPlayVideo === true, | 58 | webTorrentEnabled: this.user.webTorrentEnabled, |
59 | videoLanguages | 59 | autoPlayVideo: this.user.autoPlayVideo === true, |
60 | }) | 60 | videoLanguages |
61 | }) | 61 | }) |
62 | }) | ||
62 | } | 63 | } |
63 | 64 | ||
64 | updateDetails () { | 65 | updateDetails () { |
65 | const nsfwPolicy = this.form.value['nsfwPolicy'] | 66 | const nsfwPolicy = this.form.value[ 'nsfwPolicy' ] |
66 | const webTorrentEnabled = this.form.value['webTorrentEnabled'] | 67 | const webTorrentEnabled = this.form.value['webTorrentEnabled'] |
67 | const autoPlayVideo = this.form.value['autoPlayVideo'] | 68 | const autoPlayVideo = this.form.value['autoPlayVideo'] |
68 | 69 | ||
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts index 571f46de9..6cf1499d3 100644 --- a/client/src/app/+my-account/my-account.module.ts +++ b/client/src/app/+my-account/my-account.module.ts | |||
@@ -37,7 +37,6 @@ import { | |||
37 | } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component' | 37 | } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component' |
38 | import { DragDropModule } from '@angular/cdk/drag-drop' | 38 | import { DragDropModule } from '@angular/cdk/drag-drop' |
39 | import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email' | 39 | import { MyAccountChangeEmailComponent } from '@app/+my-account/my-account-settings/my-account-change-email' |
40 | import { MultiSelectModule } from 'primeng/multiselect' | ||
41 | import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' | 40 | import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account-settings/my-account-interface' |
42 | 41 | ||
43 | @NgModule({ | 42 | @NgModule({ |
@@ -48,8 +47,7 @@ import { MyAccountInterfaceSettingsComponent } from '@app/+my-account/my-account | |||
48 | SharedModule, | 47 | SharedModule, |
49 | TableModule, | 48 | TableModule, |
50 | InputSwitchModule, | 49 | InputSwitchModule, |
51 | DragDropModule, | 50 | DragDropModule |
52 | MultiSelectModule | ||
53 | ], | 51 | ], |
54 | 52 | ||
55 | declarations: [ | 53 | declarations: [ |
diff --git a/client/src/app/+signup/+register/register-step-user.component.html b/client/src/app/+signup/+register/register-step-user.component.html index 47b3be8cc..4381702ae 100644 --- a/client/src/app/+signup/+register/register-step-user.component.html +++ b/client/src/app/+signup/+register/register-step-user.component.html | |||
@@ -60,11 +60,16 @@ | |||
60 | </div> | 60 | </div> |
61 | 61 | ||
62 | <div class="form-group form-group-terms"> | 62 | <div class="form-group form-group-terms"> |
63 | <my-peertube-checkbox | 63 | <my-peertube-checkbox inputName="terms" formControlName="terms"> |
64 | inputName="terms" formControlName="terms" | 64 | <ng-template ptTemplate="label"> |
65 | i18n-labelHtml | 65 | <ng-container i18n> |
66 | labelHtml="I am at least 16 years old and agree to the <a href='/about/instance#terms-section' target='_blank'rel='noopener noreferrer'>Terms</a> of this instance" | 66 | I am at least 16 years old and agree |
67 | ></my-peertube-checkbox> | 67 | to the <a (click)="onTermsClick($event)" href='#'>Terms</a> |
68 | <ng-container *ngIf="hasCodeOfConduct"> and to the <a (click)="onCodeOfConductClick($event)" href='#'>Code of Conduct</a></ng-container> | ||
69 | of this instance | ||
70 | </ng-container> | ||
71 | </ng-template> | ||
72 | </my-peertube-checkbox> | ||
68 | 73 | ||
69 | <div *ngIf="formErrors.terms" class="form-error"> | 74 | <div *ngIf="formErrors.terms" class="form-error"> |
70 | {{ formErrors.terms }} | 75 | {{ formErrors.terms }} |
diff --git a/client/src/app/+signup/+register/register-step-user.component.ts b/client/src/app/+signup/+register/register-step-user.component.ts index 3b71fd3c4..6c96f20b4 100644 --- a/client/src/app/+signup/+register/register-step-user.component.ts +++ b/client/src/app/+signup/+register/register-step-user.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output } from '@angular/core' | 1 | import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core' |
2 | import { AuthService } from '@app/core' | 2 | import { AuthService } from '@app/core' |
3 | import { FormReactive, UserService, UserValidatorsService } from '@app/shared' | 3 | import { FormReactive, UserService, UserValidatorsService } from '@app/shared' |
4 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | 4 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' |
@@ -12,7 +12,11 @@ import { concat, of } from 'rxjs' | |||
12 | styleUrls: [ './register.component.scss' ] | 12 | styleUrls: [ './register.component.scss' ] |
13 | }) | 13 | }) |
14 | export class RegisterStepUserComponent extends FormReactive implements OnInit { | 14 | export class RegisterStepUserComponent extends FormReactive implements OnInit { |
15 | @Input() hasCodeOfConduct = false | ||
16 | |||
15 | @Output() formBuilt = new EventEmitter<FormGroup>() | 17 | @Output() formBuilt = new EventEmitter<FormGroup>() |
18 | @Output() termsClick = new EventEmitter<void>() | ||
19 | @Output() codeOfConductClick = new EventEmitter<void>() | ||
16 | 20 | ||
17 | constructor ( | 21 | constructor ( |
18 | protected formValidatorService: FormValidatorService, | 22 | protected formValidatorService: FormValidatorService, |
@@ -45,6 +49,16 @@ export class RegisterStepUserComponent extends FormReactive implements OnInit { | |||
45 | .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue)) | 49 | .subscribe(([ oldValue, newValue ]) => this.onDisplayNameChange(oldValue, newValue)) |
46 | } | 50 | } |
47 | 51 | ||
52 | onTermsClick (event: Event) { | ||
53 | event.preventDefault() | ||
54 | this.termsClick.emit() | ||
55 | } | ||
56 | |||
57 | onCodeOfConductClick (event: Event) { | ||
58 | event.preventDefault() | ||
59 | this.codeOfConductClick.emit() | ||
60 | } | ||
61 | |||
48 | private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) { | 62 | private onDisplayNameChange (oldDisplayName: string, newDisplayName: string) { |
49 | const username = this.form.value['username'] || '' | 63 | const username = this.form.value['username'] || '' |
50 | 64 | ||
diff --git a/client/src/app/+signup/+register/register.component.html b/client/src/app/+signup/+register/register.component.html index e4d647fef..906e29aed 100644 --- a/client/src/app/+signup/+register/register.component.html +++ b/client/src/app/+signup/+register/register.component.html | |||
@@ -7,11 +7,15 @@ | |||
7 | <my-signup-success *ngIf="signupDone" [message]="success"></my-signup-success> | 7 | <my-signup-success *ngIf="signupDone" [message]="success"></my-signup-success> |
8 | <div *ngIf="info" class="alert alert-info">{{ info }}</div> | 8 | <div *ngIf="info" class="alert alert-info">{{ info }}</div> |
9 | 9 | ||
10 | <div class="wrapper" *ngIf="!signupDone"> | 10 | <div class="wrapper" [hidden]="signupDone"> |
11 | <div> | 11 | <div class="register-form"> |
12 | <my-custom-stepper linear *ngIf="!signupDone"> | 12 | <my-custom-stepper linear *ngIf="!signupDone"> |
13 | <cdk-step [stepControl]="formStepUser" i18n-label label="User"> | 13 | <cdk-step [stepControl]="formStepUser" i18n-label label="User"> |
14 | <my-register-step-user (formBuilt)="onUserFormBuilt($event)"></my-register-step-user> | 14 | <my-register-step-user |
15 | [hasCodeOfConduct]="!!aboutHtml.codeOfConduct" | ||
16 | (formBuilt)="onUserFormBuilt($event)" (termsClick)="onTermsClick()" (codeOfConductClick)="onCodeOfConductClick()" | ||
17 | > | ||
18 | </my-register-step-user> | ||
15 | 19 | ||
16 | <button i18n cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid">Next</button> | 20 | <button i18n cdkStepperNext [disabled]="!formStepUser || !formStepUser.valid">Next</button> |
17 | </cdk-step> | 21 | </cdk-step> |
@@ -38,9 +42,56 @@ | |||
38 | </my-custom-stepper> | 42 | </my-custom-stepper> |
39 | </div> | 43 | </div> |
40 | 44 | ||
41 | <div> | 45 | <div class="instance-information"> |
42 | <label i18n>Features found on this instance</label> | 46 | <ngb-accordion [closeOthers]="true" #accordion="ngbAccordion"> |
43 | <my-instance-features-table></my-instance-features-table> | 47 | <ngb-panel id="instance-features" i18n-title title="Features found on this instance"> |
48 | <ng-template ngbPanelContent> | ||
49 | <my-instance-features-table></my-instance-features-table> | ||
50 | </ng-template> | ||
51 | </ngb-panel> | ||
52 | |||
53 | <ng-container *ngIf="about"> | ||
54 | <ngb-panel | ||
55 | *ngIf="aboutHtml.administrator || about.instance.maintenanceLifetime || about.instance.businessModel" | ||
56 | id="admin-sustainability" i18n-title title="Administrators & Sustainability" | ||
57 | > | ||
58 | <ng-template ngbPanelContent> | ||
59 | <div class="block"> | ||
60 | <strong i18n>Who are we?</strong> | ||
61 | <div [innerHTML]="aboutHtml.administrator"></div> | ||
62 | </div> | ||
63 | |||
64 | <div class="block"> | ||
65 | <strong i18n>How long do we plan to maintain this instance?</strong> | ||
66 | <div [innerHTML]="about.instance.maintenanceLifetime"></div> | ||
67 | </div> | ||
68 | |||
69 | <div class="block"> | ||
70 | <strong i18n>How will we pay this instance?</strong> | ||
71 | <div [innerHTML]="about.instance.businessModel"></div> | ||
72 | </div> | ||
73 | </ng-template> | ||
74 | </ngb-panel> | ||
75 | |||
76 | <ngb-panel *ngIf="aboutHtml.moderationInformation" id="moderation-information" i18n-title title="Moderation information"> | ||
77 | <ng-template ngbPanelContent> | ||
78 | <div class="block" [innerHTML]="aboutHtml.moderationInformation"></div> | ||
79 | </ng-template> | ||
80 | </ngb-panel> | ||
81 | |||
82 | <ngb-panel *ngIf="aboutHtml.codeOfConduct" id="code-of-conduct" i18n-title title="Code of conduct"> | ||
83 | <ng-template ngbPanelContent> | ||
84 | <div class="block" [innerHTML]="aboutHtml.codeOfConduct"></div> | ||
85 | </ng-template> | ||
86 | </ngb-panel> | ||
87 | |||
88 | <ngb-panel *ngIf="aboutHtml.terms" id="terms" i18n-title title="Terms"> | ||
89 | <ng-template ngbPanelContent> | ||
90 | <div class="block" [innerHTML]="aboutHtml.terms"></div> | ||
91 | </ng-template> | ||
92 | </ngb-panel> | ||
93 | </ng-container> | ||
94 | </ngb-accordion> | ||
44 | </div> | 95 | </div> |
45 | </div> | 96 | </div> |
46 | 97 | ||
diff --git a/client/src/app/+signup/+register/register.component.scss b/client/src/app/+signup/+register/register.component.scss index 9405b5293..2f62dd59d 100644 --- a/client/src/app/+signup/+register/register.component.scss +++ b/client/src/app/+signup/+register/register.component.scss | |||
@@ -1,5 +1,9 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | @import "./_bootstrap-variables"; | ||
4 | |||
5 | @import '~bootstrap/scss/functions'; | ||
6 | @import '~bootstrap/scss/variables'; | ||
3 | 7 | ||
4 | .alert { | 8 | .alert { |
5 | font-size: 15px; | 9 | font-size: 15px; |
@@ -13,7 +17,32 @@ | |||
13 | 17 | ||
14 | & > div { | 18 | & > div { |
15 | margin-bottom: 40px; | 19 | margin-bottom: 40px; |
16 | width: 450px; | 20 | |
21 | &.register-form { | ||
22 | width: 450px; | ||
23 | } | ||
24 | |||
25 | &.instance-information { | ||
26 | width: 600px; | ||
27 | margin-bottom: 40px; | ||
28 | |||
29 | .block { | ||
30 | font-size: 15px; | ||
31 | margin-bottom: 15px; | ||
32 | padding: 0 $btn-padding-x; | ||
33 | } | ||
34 | |||
35 | @media screen and (max-width: 1500px) { | ||
36 | width: 450px; | ||
37 | } | ||
38 | |||
39 | ngb-accordion ::ng-deep { | ||
40 | .btn { | ||
41 | font-weight: $font-semibold !important; | ||
42 | color: var(--mainForegroundColor) !important; | ||
43 | } | ||
44 | } | ||
45 | } | ||
17 | 46 | ||
18 | @media screen and (max-width: 500px) { | 47 | @media screen and (max-width: 500px) { |
19 | width: auto; | 48 | width: auto; |
@@ -21,12 +50,6 @@ | |||
21 | } | 50 | } |
22 | } | 51 | } |
23 | 52 | ||
24 | my-instance-features-table { | ||
25 | display: block; | ||
26 | |||
27 | margin-bottom: 40px; | ||
28 | } | ||
29 | |||
30 | .form-group-terms { | 53 | .form-group-terms { |
31 | margin: 30px 0; | 54 | margin: 30px 0; |
32 | } | 55 | } |
diff --git a/client/src/app/+signup/+register/register.component.ts b/client/src/app/+signup/+register/register.component.ts index cd6059728..d470ef4dc 100644 --- a/client/src/app/+signup/+register/register.component.ts +++ b/client/src/app/+signup/+register/register.component.ts | |||
@@ -1,21 +1,35 @@ | |||
1 | import { Component } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { AuthService, Notifier, RedirectService, ServerService } from '@app/core' | 2 | import { AuthService, Notifier, RedirectService, ServerService } from '@app/core' |
3 | import { UserService, UserValidatorsService } from '@app/shared' | 3 | import { UserService, UserValidatorsService } from '@app/shared' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | 4 | import { I18n } from '@ngx-translate/i18n-polyfill' |
5 | import { UserRegister } from '@shared/models/users/user-register.model' | 5 | import { UserRegister } from '@shared/models/users/user-register.model' |
6 | import { FormGroup } from '@angular/forms' | 6 | import { FormGroup } from '@angular/forms' |
7 | import { About } from '@shared/models/server' | ||
8 | import { InstanceService } from '@app/shared/instance/instance.service' | ||
9 | import { NgbAccordion } from '@ng-bootstrap/ng-bootstrap' | ||
7 | 10 | ||
8 | @Component({ | 11 | @Component({ |
9 | selector: 'my-register', | 12 | selector: 'my-register', |
10 | templateUrl: './register.component.html', | 13 | templateUrl: './register.component.html', |
11 | styleUrls: [ './register.component.scss' ] | 14 | styleUrls: [ './register.component.scss' ] |
12 | }) | 15 | }) |
13 | export class RegisterComponent { | 16 | export class RegisterComponent implements OnInit { |
17 | @ViewChild('accordion', { static: true }) accordion: NgbAccordion | ||
18 | |||
14 | info: string = null | 19 | info: string = null |
15 | error: string = null | 20 | error: string = null |
16 | success: string = null | 21 | success: string = null |
17 | signupDone = false | 22 | signupDone = false |
18 | 23 | ||
24 | about: About | ||
25 | aboutHtml = { | ||
26 | description: '', | ||
27 | terms: '', | ||
28 | codeOfConduct: '', | ||
29 | moderationInformation: '', | ||
30 | administrator: '' | ||
31 | } | ||
32 | |||
19 | formStepUser: FormGroup | 33 | formStepUser: FormGroup |
20 | formStepChannel: FormGroup | 34 | formStepChannel: FormGroup |
21 | 35 | ||
@@ -26,6 +40,7 @@ export class RegisterComponent { | |||
26 | private userService: UserService, | 40 | private userService: UserService, |
27 | private serverService: ServerService, | 41 | private serverService: ServerService, |
28 | private redirectService: RedirectService, | 42 | private redirectService: RedirectService, |
43 | private instanceService: InstanceService, | ||
29 | private i18n: I18n | 44 | private i18n: I18n |
30 | ) { | 45 | ) { |
31 | } | 46 | } |
@@ -34,6 +49,19 @@ export class RegisterComponent { | |||
34 | return this.serverService.getConfig().signup.requiresEmailVerification | 49 | return this.serverService.getConfig().signup.requiresEmailVerification |
35 | } | 50 | } |
36 | 51 | ||
52 | ngOnInit (): void { | ||
53 | this.instanceService.getAbout() | ||
54 | .subscribe( | ||
55 | async about => { | ||
56 | this.about = about | ||
57 | |||
58 | this.aboutHtml = await this.instanceService.buildHtml(about) | ||
59 | }, | ||
60 | |||
61 | err => this.notifier.error(err.message) | ||
62 | ) | ||
63 | } | ||
64 | |||
37 | hasSameChannelAndAccountNames () { | 65 | hasSameChannelAndAccountNames () { |
38 | return this.getUsername() === this.getChannelName() | 66 | return this.getUsername() === this.getChannelName() |
39 | } | 67 | } |
@@ -58,6 +86,14 @@ export class RegisterComponent { | |||
58 | this.formStepChannel = form | 86 | this.formStepChannel = form |
59 | } | 87 | } |
60 | 88 | ||
89 | onTermsClick () { | ||
90 | if (this.accordion) this.accordion.toggle('terms') | ||
91 | } | ||
92 | |||
93 | onCodeOfConductClick () { | ||
94 | if (this.accordion) this.accordion.toggle('code-of-conduct') | ||
95 | } | ||
96 | |||
61 | signup () { | 97 | signup () { |
62 | this.error = null | 98 | this.error = null |
63 | 99 | ||
diff --git a/client/src/app/+signup/+register/register.module.ts b/client/src/app/+signup/+register/register.module.ts index 46336cbd0..e55f83990 100644 --- a/client/src/app/+signup/+register/register.module.ts +++ b/client/src/app/+signup/+register/register.module.ts | |||
@@ -7,13 +7,15 @@ import { RegisterStepChannelComponent } from './register-step-channel.component' | |||
7 | import { RegisterStepUserComponent } from './register-step-user.component' | 7 | import { RegisterStepUserComponent } from './register-step-user.component' |
8 | import { CustomStepperComponent } from './custom-stepper.component' | 8 | import { CustomStepperComponent } from './custom-stepper.component' |
9 | import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module' | 9 | import { SignupSharedModule } from '@app/+signup/shared/signup-shared.module' |
10 | import { NgbAccordionModule } from '@ng-bootstrap/ng-bootstrap' | ||
10 | 11 | ||
11 | @NgModule({ | 12 | @NgModule({ |
12 | imports: [ | 13 | imports: [ |
13 | RegisterRoutingModule, | 14 | RegisterRoutingModule, |
14 | SharedModule, | 15 | SharedModule, |
15 | CdkStepperModule, | 16 | CdkStepperModule, |
16 | SignupSharedModule | 17 | SignupSharedModule, |
18 | NgbAccordionModule | ||
17 | ], | 19 | ], |
18 | 20 | ||
19 | declarations: [ | 21 | declarations: [ |
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index 07a576083..81b4351c5 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html | |||
@@ -54,3 +54,8 @@ | |||
54 | </div> | 54 | </div> |
55 | </ng-template> | 55 | </ng-template> |
56 | </p-toast> | 56 | </p-toast> |
57 | |||
58 | <ng-template [ngIf]="isUserLoggedIn()"> | ||
59 | <my-welcome-modal #welcomeModal></my-welcome-modal> | ||
60 | <my-instance-config-warning-modal #instanceConfigWarningModal></my-instance-config-warning-modal> | ||
61 | </ng-template> | ||
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index 64bfb9671..6b18e5feb 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser' | 2 | import { DomSanitizer, SafeHtml } from '@angular/platform-browser' |
3 | import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router' | 3 | import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router' |
4 | import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' | 4 | import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' |
5 | import { is18nPath } from '../../../shared/models/i18n' | 5 | import { is18nPath } from '../../../shared/models/i18n' |
6 | import { ScreenService } from '@app/shared/misc/screen.service' | 6 | import { ScreenService } from '@app/shared/misc/screen.service' |
7 | import { debounceTime, filter, map, pairwise, skip } from 'rxjs/operators' | 7 | import { debounceTime, filter, map, pairwise, skip, switchMap } from 'rxjs/operators' |
8 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' | 8 | import { Hotkey, HotkeysService } from 'angular2-hotkeys' |
9 | import { I18n } from '@ngx-translate/i18n-polyfill' | 9 | import { I18n } from '@ngx-translate/i18n-polyfill' |
10 | import { fromEvent } from 'rxjs' | 10 | import { fromEvent } from 'rxjs' |
@@ -13,6 +13,11 @@ import { PluginService } from '@app/core/plugins/plugin.service' | |||
13 | import { HooksService } from '@app/core/plugins/hooks.service' | 13 | import { HooksService } from '@app/core/plugins/hooks.service' |
14 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 14 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
15 | import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' | 15 | import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' |
16 | import { WelcomeModalComponent } from '@app/modal/welcome-modal.component' | ||
17 | import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' | ||
18 | import { UserRole } from '@shared/models' | ||
19 | import { User } from '@app/shared' | ||
20 | import { InstanceService } from '@app/shared/instance/instance.service' | ||
16 | 21 | ||
17 | @Component({ | 22 | @Component({ |
18 | selector: 'my-app', | 23 | selector: 'my-app', |
@@ -20,6 +25,9 @@ import { POP_STATE_MODAL_DISMISS } from '@app/shared/misc/constants' | |||
20 | styleUrls: [ './app.component.scss' ] | 25 | styleUrls: [ './app.component.scss' ] |
21 | }) | 26 | }) |
22 | export class AppComponent implements OnInit { | 27 | export class AppComponent implements OnInit { |
28 | @ViewChild('welcomeModal', { static: false }) welcomeModal: WelcomeModalComponent | ||
29 | @ViewChild('instanceConfigWarningModal', { static: false }) instanceConfigWarningModal: InstanceConfigWarningModalComponent | ||
30 | |||
23 | isMenuDisplayed = true | 31 | isMenuDisplayed = true |
24 | isMenuChangedByUser = false | 32 | isMenuChangedByUser = false |
25 | 33 | ||
@@ -32,6 +40,7 @@ export class AppComponent implements OnInit { | |||
32 | private authService: AuthService, | 40 | private authService: AuthService, |
33 | private serverService: ServerService, | 41 | private serverService: ServerService, |
34 | private pluginService: PluginService, | 42 | private pluginService: PluginService, |
43 | private instanceService: InstanceService, | ||
35 | private domSanitizer: DomSanitizer, | 44 | private domSanitizer: DomSanitizer, |
36 | private redirectService: RedirectService, | 45 | private redirectService: RedirectService, |
37 | private screenService: ScreenService, | 46 | private screenService: ScreenService, |
@@ -96,6 +105,8 @@ export class AppComponent implements OnInit { | |||
96 | .subscribe(() => this.onResize()) | 105 | .subscribe(() => this.onResize()) |
97 | 106 | ||
98 | this.location.onPopState(() => this.modalService.dismissAll(POP_STATE_MODAL_DISMISS)) | 107 | this.location.onPopState(() => this.modalService.dismissAll(POP_STATE_MODAL_DISMISS)) |
108 | |||
109 | this.openModalsIfNeeded() | ||
99 | } | 110 | } |
100 | 111 | ||
101 | isUserLoggedIn () { | 112 | isUserLoggedIn () { |
@@ -220,32 +231,66 @@ export class AppComponent implements OnInit { | |||
220 | this.hooks.runAction('action:application.init', 'common') | 231 | this.hooks.runAction('action:application.init', 'common') |
221 | } | 232 | } |
222 | 233 | ||
234 | private async openModalsIfNeeded () { | ||
235 | this.serverService.configLoaded | ||
236 | .pipe( | ||
237 | switchMap(() => this.authService.userInformationLoaded), | ||
238 | map(() => this.authService.getUser()), | ||
239 | filter(user => user.role === UserRole.ADMINISTRATOR) | ||
240 | ).subscribe(user => setTimeout(() => this.openAdminModals(user))) // setTimeout because of ngIf in template | ||
241 | } | ||
242 | |||
243 | private async openAdminModals (user: User) { | ||
244 | if (user.noWelcomeModal !== true) return this.welcomeModal.show() | ||
245 | |||
246 | const config = this.serverService.getConfig() | ||
247 | if (user.noInstanceConfigWarningModal === true || !config.signup.allowed) return | ||
248 | |||
249 | this.instanceService.getAbout() | ||
250 | .subscribe(about => { | ||
251 | if ( | ||
252 | config.instance.name.toLowerCase() === 'peertube' || | ||
253 | !about.instance.terms || | ||
254 | !about.instance.administrator || | ||
255 | !about.instance.maintenanceLifetime | ||
256 | ) { | ||
257 | this.instanceConfigWarningModal.show(about) | ||
258 | } | ||
259 | }) | ||
260 | } | ||
261 | |||
223 | private initHotkeys () { | 262 | private initHotkeys () { |
224 | this.hotkeysService.add([ | 263 | this.hotkeysService.add([ |
225 | new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => { | 264 | new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => { |
226 | document.getElementById('search-video').focus() | 265 | document.getElementById('search-video').focus() |
227 | return false | 266 | return false |
228 | }, undefined, this.i18n('Focus the search bar')), | 267 | }, undefined, this.i18n('Focus the search bar')), |
268 | |||
229 | new Hotkey('b', (event: KeyboardEvent): boolean => { | 269 | new Hotkey('b', (event: KeyboardEvent): boolean => { |
230 | this.toggleMenu() | 270 | this.toggleMenu() |
231 | return false | 271 | return false |
232 | }, undefined, this.i18n('Toggle the left menu')), | 272 | }, undefined, this.i18n('Toggle the left menu')), |
273 | |||
233 | new Hotkey('g o', (event: KeyboardEvent): boolean => { | 274 | new Hotkey('g o', (event: KeyboardEvent): boolean => { |
234 | this.router.navigate([ '/videos/overview' ]) | 275 | this.router.navigate([ '/videos/overview' ]) |
235 | return false | 276 | return false |
236 | }, undefined, this.i18n('Go to the discover videos page')), | 277 | }, undefined, this.i18n('Go to the discover videos page')), |
278 | |||
237 | new Hotkey('g t', (event: KeyboardEvent): boolean => { | 279 | new Hotkey('g t', (event: KeyboardEvent): boolean => { |
238 | this.router.navigate([ '/videos/trending' ]) | 280 | this.router.navigate([ '/videos/trending' ]) |
239 | return false | 281 | return false |
240 | }, undefined, this.i18n('Go to the trending videos page')), | 282 | }, undefined, this.i18n('Go to the trending videos page')), |
283 | |||
241 | new Hotkey('g r', (event: KeyboardEvent): boolean => { | 284 | new Hotkey('g r', (event: KeyboardEvent): boolean => { |
242 | this.router.navigate([ '/videos/recently-added' ]) | 285 | this.router.navigate([ '/videos/recently-added' ]) |
243 | return false | 286 | return false |
244 | }, undefined, this.i18n('Go to the recently added videos page')), | 287 | }, undefined, this.i18n('Go to the recently added videos page')), |
288 | |||
245 | new Hotkey('g l', (event: KeyboardEvent): boolean => { | 289 | new Hotkey('g l', (event: KeyboardEvent): boolean => { |
246 | this.router.navigate([ '/videos/local' ]) | 290 | this.router.navigate([ '/videos/local' ]) |
247 | return false | 291 | return false |
248 | }, undefined, this.i18n('Go to the local videos page')), | 292 | }, undefined, this.i18n('Go to the local videos page')), |
293 | |||
249 | new Hotkey('g u', (event: KeyboardEvent): boolean => { | 294 | new Hotkey('g u', (event: KeyboardEvent): boolean => { |
250 | this.router.navigate([ '/videos/upload' ]) | 295 | this.router.navigate([ '/videos/upload' ]) |
251 | return false | 296 | return false |
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 1e2936a37..a3ea33ca9 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts | |||
@@ -18,6 +18,8 @@ import { VideosModule } from './videos' | |||
18 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' | 18 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' |
19 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' | 19 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' |
20 | import { SearchModule } from '@app/search' | 20 | import { SearchModule } from '@app/search' |
21 | import { WelcomeModalComponent } from '@app/modal/welcome-modal.component' | ||
22 | import { InstanceConfigWarningModalComponent } from '@app/modal/instance-config-warning-modal.component' | ||
21 | 23 | ||
22 | export function metaFactory (serverService: ServerService): MetaLoader { | 24 | export function metaFactory (serverService: ServerService): MetaLoader { |
23 | return new MetaStaticLoader({ | 25 | return new MetaStaticLoader({ |
@@ -39,7 +41,10 @@ export function metaFactory (serverService: ServerService): MetaLoader { | |||
39 | MenuComponent, | 41 | MenuComponent, |
40 | LanguageChooserComponent, | 42 | LanguageChooserComponent, |
41 | AvatarNotificationComponent, | 43 | AvatarNotificationComponent, |
42 | HeaderComponent | 44 | HeaderComponent, |
45 | |||
46 | WelcomeModalComponent, | ||
47 | InstanceConfigWarningModalComponent | ||
43 | ], | 48 | ], |
44 | imports: [ | 49 | imports: [ |
45 | BrowserModule, | 50 | BrowserModule, |
diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html index 4efe3fb22..683355960 100644 --- a/client/src/app/login/login.component.html +++ b/client/src/app/login/login.component.html | |||
@@ -23,10 +23,11 @@ | |||
23 | or create an account on another instance | 23 | or create an account on another instance |
24 | </a> | 24 | </a> |
25 | 25 | ||
26 | <my-help | 26 | <my-help *ngIf="signupAllowed === false"> |
27 | *ngIf="signupAllowed === false" helpType="custom" i18n-customHtml | 27 | <ng-template ptTemplate="customHtml"> |
28 | customHtml="User registration is not allowed on this instance, but you can register on many others!" | 28 | <ng-container i18n>User registration is not allowed on this instance, but you can register on many others!</ng-container> |
29 | ></my-help> | 29 | </ng-template> |
30 | </my-help> | ||
30 | </div> | 31 | </div> |
31 | 32 | ||
32 | <div *ngIf="formErrors.username" class="form-error"> | 33 | <div *ngIf="formErrors.username" class="form-error"> |
diff --git a/client/src/app/modal/instance-config-warning-modal.component.html b/client/src/app/modal/instance-config-warning-modal.component.html new file mode 100644 index 000000000..64f14e69b --- /dev/null +++ b/client/src/app/modal/instance-config-warning-modal.component.html | |||
@@ -0,0 +1,45 @@ | |||
1 | <ng-template #modal let-hide="close"> | ||
2 | <div class="modal-header"> | ||
3 | <h4 i18n class="modal-title">Configuration warning!</h4> | ||
4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | ||
5 | </div> | ||
6 | |||
7 | <div class="modal-body"> | ||
8 | |||
9 | <p i18n>Hello dear administrator. You enabled user registration on your instance but you did not configure the following fields:</p> | ||
10 | |||
11 | <ul> | ||
12 | <li i18n *ngIf="about.instance.name.toLowerCase() === 'peertube'">Instance name</li> | ||
13 | <li i18n *ngIf="isDefaultShortDescription(about.instance.shortDescription)">Instance short description</li> | ||
14 | |||
15 | <li i18n *ngIf="!about.instance.administrator">Who you are</li> | ||
16 | <li i18n *ngIf="!about.instance.maintenanceLifetime">How long you plan to maintain your instance</li> | ||
17 | <li i18n *ngIf="!about.instance.businessModel">How you plan to pay your instance</li> | ||
18 | |||
19 | <li i18n *ngIf="!about.instance.moderationInformation">How you will moderate your instance</li> | ||
20 | <li i18n *ngIf="!about.instance.terms">Instance terms</li> | ||
21 | </ul> | ||
22 | |||
23 | <p> | ||
24 | Please consider to configure these fields to help people to choose <strong>the appropriate instance</strong>. | ||
25 | Without them, your instance may not be referenced on <a target="_blank" rel="noopener noreferrer" href="https://joinpeertube.org">JoinPeerTube website</a>. | ||
26 | </p> | ||
27 | |||
28 | <div class="configure-instance"> | ||
29 | <a i18n href="/admin/config/edit-custom" target="_blank" rel="noopener noreferrer">Configure these fields</a> | ||
30 | </div> | ||
31 | |||
32 | </div> | ||
33 | |||
34 | <div class="modal-footer inputs"> | ||
35 | <my-peertube-checkbox | ||
36 | inputName="stopDisplayModal" [(ngModel)]="stopDisplayModal" | ||
37 | i18n-labelText labelText="Don't show me this warning anymore" | ||
38 | > | ||
39 | |||
40 | </my-peertube-checkbox> | ||
41 | |||
42 | <span i18n class="action-button action-button-cancel" (click)="hide()">Close</span> | ||
43 | </div> | ||
44 | |||
45 | </ng-template> | ||
diff --git a/client/src/app/modal/instance-config-warning-modal.component.scss b/client/src/app/modal/instance-config-warning-modal.component.scss new file mode 100644 index 000000000..ff62a1b9c --- /dev/null +++ b/client/src/app/modal/instance-config-warning-modal.component.scss | |||
@@ -0,0 +1,22 @@ | |||
1 | @import '_mixins'; | ||
2 | @import '_variables'; | ||
3 | |||
4 | .action-button-cancel { | ||
5 | margin-right: 0 !important; | ||
6 | } | ||
7 | |||
8 | .modal-body { | ||
9 | font-size: 15px; | ||
10 | } | ||
11 | |||
12 | li { | ||
13 | margin-bottom: 10px; | ||
14 | } | ||
15 | |||
16 | .configure-instance { | ||
17 | text-align: center; | ||
18 | font-weight: 600; | ||
19 | font-size: 18px; | ||
20 | margin-top: 40px; | ||
21 | margin-bottom: 10px; | ||
22 | } | ||
diff --git a/client/src/app/modal/instance-config-warning-modal.component.ts b/client/src/app/modal/instance-config-warning-modal.component.ts new file mode 100644 index 000000000..742a7dd41 --- /dev/null +++ b/client/src/app/modal/instance-config-warning-modal.component.ts | |||
@@ -0,0 +1,47 @@ | |||
1 | import { Component, ElementRef, ViewChild } from '@angular/core' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
4 | import { About } from '@shared/models/server' | ||
5 | import { UserService } from '@app/shared' | ||
6 | |||
7 | @Component({ | ||
8 | selector: 'my-instance-config-warning-modal', | ||
9 | templateUrl: './instance-config-warning-modal.component.html', | ||
10 | styleUrls: [ './instance-config-warning-modal.component.scss' ] | ||
11 | }) | ||
12 | export class InstanceConfigWarningModalComponent { | ||
13 | @ViewChild('modal', { static: true }) modal: ElementRef | ||
14 | |||
15 | stopDisplayModal = false | ||
16 | about: About | ||
17 | |||
18 | constructor ( | ||
19 | private userService: UserService, | ||
20 | private modalService: NgbModal, | ||
21 | private notifier: Notifier | ||
22 | ) { } | ||
23 | |||
24 | show (about: About) { | ||
25 | this.about = about | ||
26 | |||
27 | const ref = this.modalService.open(this.modal) | ||
28 | |||
29 | ref.result.finally(() => { | ||
30 | if (this.stopDisplayModal === true) this.doNotOpenAgain() | ||
31 | }) | ||
32 | } | ||
33 | |||
34 | isDefaultShortDescription (description: string) { | ||
35 | return description === 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly ' + | ||
36 | 'in the web browser with WebTorrent and Angular.' | ||
37 | } | ||
38 | |||
39 | private doNotOpenAgain () { | ||
40 | this.userService.updateMyProfile({ noInstanceConfigWarningModal: true }) | ||
41 | .subscribe( | ||
42 | () => console.log('We will not open the instance config warning modal again.'), | ||
43 | |||
44 | err => this.notifier.error(err.message) | ||
45 | ) | ||
46 | } | ||
47 | } | ||
diff --git a/client/src/app/modal/welcome-modal.component.html b/client/src/app/modal/welcome-modal.component.html new file mode 100644 index 000000000..09ff2163b --- /dev/null +++ b/client/src/app/modal/welcome-modal.component.html | |||
@@ -0,0 +1,67 @@ | |||
1 | <ng-template #modal let-hide="close"> | ||
2 | <div class="modal-header"> | ||
3 | <h4 i18n class="modal-title">Welcome on PeerTube dear administrator!</h4> | ||
4 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | ||
5 | </div> | ||
6 | |||
7 | <div class="modal-body"> | ||
8 | |||
9 | <div class="block-documentation"> | ||
10 | <div i18n class="subtitle">Documentation</div> | ||
11 | |||
12 | <div class="columns"> | ||
13 | <a class="link-block" href="https://docs.joinpeertube.org/#/maintain-tools" target="_blank" rel="noopener noreferrer"> | ||
14 | <a class="link-title" href="https://docs.joinpeertube.org/#/maintain-tools" target="_blank" rel="noopener noreferrer">CLI</a> | ||
15 | |||
16 | <div>Upload or import videos, parse logs, prune storage directories, reset user password...</div> | ||
17 | </a> | ||
18 | |||
19 | <a class="link-block" href="https://docs.joinpeertube.org/#/admin-following-instances" target="_blank" rel="noopener noreferrer"> | ||
20 | <a class="link-title" href="https://docs.joinpeertube.org/#/admin-following-instances" target="_blank" rel="noopener noreferrer">Administer</a> | ||
21 | |||
22 | <div>Managing users, following other instances, dealing with spammers...</div> | ||
23 | </a> | ||
24 | |||
25 | <a class="link-block" href="https://docs.joinpeertube.org/#/use-setup-account" target="_blank" rel="noopener noreferrer"> | ||
26 | <a class="link-title" href="https://docs.joinpeertube.org/#/use-setup-account" target="_blank" rel="noopener noreferrer">Use</a> | ||
27 | |||
28 | <div>Setup your account, managing video playlists, discover third-party applications...</div> | ||
29 | </a> | ||
30 | </div> | ||
31 | </div> | ||
32 | |||
33 | <div class="block-configuration"> | ||
34 | <div i18n class="subtitle">It's time to configure your instance!</div> | ||
35 | |||
36 | <p i18n> | ||
37 | Choosing your <strong>instance name</strong>, <strong>setting up a description</strong>, specifying <strong>who you are</strong>, | ||
38 | why <strong>you created your instance</strong> and <strong>how long</strong> you plan to <strong>maintain your it</strong> | ||
39 | is very important for visitors to understand on what type of instance they are. | ||
40 | </p> | ||
41 | |||
42 | <p i18n> | ||
43 | If you want to open registrations, please decide what are <strong>your moderation rules</strong>, fill your <strong>instance terms</strong> | ||
44 | and specify the categories and languages you speak. This way, you will help users to register on <strong>the appropriate</strong> PeerTube instance. | ||
45 | </p> | ||
46 | |||
47 | <div class="configure-instance"> | ||
48 | <a i18n href="/admin/config/edit-custom" target="_blank" rel="noopener noreferrer">Configure your instance</a> | ||
49 | </div> | ||
50 | </div> | ||
51 | |||
52 | <div class="block-links"> | ||
53 | <div i18n class="subtitle">Useful links</div> | ||
54 | |||
55 | <ul> | ||
56 | <li>Official PeerTube website (news, support, contribute...): <a href="https://joinpeertube.org" target="_blank" rel="noopener noreferrer">https://joinpeertube.org</a></li> | ||
57 | |||
58 | <li>Put your instance on the public PeerTube index: <a href="https://instances.joinpeertube.org/instances">https://instances.joinpeertube.org/instances</a></li> | ||
59 | </ul> | ||
60 | </div> | ||
61 | </div> | ||
62 | |||
63 | <div class="modal-footer inputs"> | ||
64 | <span i18n class="action-button action-button-submit" (click)="hide()">Understood!</span> | ||
65 | </div> | ||
66 | |||
67 | </ng-template> | ||
diff --git a/client/src/app/modal/welcome-modal.component.scss b/client/src/app/modal/welcome-modal.component.scss new file mode 100644 index 000000000..8bb6973f4 --- /dev/null +++ b/client/src/app/modal/welcome-modal.component.scss | |||
@@ -0,0 +1,56 @@ | |||
1 | @import '_mixins'; | ||
2 | @import '_variables'; | ||
3 | |||
4 | .modal-body { | ||
5 | font-size: 15px; | ||
6 | } | ||
7 | |||
8 | .subtitle { | ||
9 | font-weight: $font-semibold; | ||
10 | margin-bottom: 10px; | ||
11 | font-size: 16px; | ||
12 | } | ||
13 | |||
14 | .block-documentation .subtitle { | ||
15 | margin-bottom: 20px; | ||
16 | } | ||
17 | |||
18 | .block-configuration, | ||
19 | .block-instance { | ||
20 | margin-top: 30px; | ||
21 | } | ||
22 | |||
23 | li { | ||
24 | margin-bottom: 10px; | ||
25 | } | ||
26 | |||
27 | .configure-instance { | ||
28 | text-align: center; | ||
29 | font-weight: 600; | ||
30 | font-size: 18px; | ||
31 | margin: 20px 0 40px 0; | ||
32 | } | ||
33 | |||
34 | .columns { | ||
35 | display: flex; | ||
36 | |||
37 | .link-block { | ||
38 | @include disable-default-a-behaviour; | ||
39 | |||
40 | color: var(--mainForegroundColor); | ||
41 | padding: 10px; | ||
42 | transition: background-color 0.2s ease-in; | ||
43 | |||
44 | &:hover { | ||
45 | background-color: rgba(0, 0, 0, 0.05); | ||
46 | } | ||
47 | |||
48 | .link-title { | ||
49 | font-size: 16px; | ||
50 | font-weight: $font-semibold; | ||
51 | display: flex; | ||
52 | justify-content: center; | ||
53 | margin-bottom: 5px; | ||
54 | } | ||
55 | } | ||
56 | } | ||
diff --git a/client/src/app/modal/welcome-modal.component.ts b/client/src/app/modal/welcome-modal.component.ts new file mode 100644 index 000000000..05412a4cd --- /dev/null +++ b/client/src/app/modal/welcome-modal.component.ts | |||
@@ -0,0 +1,38 @@ | |||
1 | import { Component, ElementRef, ViewChild } from '@angular/core' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | ||
4 | import { UserService } from '@app/shared' | ||
5 | |||
6 | @Component({ | ||
7 | selector: 'my-welcome-modal', | ||
8 | templateUrl: './welcome-modal.component.html', | ||
9 | styleUrls: [ './welcome-modal.component.scss' ] | ||
10 | }) | ||
11 | export class WelcomeModalComponent { | ||
12 | @ViewChild('modal', { static: true }) modal: ElementRef | ||
13 | |||
14 | constructor ( | ||
15 | private userService: UserService, | ||
16 | private modalService: NgbModal, | ||
17 | private notifier: Notifier | ||
18 | ) { } | ||
19 | |||
20 | show () { | ||
21 | const ref = this.modalService.open(this.modal,{ | ||
22 | backdrop: 'static', | ||
23 | keyboard: false, | ||
24 | size: 'lg' | ||
25 | }) | ||
26 | |||
27 | ref.result.finally(() => this.doNotOpenAgain()) | ||
28 | } | ||
29 | |||
30 | private doNotOpenAgain () { | ||
31 | this.userService.updateMyProfile({ noWelcomeModal: true }) | ||
32 | .subscribe( | ||
33 | () => console.log('We will not open the welcome modal again.'), | ||
34 | |||
35 | err => this.notifier.error(err.message) | ||
36 | ) | ||
37 | } | ||
38 | } | ||
diff --git a/client/src/app/shared/angular/peertube-template.directive.ts b/client/src/app/shared/angular/peertube-template.directive.ts index a514b6057..e04c25d9a 100644 --- a/client/src/app/shared/angular/peertube-template.directive.ts +++ b/client/src/app/shared/angular/peertube-template.directive.ts | |||
@@ -3,8 +3,8 @@ import { Directive, Input, TemplateRef } from '@angular/core' | |||
3 | @Directive({ | 3 | @Directive({ |
4 | selector: '[ptTemplate]' | 4 | selector: '[ptTemplate]' |
5 | }) | 5 | }) |
6 | export class PeerTubeTemplateDirective { | 6 | export class PeerTubeTemplateDirective <T extends string> { |
7 | @Input('ptTemplate') name: string | 7 | @Input('ptTemplate') name: T |
8 | 8 | ||
9 | constructor (public template: TemplateRef<any>) { | 9 | constructor (public template: TemplateRef<any>) { |
10 | // empty | 10 | // empty |
diff --git a/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts b/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts index 882e39453..767e3f026 100644 --- a/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts +++ b/client/src/app/shared/forms/form-validators/custom-config-validators.service.ts | |||
@@ -13,6 +13,7 @@ export class CustomConfigValidatorsService { | |||
13 | readonly SIGNUP_LIMIT: BuildFormValidator | 13 | readonly SIGNUP_LIMIT: BuildFormValidator |
14 | readonly ADMIN_EMAIL: BuildFormValidator | 14 | readonly ADMIN_EMAIL: BuildFormValidator |
15 | readonly TRANSCODING_THREADS: BuildFormValidator | 15 | readonly TRANSCODING_THREADS: BuildFormValidator |
16 | readonly INDEX_URL: BuildFormValidator | ||
16 | 17 | ||
17 | constructor (private i18n: I18n) { | 18 | constructor (private i18n: I18n) { |
18 | this.INSTANCE_NAME = { | 19 | this.INSTANCE_NAME = { |
@@ -78,5 +79,13 @@ export class CustomConfigValidatorsService { | |||
78 | 'min': this.i18n('Transcoding threads must be greater or equal to 0.') | 79 | 'min': this.i18n('Transcoding threads must be greater or equal to 0.') |
79 | } | 80 | } |
80 | } | 81 | } |
82 | |||
83 | this.INDEX_URL = { | ||
84 | VALIDATORS: [ Validators.required, Validators.pattern(/^https:\/\//) ], | ||
85 | MESSAGES: { | ||
86 | 'required': this.i18n('Index URL is required.'), | ||
87 | 'pattern': this.i18n('Index URL should be a URL') | ||
88 | } | ||
89 | } | ||
81 | } | 90 | } |
82 | } | 91 | } |
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.html b/client/src/app/shared/forms/peertube-checkbox.component.html index 571a1a673..f1e3bf0bf 100644 --- a/client/src/app/shared/forms/peertube-checkbox.component.html +++ b/client/src/app/shared/forms/peertube-checkbox.component.html | |||
@@ -3,8 +3,15 @@ | |||
3 | <input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="disabled" /> | 3 | <input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="disabled" /> |
4 | <span role="checkbox" [attr.aria-checked]="checked"></span> | 4 | <span role="checkbox" [attr.aria-checked]="checked"></span> |
5 | <span *ngIf="labelText">{{ labelText }}</span> | 5 | <span *ngIf="labelText">{{ labelText }}</span> |
6 | <span *ngIf="labelHtml" [innerHTML]="labelHtml"></span> | 6 | |
7 | <span *ngIf="labelTemplate"> | ||
8 | <ng-container *ngTemplateOutlet="labelTemplate"></ng-container> | ||
9 | </span> | ||
7 | </label> | 10 | </label> |
8 | 11 | ||
9 | <my-help *ngIf="helpHtml" [tooltipPlacement]="helpPlacement" helpType="custom" i18n-customHtml [customHtml]="helpHtml"></my-help> | 12 | <my-help *ngIf="helpTemplate" [tooltipPlacement]="helpPlacement" helpType="custom"> |
13 | <ng-template ptTemplate="customHtml"> | ||
14 | <ng-template *ngTemplateOutlet="helpTemplate"></ng-template> | ||
15 | </ng-template> | ||
16 | </my-help> | ||
10 | </div> | 17 | </div> |
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.scss b/client/src/app/shared/forms/peertube-checkbox.component.scss index 84ea788af..51f98b0bc 100644 --- a/client/src/app/shared/forms/peertube-checkbox.component.scss +++ b/client/src/app/shared/forms/peertube-checkbox.component.scss | |||
@@ -7,7 +7,7 @@ | |||
7 | .form-group-checkbox { | 7 | .form-group-checkbox { |
8 | display: flex; | 8 | display: flex; |
9 | 9 | ||
10 | span { | 10 | .label-text { |
11 | font-weight: $font-regular; | 11 | font-weight: $font-regular; |
12 | margin: 0; | 12 | margin: 0; |
13 | } | 13 | } |
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.ts b/client/src/app/shared/forms/peertube-checkbox.component.ts index a4b72aa37..3b8f39ed0 100644 --- a/client/src/app/shared/forms/peertube-checkbox.component.ts +++ b/client/src/app/shared/forms/peertube-checkbox.component.ts | |||
@@ -1,5 +1,6 @@ | |||
1 | import { ChangeDetectorRef, Component, forwardRef, Input, OnChanges, SimpleChanges } from '@angular/core' | 1 | import { AfterContentInit, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, QueryList, TemplateRef } from '@angular/core' |
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | 2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' |
3 | import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' | ||
3 | 4 | ||
4 | @Component({ | 5 | @Component({ |
5 | selector: 'my-peertube-checkbox', | 6 | selector: 'my-peertube-checkbox', |
@@ -13,20 +14,35 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | |||
13 | } | 14 | } |
14 | ] | 15 | ] |
15 | }) | 16 | }) |
16 | export class PeertubeCheckboxComponent implements ControlValueAccessor { | 17 | export class PeertubeCheckboxComponent implements ControlValueAccessor, AfterContentInit { |
17 | @Input() checked = false | 18 | @Input() checked = false |
18 | @Input() inputName: string | 19 | @Input() inputName: string |
19 | @Input() labelText: string | 20 | @Input() labelText: string |
20 | @Input() labelHtml: string | ||
21 | @Input() helpHtml: string | ||
22 | @Input() helpPlacement = 'top' | 21 | @Input() helpPlacement = 'top' |
23 | @Input() disabled = false | 22 | @Input() disabled = false |
24 | 23 | ||
24 | @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective<'label' | 'help'>> | ||
25 | |||
25 | // FIXME: https://github.com/angular/angular/issues/10816#issuecomment-307567836 | 26 | // FIXME: https://github.com/angular/angular/issues/10816#issuecomment-307567836 |
26 | @Input() onPushWorkaround = false | 27 | @Input() onPushWorkaround = false |
27 | 28 | ||
29 | labelTemplate: TemplateRef<any> | ||
30 | helpTemplate: TemplateRef<any> | ||
31 | |||
28 | constructor (private cdr: ChangeDetectorRef) { } | 32 | constructor (private cdr: ChangeDetectorRef) { } |
29 | 33 | ||
34 | ngAfterContentInit () { | ||
35 | { | ||
36 | const t = this.templates.find(t => t.name === 'label') | ||
37 | if (t) this.labelTemplate = t.template | ||
38 | } | ||
39 | |||
40 | { | ||
41 | const t = this.templates.find(t => t.name === 'help') | ||
42 | if (t) this.helpTemplate = t.template | ||
43 | } | ||
44 | } | ||
45 | |||
30 | propagateChange = (_: any) => { /* empty */ } | 46 | propagateChange = (_: any) => { /* empty */ } |
31 | 47 | ||
32 | writeValue (checked: boolean) { | 48 | writeValue (checked: boolean) { |
diff --git a/client/src/app/shared/instance/feature-boolean.component.html b/client/src/app/shared/instance/feature-boolean.component.html new file mode 100644 index 000000000..ac208fc13 --- /dev/null +++ b/client/src/app/shared/instance/feature-boolean.component.html | |||
@@ -0,0 +1,3 @@ | |||
1 | <span *ngIf="value === true" class="glyphicon glyphicon-ok"></span> | ||
2 | <span *ngIf="value === false" class="glyphicon glyphicon-remove"></span> | ||
3 | |||
diff --git a/client/src/app/shared/instance/feature-boolean.component.scss b/client/src/app/shared/instance/feature-boolean.component.scss new file mode 100644 index 000000000..56d08af06 --- /dev/null +++ b/client/src/app/shared/instance/feature-boolean.component.scss | |||
@@ -0,0 +1,10 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .glyphicon-ok { | ||
5 | color: $green; | ||
6 | } | ||
7 | |||
8 | .glyphicon-remove { | ||
9 | color: $red; | ||
10 | } | ||
diff --git a/client/src/app/shared/instance/feature-boolean.component.ts b/client/src/app/shared/instance/feature-boolean.component.ts new file mode 100644 index 000000000..d02d513d6 --- /dev/null +++ b/client/src/app/shared/instance/feature-boolean.component.ts | |||
@@ -0,0 +1,10 @@ | |||
1 | import { Component, Input } from '@angular/core' | ||
2 | |||
3 | @Component({ | ||
4 | selector: 'my-feature-boolean', | ||
5 | templateUrl: './feature-boolean.component.html', | ||
6 | styleUrls: [ './feature-boolean.component.scss' ] | ||
7 | }) | ||
8 | export class FeatureBooleanComponent { | ||
9 | @Input() value: boolean | ||
10 | } | ||
diff --git a/client/src/app/shared/instance/instance-features-table.component.html b/client/src/app/shared/instance/instance-features-table.component.html index 2987bd00e..d1cb8fcbe 100644 --- a/client/src/app/shared/instance/instance-features-table.component.html +++ b/client/src/app/shared/instance/instance-features-table.component.html | |||
@@ -1,28 +1,53 @@ | |||
1 | <div class="feature-table"> | 1 | <div class="feature-table"> |
2 | 2 | ||
3 | <table class="table"> | 3 | <table class="table" *ngIf="config"> |
4 | <tr> | 4 | <tr> |
5 | <td i18n class="label">Default NSFW/sensitive videos policy (can be redefined by the users)</td> | 5 | <td i18n class="label"> |
6 | <div>Default NSFW/sensitive videos policy</div> | ||
7 | <div class="more-info">can be redefined by the users</div> | ||
8 | </td> | ||
6 | 9 | ||
7 | <td class="value">{{ buildNSFWLabel() }}</td> | 10 | <td class="value">{{ buildNSFWLabel() }}</td> |
8 | </tr> | 11 | </tr> |
9 | 12 | ||
10 | <tr *ngFor="let feature of features"> | 13 | <tr> |
11 | <td class="label">{{ feature.label }}</td> | 14 | <td i18n class="label">User registration allowed</td> |
12 | <td> | 15 | <td> |
13 | <span *ngIf="feature.value === true" class="glyphicon glyphicon-ok"></span> | 16 | <my-feature-boolean [value]="config.signup.allowed"></my-feature-boolean> |
14 | <span *ngIf="feature.value === false" class="glyphicon glyphicon-remove"></span> | ||
15 | </td> | 17 | </td> |
16 | </tr> | 18 | </tr> |
17 | 19 | ||
18 | <tr> | 20 | <tr> |
19 | <td i18n class="label">Video quota</td> | 21 | <td i18n class="label" colspan="2">Video uploads</td> |
22 | </tr> | ||
23 | |||
24 | <tr> | ||
25 | <td i18n class="sub-label">Transcoding in multiple resolutions</td> | ||
26 | <td> | ||
27 | <my-feature-boolean [value]="config.transcoding.enabledResolutions.length !== 0"></my-feature-boolean> | ||
28 | </td> | ||
29 | </tr> | ||
30 | |||
31 | <tr> | ||
32 | <td i18n class="sub-label">Video uploads</td> | ||
33 | <td> | ||
34 | <span *ngIf="config.autoBlacklist.videos.ofUsers.enabled">Requires manual validation by moderators</span> | ||
35 | <span *ngIf="!config.autoBlacklist.videos.ofUsers.enabled">Automatically published</span> | ||
36 | </td> | ||
37 | </tr> | ||
38 | |||
39 | <tr> | ||
40 | <td i18n class="sub-label">Video quota</td> | ||
20 | 41 | ||
21 | <td class="value"> | 42 | <td class="value"> |
22 | <ng-container *ngIf="initialUserVideoQuota !== -1"> | 43 | <ng-container *ngIf="initialUserVideoQuota !== -1"> |
23 | {{ initialUserVideoQuota | bytes: 0 }} <ng-container *ngIf="dailyUserVideoQuota !== -1">({{ dailyUserVideoQuota | bytes: 0 }} per day)</ng-container> | 44 | {{ initialUserVideoQuota | bytes: 0 }} <ng-container *ngIf="dailyUserVideoQuota !== -1">({{ dailyUserVideoQuota | bytes: 0 }} per day)</ng-container> |
24 | 45 | ||
25 | <my-help tooltipPlacement="auto" helpType="custom" [customHtml]="quotaHelpIndication"></my-help> | 46 | <my-help tooltipPlacement="auto" helpType="custom"> |
47 | <ng-template ptTemplate="customHtml"> | ||
48 | <div [innerHTML]="quotaHelpIndication"></div> | ||
49 | </ng-template> | ||
50 | </my-help> | ||
26 | </ng-container> | 51 | </ng-container> |
27 | 52 | ||
28 | <ng-container i18n *ngIf="initialUserVideoQuota === -1"> | 53 | <ng-container i18n *ngIf="initialUserVideoQuota === -1"> |
@@ -30,5 +55,35 @@ | |||
30 | </ng-container> | 55 | </ng-container> |
31 | </td> | 56 | </td> |
32 | </tr> | 57 | </tr> |
58 | |||
59 | <tr> | ||
60 | <td i18n class="label" colspan="2">Import</td> | ||
61 | </tr> | ||
62 | |||
63 | <tr> | ||
64 | <td i18n class="sub-label">HTTP import (YouTube, Vimeo, direct URL...)</td> | ||
65 | <td> | ||
66 | <my-feature-boolean [value]="config.import.videos.http.enabled"></my-feature-boolean> | ||
67 | </td> | ||
68 | </tr> | ||
69 | |||
70 | <tr> | ||
71 | <td i18n class="sub-label">Torrent import</td> | ||
72 | <td> | ||
73 | <my-feature-boolean [value]="config.import.videos.torrent.enabled"></my-feature-boolean> | ||
74 | </td> | ||
75 | </tr> | ||
76 | |||
77 | |||
78 | <tr> | ||
79 | <td i18n class="label" colspan="2">Player</td> | ||
80 | </tr> | ||
81 | |||
82 | <tr> | ||
83 | <td i18n class="sub-label">P2P enabled</td> | ||
84 | <td> | ||
85 | <my-feature-boolean [value]="config.tracker.enabled"></my-feature-boolean> | ||
86 | </td> | ||
87 | </tr> | ||
33 | </table> | 88 | </table> |
34 | </div> | 89 | </div> |
diff --git a/client/src/app/shared/instance/instance-features-table.component.scss b/client/src/app/shared/instance/instance-features-table.component.scss index f9bec038d..67f2b6c84 100644 --- a/client/src/app/shared/instance/instance-features-table.component.scss +++ b/client/src/app/shared/instance/instance-features-table.component.scss | |||
@@ -5,16 +5,28 @@ table { | |||
5 | font-size: 14px; | 5 | font-size: 14px; |
6 | color: var(--mainForegroundColor); | 6 | color: var(--mainForegroundColor); |
7 | 7 | ||
8 | .label { | 8 | .label, |
9 | font-weight: $font-semibold; | 9 | .sub-label { |
10 | min-width: 330px; | 10 | min-width: 330px; |
11 | } | ||
12 | 11 | ||
13 | .glyphicon-ok { | 12 | &.label { |
14 | color: $green; | 13 | font-weight: $font-semibold; |
14 | } | ||
15 | |||
16 | &.sub-label { | ||
17 | padding-left: 30px; | ||
18 | } | ||
19 | |||
20 | .more-info { | ||
21 | font-style: italic; | ||
22 | font-weight: initial; | ||
23 | font-size: 14px | ||
24 | } | ||
15 | } | 25 | } |
16 | 26 | ||
17 | .glyphicon-remove { | 27 | td { |
18 | color: $red; | 28 | vertical-align: middle; |
19 | } | 29 | } |
20 | } | 30 | } |
31 | |||
32 | |||
diff --git a/client/src/app/shared/instance/instance-features-table.component.ts b/client/src/app/shared/instance/instance-features-table.component.ts index a53082a93..46df4d0b2 100644 --- a/client/src/app/shared/instance/instance-features-table.component.ts +++ b/client/src/app/shared/instance/instance-features-table.component.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { ServerService } from '@app/core' | 2 | import { ServerService } from '@app/core' |
3 | import { I18n } from '@ngx-translate/i18n-polyfill' | 3 | import { I18n } from '@ngx-translate/i18n-polyfill' |
4 | import { ServerConfig } from '@shared/models' | ||
4 | 5 | ||
5 | @Component({ | 6 | @Component({ |
6 | selector: 'my-instance-features-table', | 7 | selector: 'my-instance-features-table', |
@@ -8,8 +9,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill' | |||
8 | styleUrls: [ './instance-features-table.component.scss' ] | 9 | styleUrls: [ './instance-features-table.component.scss' ] |
9 | }) | 10 | }) |
10 | export class InstanceFeaturesTableComponent implements OnInit { | 11 | export class InstanceFeaturesTableComponent implements OnInit { |
11 | features: { label: string, value?: boolean }[] = [] | ||
12 | quotaHelpIndication = '' | 12 | quotaHelpIndication = '' |
13 | config: ServerConfig | ||
13 | 14 | ||
14 | constructor ( | 15 | constructor ( |
15 | private i18n: I18n, | 16 | private i18n: I18n, |
@@ -28,7 +29,7 @@ export class InstanceFeaturesTableComponent implements OnInit { | |||
28 | ngOnInit () { | 29 | ngOnInit () { |
29 | this.serverService.configLoaded | 30 | this.serverService.configLoaded |
30 | .subscribe(() => { | 31 | .subscribe(() => { |
31 | this.buildFeatures() | 32 | this.config = this.serverService.getConfig() |
32 | this.buildQuotaHelpIndication() | 33 | this.buildQuotaHelpIndication() |
33 | }) | 34 | }) |
34 | } | 35 | } |
@@ -41,37 +42,6 @@ export class InstanceFeaturesTableComponent implements OnInit { | |||
41 | if (policy === 'display') return this.i18n('Displayed') | 42 | if (policy === 'display') return this.i18n('Displayed') |
42 | } | 43 | } |
43 | 44 | ||
44 | private buildFeatures () { | ||
45 | const config = this.serverService.getConfig() | ||
46 | |||
47 | this.features = [ | ||
48 | { | ||
49 | label: this.i18n('User registration allowed'), | ||
50 | value: config.signup.allowed | ||
51 | }, | ||
52 | { | ||
53 | label: this.i18n('Video uploads require manual validation by moderators'), | ||
54 | value: config.autoBlacklist.videos.ofUsers.enabled | ||
55 | }, | ||
56 | { | ||
57 | label: this.i18n('Transcode your videos in multiple resolutions'), | ||
58 | value: config.transcoding.enabledResolutions.length !== 0 | ||
59 | }, | ||
60 | { | ||
61 | label: this.i18n('HTTP import (YouTube, Vimeo, direct URL...)'), | ||
62 | value: config.import.videos.http.enabled | ||
63 | }, | ||
64 | { | ||
65 | label: this.i18n('Torrent import'), | ||
66 | value: config.import.videos.torrent.enabled | ||
67 | }, | ||
68 | { | ||
69 | label: this.i18n('P2P enabled'), | ||
70 | value: config.tracker.enabled | ||
71 | } | ||
72 | ] | ||
73 | } | ||
74 | |||
75 | private getApproximateTime (seconds: number) { | 45 | private getApproximateTime (seconds: number) { |
76 | const hours = Math.floor(seconds / 3600) | 46 | const hours = Math.floor(seconds / 3600) |
77 | let pluralSuffix = '' | 47 | let pluralSuffix = '' |
diff --git a/client/src/app/shared/instance/instance.service.ts b/client/src/app/shared/instance/instance.service.ts index d0c96941d..44b413fa4 100644 --- a/client/src/app/shared/instance/instance.service.ts +++ b/client/src/app/shared/instance/instance.service.ts | |||
@@ -4,6 +4,9 @@ import { Injectable } from '@angular/core' | |||
4 | import { environment } from '../../../environments/environment' | 4 | import { environment } from '../../../environments/environment' |
5 | import { RestExtractor, RestService } from '../rest' | 5 | import { RestExtractor, RestService } from '../rest' |
6 | import { About } from '../../../../../shared/models/server' | 6 | import { About } from '../../../../../shared/models/server' |
7 | import { MarkdownService } from '@app/shared/renderer' | ||
8 | import { peertubeTranslate } from '@shared/models' | ||
9 | import { ServerService } from '@app/core' | ||
7 | 10 | ||
8 | @Injectable() | 11 | @Injectable() |
9 | export class InstanceService { | 12 | export class InstanceService { |
@@ -13,7 +16,9 @@ export class InstanceService { | |||
13 | constructor ( | 16 | constructor ( |
14 | private authHttp: HttpClient, | 17 | private authHttp: HttpClient, |
15 | private restService: RestService, | 18 | private restService: RestService, |
16 | private restExtractor: RestExtractor | 19 | private restExtractor: RestExtractor, |
20 | private markdownService: MarkdownService, | ||
21 | private serverService: ServerService | ||
17 | ) { | 22 | ) { |
18 | } | 23 | } |
19 | 24 | ||
@@ -34,4 +39,43 @@ export class InstanceService { | |||
34 | .pipe(catchError(res => this.restExtractor.handleError(res))) | 39 | .pipe(catchError(res => this.restExtractor.handleError(res))) |
35 | 40 | ||
36 | } | 41 | } |
42 | |||
43 | async buildHtml (about: About) { | ||
44 | const html = { | ||
45 | description: '', | ||
46 | terms: '', | ||
47 | codeOfConduct: '', | ||
48 | moderationInformation: '', | ||
49 | administrator: '', | ||
50 | hardwareInformation: '' | ||
51 | } | ||
52 | |||
53 | for (const key of Object.keys(html)) { | ||
54 | html[ key ] = await this.markdownService.textMarkdownToHTML(about.instance[ key ]) | ||
55 | } | ||
56 | |||
57 | return html | ||
58 | } | ||
59 | |||
60 | buildTranslatedLanguages (about: About, translations: any) { | ||
61 | const languagesArray = this.serverService.getVideoLanguages() | ||
62 | |||
63 | return about.instance.languages | ||
64 | .map(l => { | ||
65 | const languageObj = languagesArray.find(la => la.id === l) | ||
66 | |||
67 | return peertubeTranslate(languageObj.label, translations) | ||
68 | }) | ||
69 | } | ||
70 | |||
71 | buildTranslatedCategories (about: About, translations: any) { | ||
72 | const categoriesArray = this.serverService.getVideoCategories() | ||
73 | |||
74 | return about.instance.categories | ||
75 | .map(c => { | ||
76 | const categoryObj = categoriesArray.find(ca => ca.id === c) | ||
77 | |||
78 | return peertubeTranslate(categoryObj.label, translations) | ||
79 | }) | ||
80 | } | ||
37 | } | 81 | } |
diff --git a/client/src/app/shared/misc/help.component.html b/client/src/app/shared/misc/help.component.html index e31eef06a..9a6d3e48e 100644 --- a/client/src/app/shared/misc/help.component.html +++ b/client/src/app/shared/misc/help.component.html | |||
@@ -1,15 +1,25 @@ | |||
1 | <ng-template #tooltipTemplate> | 1 | <ng-template #tooltipTemplate> |
2 | <ng-template [ngIf]="preHtml"> | 2 | <p *ngIf="preHtmlTemplate"> |
3 | <p [innerHTML]="preHtml"></p> | 3 | <ng-template *ngTemplateOutlet="preHtmlTemplate"></ng-template> |
4 | <br /> | 4 | </p> |
5 | </ng-template> | ||
6 | 5 | ||
7 | <p [innerHTML]="mainHtml"></p> | 6 | <ng-container *ngIf="preHtmlTemplate && (customHtmlTemplate || mainHtml || postHtmlTemplate)"> |
7 | <br /><br /> | ||
8 | </ng-container> | ||
8 | 9 | ||
9 | <ng-template [ngIf]="postHtml"> | 10 | <p *ngIf="customHtmlTemplate"> |
10 | <br /> | 11 | <ng-template *ngTemplateOutlet="customHtmlTemplate"></ng-template> |
11 | <p [innerHTML]="postHtml"></p> | 12 | </p> |
12 | </ng-template> | 13 | |
14 | <p *ngIf="mainHtml" [innerHTML]="mainHtml"></p> | ||
15 | |||
16 | <ng-container *ngIf="(customHtmlTemplate || mainHtml) && postHtmlTemplate"> | ||
17 | <br /><br /> | ||
18 | </ng-container> | ||
19 | |||
20 | <p *ngIf="postHtmlTemplate"> | ||
21 | <ng-template *ngTemplateOutlet="postHtmlTemplate"></ng-template> | ||
22 | </p> | ||
13 | </ng-template> | 23 | </ng-template> |
14 | 24 | ||
15 | <span | 25 | <span |
diff --git a/client/src/app/shared/misc/help.component.ts b/client/src/app/shared/misc/help.component.ts index f3426f70f..18ba8ad5e 100644 --- a/client/src/app/shared/misc/help.component.ts +++ b/client/src/app/shared/misc/help.component.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Component, Input, OnChanges, OnInit } from '@angular/core' | 1 | import { AfterContentInit, Component, ContentChildren, Input, OnChanges, OnInit, QueryList, TemplateRef } from '@angular/core' |
2 | import { I18n } from '@ngx-translate/i18n-polyfill' | 2 | import { I18n } from '@ngx-translate/i18n-polyfill' |
3 | import { MarkdownService } from '@app/shared/renderer' | 3 | import { MarkdownService } from '@app/shared/renderer' |
4 | import { PeerTubeTemplateDirective } from '@app/shared/angular/peertube-template.directive' | ||
4 | 5 | ||
5 | @Component({ | 6 | @Component({ |
6 | selector: 'my-help', | 7 | selector: 'my-help', |
@@ -8,22 +9,42 @@ import { MarkdownService } from '@app/shared/renderer' | |||
8 | templateUrl: './help.component.html' | 9 | templateUrl: './help.component.html' |
9 | }) | 10 | }) |
10 | 11 | ||
11 | export class HelpComponent implements OnInit, OnChanges { | 12 | export class HelpComponent implements OnInit, OnChanges, AfterContentInit { |
12 | @Input() preHtml = '' | ||
13 | @Input() postHtml = '' | ||
14 | @Input() customHtml = '' | ||
15 | @Input() helpType: 'custom' | 'markdownText' | 'markdownEnhanced' = 'custom' | 13 | @Input() helpType: 'custom' | 'markdownText' | 'markdownEnhanced' = 'custom' |
16 | @Input() tooltipPlacement = 'right' | 14 | @Input() tooltipPlacement = 'right' |
17 | 15 | ||
16 | @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective<'preHtml' | 'customHtml' | 'postHtml'>> | ||
17 | |||
18 | isPopoverOpened = false | 18 | isPopoverOpened = false |
19 | mainHtml = '' | 19 | mainHtml = '' |
20 | 20 | ||
21 | preHtmlTemplate: TemplateRef<any> | ||
22 | customHtmlTemplate: TemplateRef<any> | ||
23 | postHtmlTemplate: TemplateRef<any> | ||
24 | |||
21 | constructor (private i18n: I18n) { } | 25 | constructor (private i18n: I18n) { } |
22 | 26 | ||
23 | ngOnInit () { | 27 | ngOnInit () { |
24 | this.init() | 28 | this.init() |
25 | } | 29 | } |
26 | 30 | ||
31 | ngAfterContentInit () { | ||
32 | { | ||
33 | const t = this.templates.find(t => t.name === 'preHtml') | ||
34 | if (t) this.preHtmlTemplate = t.template | ||
35 | } | ||
36 | |||
37 | { | ||
38 | const t = this.templates.find(t => t.name === 'customHtml') | ||
39 | if (t) this.customHtmlTemplate = t.template | ||
40 | } | ||
41 | |||
42 | { | ||
43 | const t = this.templates.find(t => t.name === 'postHtml') | ||
44 | if (t) this.postHtmlTemplate = t.template | ||
45 | } | ||
46 | } | ||
47 | |||
27 | ngOnChanges () { | 48 | ngOnChanges () { |
28 | this.init() | 49 | this.init() |
29 | } | 50 | } |
@@ -37,11 +58,6 @@ export class HelpComponent implements OnInit, OnChanges { | |||
37 | } | 58 | } |
38 | 59 | ||
39 | private init () { | 60 | private init () { |
40 | if (this.helpType === 'custom') { | ||
41 | this.mainHtml = this.customHtml | ||
42 | return | ||
43 | } | ||
44 | |||
45 | if (this.helpType === 'markdownText') { | 61 | if (this.helpType === 'markdownText') { |
46 | this.mainHtml = this.formatMarkdownSupport(MarkdownService.TEXT_RULES) | 62 | this.mainHtml = this.formatMarkdownSupport(MarkdownService.TEXT_RULES) |
47 | return | 63 | return |
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index eb57a2fff..65e0f21a4 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts | |||
@@ -6,10 +6,8 @@ import { RouterModule } from '@angular/router' | |||
6 | import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' | 6 | import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component' |
7 | import { HelpComponent } from '@app/shared/misc/help.component' | 7 | import { HelpComponent } from '@app/shared/misc/help.component' |
8 | import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' | 8 | import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive' |
9 | |||
10 | import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' | 9 | import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' |
11 | import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' | 10 | import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' |
12 | |||
13 | import { AUTH_INTERCEPTOR_PROVIDER } from './auth' | 11 | import { AUTH_INTERCEPTOR_PROVIDER } from './auth' |
14 | import { ButtonComponent } from './buttons/button.component' | 12 | import { ButtonComponent } from './buttons/button.component' |
15 | import { DeleteButtonComponent } from './buttons/delete-button.component' | 13 | import { DeleteButtonComponent } from './buttons/delete-button.component' |
@@ -93,6 +91,8 @@ import { VideoDownloadComponent } from '@app/shared/video/modals/video-download. | |||
93 | import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' | 91 | import { VideoReportComponent } from '@app/shared/video/modals/video-report.component' |
94 | import { ClipboardModule } from 'ngx-clipboard' | 92 | import { ClipboardModule } from 'ngx-clipboard' |
95 | import { FollowService } from '@app/shared/instance/follow.service' | 93 | import { FollowService } from '@app/shared/instance/follow.service' |
94 | import { MultiSelectModule } from 'primeng/multiselect' | ||
95 | import { FeatureBooleanComponent } from '@app/shared/instance/feature-boolean.component' | ||
96 | 96 | ||
97 | @NgModule({ | 97 | @NgModule({ |
98 | imports: [ | 98 | imports: [ |
@@ -113,7 +113,8 @@ import { FollowService } from '@app/shared/instance/follow.service' | |||
113 | 113 | ||
114 | PrimeSharedModule, | 114 | PrimeSharedModule, |
115 | InputMaskModule, | 115 | InputMaskModule, |
116 | NgPipesModule | 116 | NgPipesModule, |
117 | MultiSelectModule | ||
117 | ], | 118 | ], |
118 | 119 | ||
119 | declarations: [ | 120 | declarations: [ |
@@ -156,6 +157,7 @@ import { FollowService } from '@app/shared/instance/follow.service' | |||
156 | SubscribeButtonComponent, | 157 | SubscribeButtonComponent, |
157 | RemoteSubscribeComponent, | 158 | RemoteSubscribeComponent, |
158 | InstanceFeaturesTableComponent, | 159 | InstanceFeaturesTableComponent, |
160 | FeatureBooleanComponent, | ||
159 | UserBanModalComponent, | 161 | UserBanModalComponent, |
160 | UserModerationDropdownComponent, | 162 | UserModerationDropdownComponent, |
161 | TopMenuDropdownComponent, | 163 | TopMenuDropdownComponent, |
@@ -186,6 +188,7 @@ import { FollowService } from '@app/shared/instance/follow.service' | |||
186 | InputMaskModule, | 188 | InputMaskModule, |
187 | BytesPipe, | 189 | BytesPipe, |
188 | KeysPipe, | 190 | KeysPipe, |
191 | MultiSelectModule, | ||
189 | 192 | ||
190 | LoaderComponent, | 193 | LoaderComponent, |
191 | SmallLoaderComponent, | 194 | SmallLoaderComponent, |
diff --git a/client/src/app/shared/user-subscription/remote-subscribe.component.html b/client/src/app/shared/user-subscription/remote-subscribe.component.html index ec3636b3e..59ee1cb04 100644 --- a/client/src/app/shared/user-subscription/remote-subscribe.component.html +++ b/client/src/app/shared/user-subscription/remote-subscribe.component.html | |||
@@ -12,13 +12,21 @@ | |||
12 | <span *ngIf="interact">Remote interact</span> | 12 | <span *ngIf="interact">Remote interact</span> |
13 | </button> | 13 | </button> |
14 | 14 | ||
15 | <my-help *ngIf="!interact && showHelp" | 15 | <my-help *ngIf="!interact && showHelp"> |
16 | helpType="custom" | 16 | <ng-template ptTemplate="customHtml"> |
17 | i18n-customHtml customHtml="You can subscribe to the channel via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type the channel URL in the search box and subscribe there."> | 17 | <ng-container i18n> |
18 | You can subscribe to the channel via any ActivityPub-capable fediverse instance.<br /><br /> | ||
19 | For instance with Mastodon or Pleroma you can type the channel URL in the search box and subscribe there. | ||
20 | </ng-container> | ||
21 | </ng-template> | ||
18 | </my-help> | 22 | </my-help> |
19 | 23 | ||
20 | <my-help *ngIf="showHelp && interact" | 24 | <my-help *ngIf="showHelp && interact"> |
21 | helpType="custom" | 25 | <ng-template ptTemplate="customHtml"> |
22 | i18n-customHtml customHtml="You can interact with this via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type the current URL in the search box and interact with it there."> | 26 | <ng-container i18n> |
27 | You can interact with this via any ActivityPub-capable fediverse instance.<br /><br /> | ||
28 | For instance with Mastodon or Pleroma you can type the current URL in the search box and interact with it there. | ||
29 | </ng-container> | ||
30 | </ng-template> | ||
23 | </my-help> | 31 | </my-help> |
24 | </form> \ No newline at end of file | 32 | </form> |
diff --git a/client/src/app/shared/users/user-notification.model.ts b/client/src/app/shared/users/user-notification.model.ts index 06eace71c..b4ac075c5 100644 --- a/client/src/app/shared/users/user-notification.model.ts +++ b/client/src/app/shared/users/user-notification.model.ts | |||
@@ -42,9 +42,10 @@ export class UserNotification implements UserNotificationServer { | |||
42 | state: FollowState | 42 | state: FollowState |
43 | follower: ActorInfo & { avatarUrl?: string } | 43 | follower: ActorInfo & { avatarUrl?: string } |
44 | following: { | 44 | following: { |
45 | type: 'account' | 'channel' | 45 | type: 'account' | 'channel' | 'instance' |
46 | name: string | 46 | name: string |
47 | displayName: string | 47 | displayName: string |
48 | host: string | ||
48 | } | 49 | } |
49 | } | 50 | } |
50 | 51 | ||
@@ -112,7 +113,7 @@ export class UserNotification implements UserNotificationServer { | |||
112 | 113 | ||
113 | case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS: | 114 | case UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS: |
114 | this.videoAutoBlacklistUrl = '/admin/moderation/video-auto-blacklist/list' | 115 | this.videoAutoBlacklistUrl = '/admin/moderation/video-auto-blacklist/list' |
115 | this.videoUrl = this.buildVideoUrl(this.video) | 116 | this.videoUrl = this.buildVideoUrl(this.videoBlacklist.video) |
116 | break | 117 | break |
117 | 118 | ||
118 | case UserNotificationType.BLACKLIST_ON_MY_VIDEO: | 119 | case UserNotificationType.BLACKLIST_ON_MY_VIDEO: |
@@ -146,6 +147,10 @@ export class UserNotification implements UserNotificationServer { | |||
146 | case UserNotificationType.NEW_INSTANCE_FOLLOWER: | 147 | case UserNotificationType.NEW_INSTANCE_FOLLOWER: |
147 | this.instanceFollowUrl = '/admin/follows/followers-list' | 148 | this.instanceFollowUrl = '/admin/follows/followers-list' |
148 | break | 149 | break |
150 | |||
151 | case UserNotificationType.AUTO_INSTANCE_FOLLOWING: | ||
152 | this.instanceFollowUrl = '/admin/follows/following-list' | ||
153 | break | ||
149 | } | 154 | } |
150 | } catch (err) { | 155 | } catch (err) { |
151 | this.type = null | 156 | this.type = null |
diff --git a/client/src/app/shared/users/user-notifications.component.html b/client/src/app/shared/users/user-notifications.component.html index 292813426..0702d3b5e 100644 --- a/client/src/app/shared/users/user-notifications.component.html +++ b/client/src/app/shared/users/user-notifications.component.html | |||
@@ -40,7 +40,7 @@ | |||
40 | <my-global-icon iconName="no"></my-global-icon> | 40 | <my-global-icon iconName="no"></my-global-icon> |
41 | 41 | ||
42 | <div class="message"> | 42 | <div class="message"> |
43 | The recently added video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.video.name }}</a> has been <a (click)="markAsRead(notification)" [routerLink]="notification.videoAutoBlacklistUrl">auto-blacklisted</a> | 43 | The recently added video <a (click)="markAsRead(notification)" [routerLink]="notification.videoUrl">{{ notification.videoBlacklist.video.name }}</a> has been <a (click)="markAsRead(notification)" [routerLink]="notification.videoAutoBlacklistUrl">auto-blacklisted</a> |
44 | </div> | 44 | </div> |
45 | </ng-container> | 45 | </ng-container> |
46 | 46 | ||
@@ -111,6 +111,14 @@ | |||
111 | <ng-container *ngIf="notification.actorFollow.state === 'pending'"> awaiting your approval</ng-container> | 111 | <ng-container *ngIf="notification.actorFollow.state === 'pending'"> awaiting your approval</ng-container> |
112 | </div> | 112 | </div> |
113 | </ng-container> | 113 | </ng-container> |
114 | |||
115 | <ng-container i18n *ngSwitchCase="UserNotificationType.AUTO_INSTANCE_FOLLOWING"> | ||
116 | <my-global-icon iconName="users"></my-global-icon> | ||
117 | |||
118 | <div class="message"> | ||
119 | Your instance automatically followed <a (click)="markAsRead(notification)" [routerLink]="notification.instanceFollowUrl">{{ notification.actorFollow.following.host }}</a> | ||
120 | </div> | ||
121 | </ng-container> | ||
114 | </ng-container> | 122 | </ng-container> |
115 | 123 | ||
116 | <div class="from-date">{{ notification.createdAt | myFromNow }}</div> | 124 | <div class="from-date">{{ notification.createdAt | myFromNow }}</div> |
diff --git a/client/src/app/shared/users/user.model.ts b/client/src/app/shared/users/user.model.ts index 53809f82c..656b73dd2 100644 --- a/client/src/app/shared/users/user.model.ts +++ b/client/src/app/shared/users/user.model.ts | |||
@@ -9,31 +9,38 @@ export class User implements UserServerModel { | |||
9 | username: string | 9 | username: string |
10 | email: string | 10 | email: string |
11 | pendingEmail: string | null | 11 | pendingEmail: string | null |
12 | |||
12 | emailVerified: boolean | 13 | emailVerified: boolean |
13 | nsfwPolicy: NSFWPolicyType | 14 | nsfwPolicy: NSFWPolicyType |
14 | 15 | ||
15 | role: UserRole | 16 | adminFlags?: UserAdminFlag |
16 | roleLabel: string | ||
17 | 17 | ||
18 | webTorrentEnabled: boolean | ||
19 | autoPlayVideo: boolean | 18 | autoPlayVideo: boolean |
19 | webTorrentEnabled: boolean | ||
20 | videosHistoryEnabled: boolean | 20 | videosHistoryEnabled: boolean |
21 | videoLanguages: string[] | 21 | videoLanguages: string[] |
22 | 22 | ||
23 | role: UserRole | ||
24 | roleLabel: string | ||
25 | |||
23 | videoQuota: number | 26 | videoQuota: number |
24 | videoQuotaDaily: number | 27 | videoQuotaDaily: number |
25 | account: Account | 28 | videoQuotaUsed?: number |
26 | videoChannels: VideoChannel[] | 29 | videoQuotaUsedDaily?: number |
27 | createdAt: Date | ||
28 | 30 | ||
29 | theme: string | 31 | theme: string |
30 | 32 | ||
31 | adminFlags?: UserAdminFlag | 33 | account: Account |
34 | notificationSettings?: UserNotificationSetting | ||
35 | videoChannels?: VideoChannel[] | ||
32 | 36 | ||
33 | blocked: boolean | 37 | blocked: boolean |
34 | blockedReason?: string | 38 | blockedReason?: string |
35 | 39 | ||
36 | notificationSettings?: UserNotificationSetting | 40 | noInstanceConfigWarningModal: boolean |
41 | noWelcomeModal: boolean | ||
42 | |||
43 | createdAt: Date | ||
37 | 44 | ||
38 | constructor (hash: Partial<UserServerModel>) { | 45 | constructor (hash: Partial<UserServerModel>) { |
39 | this.id = hash.id | 46 | this.id = hash.id |
@@ -43,13 +50,16 @@ export class User implements UserServerModel { | |||
43 | this.role = hash.role | 50 | this.role = hash.role |
44 | 51 | ||
45 | this.videoChannels = hash.videoChannels | 52 | this.videoChannels = hash.videoChannels |
53 | |||
46 | this.videoQuota = hash.videoQuota | 54 | this.videoQuota = hash.videoQuota |
47 | this.videoQuotaDaily = hash.videoQuotaDaily | 55 | this.videoQuotaDaily = hash.videoQuotaDaily |
56 | this.videoQuotaUsed = hash.videoQuotaUsed | ||
57 | this.videoQuotaUsedDaily = hash.videoQuotaUsedDaily | ||
58 | |||
48 | this.nsfwPolicy = hash.nsfwPolicy | 59 | this.nsfwPolicy = hash.nsfwPolicy |
49 | this.webTorrentEnabled = hash.webTorrentEnabled | 60 | this.webTorrentEnabled = hash.webTorrentEnabled |
50 | this.videosHistoryEnabled = hash.videosHistoryEnabled | 61 | this.videosHistoryEnabled = hash.videosHistoryEnabled |
51 | this.autoPlayVideo = hash.autoPlayVideo | 62 | this.autoPlayVideo = hash.autoPlayVideo |
52 | this.createdAt = hash.createdAt | ||
53 | 63 | ||
54 | this.theme = hash.theme | 64 | this.theme = hash.theme |
55 | 65 | ||
@@ -58,8 +68,13 @@ export class User implements UserServerModel { | |||
58 | this.blocked = hash.blocked | 68 | this.blocked = hash.blocked |
59 | this.blockedReason = hash.blockedReason | 69 | this.blockedReason = hash.blockedReason |
60 | 70 | ||
71 | this.noInstanceConfigWarningModal = hash.noInstanceConfigWarningModal | ||
72 | this.noWelcomeModal = hash.noWelcomeModal | ||
73 | |||
61 | this.notificationSettings = hash.notificationSettings | 74 | this.notificationSettings = hash.notificationSettings |
62 | 75 | ||
76 | this.createdAt = hash.createdAt | ||
77 | |||
63 | if (hash.account !== undefined) { | 78 | if (hash.account !== undefined) { |
64 | this.account = new Account(hash.account) | 79 | this.account = new Account(hash.account) |
65 | } | 80 | } |
diff --git a/client/src/app/shared/video/videos-selection.component.ts b/client/src/app/shared/video/videos-selection.component.ts index 994e0fa1e..064420056 100644 --- a/client/src/app/shared/video/videos-selection.component.ts +++ b/client/src/app/shared/video/videos-selection.component.ts | |||
@@ -35,7 +35,7 @@ export class VideosSelectionComponent extends AbstractVideoList implements OnIni | |||
35 | @Input() titlePage: string | 35 | @Input() titlePage: string |
36 | @Input() miniatureDisplayOptions: MiniatureDisplayOptions | 36 | @Input() miniatureDisplayOptions: MiniatureDisplayOptions |
37 | @Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<ResultList<Video>> | 37 | @Input() getVideosObservableFunction: (page: number, sort?: VideoSortField) => Observable<ResultList<Video>> |
38 | @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective> | 38 | @ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective<'rowButtons' | 'globalButtons'>> |
39 | 39 | ||
40 | @Output() selectionChange = new EventEmitter<SelectionType>() | 40 | @Output() selectionChange = new EventEmitter<SelectionType>() |
41 | @Output() videosModelChange = new EventEmitter<Video[]>() | 41 | @Output() videosModelChange = new EventEmitter<Video[]>() |
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html index 217cadc66..245ae42b6 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.html +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html | |||
@@ -15,7 +15,16 @@ | |||
15 | 15 | ||
16 | <div class="form-group"> | 16 | <div class="form-group"> |
17 | <label i18n class="label-tags">Tags</label> | 17 | <label i18n class="label-tags">Tags</label> |
18 | <my-help i18n-preHtml preHtml="Tags could be used to suggest relevant recommendations.</br>Press Enter to add a new tag."></my-help> | 18 | |
19 | <my-help> | ||
20 | <ng-template ptTemplate="customHtml"> | ||
21 | <ng-container i18n> | ||
22 | Tags could be used to suggest relevant recommendations. <br /> | ||
23 | Press Enter to add a new tag. | ||
24 | </ng-container> | ||
25 | </ng-template> | ||
26 | </my-help> | ||
27 | |||
19 | <tag-input | 28 | <tag-input |
20 | [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" | 29 | [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" |
21 | i18n-placeholder placeholder="+ Tag" i18n-secondaryPlaceholder secondaryPlaceholder="Enter a new tag" | 30 | i18n-placeholder placeholder="+ Tag" i18n-secondaryPlaceholder secondaryPlaceholder="Enter a new tag" |
@@ -25,7 +34,15 @@ | |||
25 | 34 | ||
26 | <div class="form-group"> | 35 | <div class="form-group"> |
27 | <label i18n for="description">Description</label> | 36 | <label i18n for="description">Description</label> |
28 | <my-help helpType="markdownText" i18n-preHtml preHtml="Video descriptions are truncated by default and require manual action to expand them."></my-help> | 37 | |
38 | <my-help helpType="markdownText"> | ||
39 | <ng-template ptTemplate="preHtml"> | ||
40 | <ng-container i18n> | ||
41 | Video descriptions are truncated by default and require manual action to expand them. | ||
42 | </ng-container> | ||
43 | </ng-template> | ||
44 | </my-help> | ||
45 | |||
29 | <my-markdown-textarea truncate="250" formControlName="description"></my-markdown-textarea> | 46 | <my-markdown-textarea truncate="250" formControlName="description"></my-markdown-textarea> |
30 | 47 | ||
31 | <div *ngIf="formErrors.description" class="form-error"> | 48 | <div *ngIf="formErrors.description" class="form-error"> |
@@ -114,20 +131,25 @@ | |||
114 | </div> | 131 | </div> |
115 | </div> | 132 | </div> |
116 | 133 | ||
117 | <my-peertube-checkbox | 134 | <my-peertube-checkbox inputName="nsfw" formControlName="nsfw" helpPlacement="bottom-right"> |
118 | inputName="nsfw" formControlName="nsfw" | 135 | <ng-template ptTemplate="label"> |
119 | i18n-labelText labelText="This video contains mature or explicit content" | 136 | <ng-container i18n>This video contains mature or explicit content</ng-container> |
120 | i18n-helpHtml helpHtml="Some instances do not list videos containing mature or explicit content by default." | 137 | </ng-template> |
121 | helpPlacement="bottom-right" | 138 | |
122 | ></my-peertube-checkbox> | 139 | <ng-template ptTemplate="help"> |
123 | 140 | <ng-container i18n>Some instances do not list videos containing mature or explicit content by default.</ng-container> | |
124 | <my-peertube-checkbox | 141 | </ng-template> |
125 | *ngIf="waitTranscodingEnabled" | 142 | </my-peertube-checkbox> |
126 | inputName="waitTranscoding" formControlName="waitTranscoding" | 143 | |
127 | i18n-labelText labelText="Wait transcoding before publishing the video" | 144 | <my-peertube-checkbox *ngIf="waitTranscodingEnabled" inputName="waitTranscoding" formControlName="waitTranscoding" helpPlacement="bottom-right"> |
128 | i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends." | 145 | <ng-template ptTemplate="label"> |
129 | helpPlacement="bottom-right" | 146 | <ng-container i18n>Wait transcoding before publishing the video</ng-container> |
130 | ></my-peertube-checkbox> | 147 | </ng-template> |
148 | |||
149 | <ng-template ptTemplate="help"> | ||
150 | <ng-container i18n>If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends.</ng-container> | ||
151 | </ng-template> | ||
152 | </my-peertube-checkbox> | ||
131 | 153 | ||
132 | </div> | 154 | </div> |
133 | </div> | 155 | </div> |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html index 7a495fea5..c290fd4b1 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html +++ b/client/src/app/videos/+video-edit/video-add-components/video-import-torrent.component.html | |||
@@ -12,10 +12,14 @@ | |||
12 | 12 | ||
13 | <div class="form-group form-group-magnet-uri"> | 13 | <div class="form-group form-group-magnet-uri"> |
14 | <label i18n for="magnetUri">Paste magnet URI</label> | 14 | <label i18n for="magnetUri">Paste magnet URI</label> |
15 | <my-help | 15 | <my-help> |
16 | helpType="custom" i18n-customHtml | 16 | <ng-template ptTemplate="customHtml"> |
17 | customHtml="You can import any torrent file that points to a mp4 file. You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance." | 17 | <ng-container i18n> |
18 | ></my-help> | 18 | You can import any torrent file that points to a mp4 file. |
19 | You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance. | ||
20 | </ng-container> | ||
21 | </ng-template> | ||
22 | </my-help> | ||
19 | 23 | ||
20 | <input type="text" id="magnetUri" [(ngModel)]="magnetUri" /> | 24 | <input type="text" id="magnetUri" [(ngModel)]="magnetUri" /> |
21 | </div> | 25 | </div> |
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html index e4f19faa8..09d0b8272 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html +++ b/client/src/app/videos/+video-edit/video-add-components/video-import-url.component.html | |||
@@ -4,10 +4,16 @@ | |||
4 | 4 | ||
5 | <div class="form-group"> | 5 | <div class="form-group"> |
6 | <label i18n for="targetUrl">URL</label> | 6 | <label i18n for="targetUrl">URL</label> |
7 | <my-help | 7 | |
8 | helpType="custom" i18n-customHtml | 8 | <my-help> |
9 | customHtml="You can import any URL <a href='https://rg3.github.io/youtube-dl/supportedsites.html' target='_blank' rel='noopener noreferrer'>supported by youtube-dl</a> or URL that points to a raw MP4 file. You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance." | 9 | <ng-template ptTemplate="customHtml"> |
10 | ></my-help> | 10 | <ng-container i18n> |
11 | You can import any URL <a href='https://rg3.github.io/youtube-dl/supportedsites.html' target='_blank' rel='noopener noreferrer'>supported by youtube-dl</a> | ||
12 | or URL that points to a raw MP4 file. | ||
13 | You should make sure you have diffusion rights over the content it points to, otherwise it could cause legal trouble to yourself and your instance. | ||
14 | </ng-container> | ||
15 | </ng-template> | ||
16 | </my-help> | ||
11 | 17 | ||
12 | <input type="text" id="targetUrl" [(ngModel)]="targetUrl" /> | 18 | <input type="text" id="targetUrl" [(ngModel)]="targetUrl" /> |
13 | </div> | 19 | </div> |
diff --git a/client/src/sass/include/_mixins.scss b/client/src/sass/include/_mixins.scss index abbc137b2..ebd3c0cc4 100644 --- a/client/src/sass/include/_mixins.scss +++ b/client/src/sass/include/_mixins.scss | |||
@@ -343,6 +343,7 @@ | |||
343 | & + span { | 343 | & + span { |
344 | position: relative; | 344 | position: relative; |
345 | width: 18px; | 345 | width: 18px; |
346 | min-width: 18px; | ||
346 | height: 18px; | 347 | height: 18px; |
347 | border: $border-width solid var(--mainForegroundColor); | 348 | border: $border-width solid var(--mainForegroundColor); |
348 | border-radius: 3px; | 349 | border-radius: 3px; |
diff --git a/client/src/sass/player/peertube-skin.scss b/client/src/sass/player/peertube-skin.scss index 1a5144b11..4bf48a570 100644 --- a/client/src/sass/player/peertube-skin.scss +++ b/client/src/sass/player/peertube-skin.scss | |||
@@ -26,11 +26,6 @@ body { | |||
26 | .vjs-dock-description { | 26 | .vjs-dock-description { |
27 | font-size: 11px; | 27 | font-size: 11px; |
28 | 28 | ||
29 | .text::before, .text::after { | ||
30 | display: inline-block; | ||
31 | content: '\1F308'; | ||
32 | } | ||
33 | |||
34 | .text::before { | 29 | .text::before { |
35 | margin-right: 4px; | 30 | margin-right: 4px; |
36 | } | 31 | } |
diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index 6ff3efef1..19d2a1d02 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts | |||
@@ -239,7 +239,7 @@ export class PeerTubeEmbed { | |||
239 | 239 | ||
240 | const config: ServerConfig = await configResponse.json() | 240 | const config: ServerConfig = await configResponse.json() |
241 | const description = config.tracker.enabled && this.warningTitle | 241 | const description = config.tracker.enabled && this.warningTitle |
242 | ? '<span class="text">' + this.player.localize('Uses P2P, others may know your IP is downloading this video.') + '</span>' | 242 | ? '<span class="text">' + this.player.localize('Watching this video may reveal your IP address to others.') + '</span>' |
243 | : undefined | 243 | : undefined |
244 | 244 | ||
245 | this.player.dock({ | 245 | this.player.dock({ |
diff --git a/config/default.yaml b/config/default.yaml index dfba23f59..5ebfdeddb 100644 --- a/config/default.yaml +++ b/config/default.yaml | |||
@@ -69,7 +69,7 @@ email: | |||
69 | 69 | ||
70 | # From the project root directory | 70 | # From the project root directory |
71 | storage: | 71 | storage: |
72 | tmp: 'storage/tmp/' # Used to download data (imports etc), store uploaded files before processing... | 72 | tmp: 'storage/tmp/' # Use to download data (imports etc), store uploaded files before processing... |
73 | avatars: 'storage/avatars/' | 73 | avatars: 'storage/avatars/' |
74 | videos: 'storage/videos/' | 74 | videos: 'storage/videos/' |
75 | streaming_playlists: 'storage/streaming-playlists/' | 75 | streaming_playlists: 'storage/streaming-playlists/' |
@@ -85,7 +85,7 @@ storage: | |||
85 | log: | 85 | log: |
86 | level: 'info' # debug/info/warning/error | 86 | level: 'info' # debug/info/warning/error |
87 | rotation: | 87 | rotation: |
88 | enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate | 88 | enabled : true |
89 | 89 | ||
90 | search: | 90 | search: |
91 | # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance | 91 | # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance |
@@ -238,7 +238,60 @@ instance: | |||
238 | short_description: 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.' | 238 | short_description: 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.' |
239 | description: 'Welcome to this PeerTube instance!' # Support markdown | 239 | description: 'Welcome to this PeerTube instance!' # Support markdown |
240 | terms: 'No terms for now.' # Support markdown | 240 | terms: 'No terms for now.' # Support markdown |
241 | code_of_conduct: '' # Supports markdown | ||
242 | |||
243 | # Who moderates the instance? What is the policy regarding NSFW videos? Political videos? etc | ||
244 | moderation_information: '' # Supports markdown | ||
245 | |||
246 | # Why did you create this instance? | ||
247 | creation_reason: '' | ||
248 | |||
249 | # Who is behind the instance? A single person? A non profit? | ||
250 | administrator: '' | ||
251 | |||
252 | # How long do you plan to maintain this instance? | ||
253 | maintenance_lifetime: '' | ||
254 | |||
255 | # How will you pay the PeerTube instance server? With you own funds? With users donations? Advertising? | ||
256 | business_model: '' | ||
257 | |||
258 | # If you want to explain on what type of hardware your PeerTube instance runs | ||
259 | # Example: "2 vCore, 2GB RAM..." | ||
260 | hardware_information: '' # Supports Markdown | ||
261 | |||
262 | # What are the main languages of your instance? To interact with your users for example | ||
263 | # Uncomment or add the languages you want | ||
264 | # List of supported languages: https://peertube.cpy.re/api/v1/videos/languages | ||
265 | languages: | ||
266 | # - en | ||
267 | # - es | ||
268 | # - fr | ||
269 | |||
270 | # You can specify the main categories of your instance (dedicated to music, gaming or politics etc) | ||
271 | # Uncomment or add the category ids you want | ||
272 | # List of supported categories: https://peertube.cpy.re/api/v1/videos/categories | ||
273 | categories: | ||
274 | # - 1 # Music | ||
275 | # - 2 # Films | ||
276 | # - 3 # Vehicles | ||
277 | # - 4 # Art | ||
278 | # - 5 # Sports | ||
279 | # - 6 # Travels | ||
280 | # - 7 # Gaming | ||
281 | # - 8 # People | ||
282 | # - 9 # Comedy | ||
283 | # - 10 # Entertainment | ||
284 | # - 11 # News & Politics | ||
285 | # - 12 # How To | ||
286 | # - 13 # Education | ||
287 | # - 14 # Activism | ||
288 | # - 15 # Science & Technology | ||
289 | # - 16 # Animals | ||
290 | # - 17 # Kids | ||
291 | # - 18 # Food | ||
292 | |||
241 | default_client_route: '/videos/trending' | 293 | default_client_route: '/videos/trending' |
294 | |||
242 | # Whether or not the instance is dedicated to NSFW content | 295 | # Whether or not the instance is dedicated to NSFW content |
243 | # Enabling it will allow other administrators to know that you are mainly federating sensitive content | 296 | # Enabling it will allow other administrators to know that you are mainly federating sensitive content |
244 | # Moreover, the NSFW checkbox on video upload will be automatically checked by default | 297 | # Moreover, the NSFW checkbox on video upload will be automatically checked by default |
@@ -246,6 +299,7 @@ instance: | |||
246 | # By default, "do_not_list" or "blur" or "display" NSFW videos | 299 | # By default, "do_not_list" or "blur" or "display" NSFW videos |
247 | # Could be overridden per user with a setting | 300 | # Could be overridden per user with a setting |
248 | default_nsfw_policy: 'do_not_list' | 301 | default_nsfw_policy: 'do_not_list' |
302 | |||
249 | customizations: | 303 | customizations: |
250 | javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime | 304 | javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime |
251 | css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime | 305 | css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime |
@@ -273,5 +327,21 @@ followers: | |||
273 | # Whether or not an administrator must manually validate a new follower | 327 | # Whether or not an administrator must manually validate a new follower |
274 | manual_approval: false | 328 | manual_approval: false |
275 | 329 | ||
330 | followings: | ||
331 | instance: | ||
332 | # If you want to automatically follow back new instance followers | ||
333 | # Only follows accepted followers (in case you enabled manual followers approbation) | ||
334 | # If this option is enabled, use the mute feature instead of deleting followings | ||
335 | # /!\ Don't enable this if you don't have a reactive moderation team /!\ | ||
336 | auto_follow_back: | ||
337 | enabled: false | ||
338 | |||
339 | # If you want to automatically follow instances of the public index | ||
340 | # If this option is enabled, use the mute feature instead of deleting followings | ||
341 | # /!\ Don't enable this if you don't have a reactive moderation team /!\ | ||
342 | auto_follow_index: | ||
343 | enabled: false | ||
344 | index_url: 'https://instances.joinpeertube.org' | ||
345 | |||
276 | theme: | 346 | theme: |
277 | default: 'default' | 347 | default: 'default' |
diff --git a/config/production.yaml.example b/config/production.yaml.example index 267186e08..96d676a35 100644 --- a/config/production.yaml.example +++ b/config/production.yaml.example | |||
@@ -70,11 +70,11 @@ email: | |||
70 | 70 | ||
71 | # From the project root directory | 71 | # From the project root directory |
72 | storage: | 72 | storage: |
73 | tmp: '/var/www/peertube/storage/tmp/' # Used to download data (imports etc), store uploaded files before processing... | 73 | tmp: '/var/www/peertube/storage/tmp/' # Use to download data (imports etc), store uploaded files before processing... |
74 | avatars: '/var/www/peertube/storage/avatars/' | 74 | avatars: '/var/www/peertube/storage/avatars/' |
75 | videos: '/var/www/peertube/storage/videos/' | 75 | videos: '/var/www/peertube/storage/videos/' |
76 | streaming_playlists: '/var/www/peertube/storage/streaming-playlists/' | 76 | streaming_playlists: '/var/www/peertube/storage/streaming-playlists/' |
77 | redundancy: '/var/www/peertube/storage/videos/' | 77 | redundancy: '/var/www/peertube/storage/redundancy/' |
78 | logs: '/var/www/peertube/storage/logs/' | 78 | logs: '/var/www/peertube/storage/logs/' |
79 | previews: '/var/www/peertube/storage/previews/' | 79 | previews: '/var/www/peertube/storage/previews/' |
80 | thumbnails: '/var/www/peertube/storage/thumbnails/' | 80 | thumbnails: '/var/www/peertube/storage/thumbnails/' |
@@ -86,7 +86,7 @@ storage: | |||
86 | log: | 86 | log: |
87 | level: 'info' # debug/info/warning/error | 87 | level: 'info' # debug/info/warning/error |
88 | rotation: | 88 | rotation: |
89 | enabled : true | 89 | enabled : true # Enabled by default, if disabled make sure that 'storage.logs' is pointing to a folder handled by logrotate |
90 | 90 | ||
91 | search: | 91 | search: |
92 | # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance | 92 | # Add ability to fetch remote videos/actors by their URI, that may not be federated with your instance |
@@ -157,8 +157,8 @@ views: | |||
157 | max_age: -1 | 157 | max_age: -1 |
158 | 158 | ||
159 | plugins: | 159 | plugins: |
160 | # The website PeerTube will ask for available PeerTube plugins | 160 | # The website PeerTube will ask for available PeerTube plugins and themes |
161 | # This is an unmoderated plugin index, so only install plugins you trust | 161 | # This is an unmoderated plugin index, so only install plugins/themes you trust |
162 | index: | 162 | index: |
163 | enabled: true | 163 | enabled: true |
164 | check_latest_versions_interval: '12 hours' # How often you want to check new plugins/themes versions | 164 | check_latest_versions_interval: '12 hours' # How often you want to check new plugins/themes versions |
@@ -251,9 +251,62 @@ auto_blacklist: | |||
251 | instance: | 251 | instance: |
252 | name: 'PeerTube' | 252 | name: 'PeerTube' |
253 | short_description: 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.' | 253 | short_description: 'PeerTube, a federated (ActivityPub) video streaming platform using P2P (BitTorrent) directly in the web browser with WebTorrent and Angular.' |
254 | description: '' # Support markdown | 254 | description: 'Welcome to this PeerTube instance!' # Support markdown |
255 | terms: '' # Support markdown | 255 | terms: 'No terms for now.' # Support markdown |
256 | code_of_conduct: '' # Supports markdown | ||
257 | |||
258 | # Who moderates the instance? What is the policy regarding NSFW videos? Political videos? etc | ||
259 | moderation_information: '' # Supports markdown | ||
260 | |||
261 | # Why did you create this instance? | ||
262 | creation_reason: '' | ||
263 | |||
264 | # Who is behind the instance? A single person? A non profit? | ||
265 | administrator: '' | ||
266 | |||
267 | # How long do you plan to maintain this instance? | ||
268 | maintenance_lifetime: '' | ||
269 | |||
270 | # How will you pay the PeerTube instance server? With you own funds? With users donations? Advertising? | ||
271 | business_model: '' | ||
272 | |||
273 | # If you want to explain on what type of hardware your PeerTube instance runs | ||
274 | # Example: "2 vCore, 2GB RAM..." | ||
275 | hardware_information: '' # Supports Markdown | ||
276 | |||
277 | # What are the main languages of your instance? To interact with your users for example | ||
278 | # Uncomment or add the languages you want | ||
279 | # List of supported languages: https://peertube.cpy.re/api/v1/videos/languages | ||
280 | languages: | ||
281 | # - en | ||
282 | # - es | ||
283 | # - fr | ||
284 | |||
285 | # You can specify the main categories of your instance (dedicated to music, gaming or politics etc) | ||
286 | # Uncomment or add the category ids you want | ||
287 | # List of supported categories: https://peertube.cpy.re/api/v1/videos/categories | ||
288 | categories: | ||
289 | # - 1 # Music | ||
290 | # - 2 # Films | ||
291 | # - 3 # Vehicles | ||
292 | # - 4 # Art | ||
293 | # - 5 # Sports | ||
294 | # - 6 # Travels | ||
295 | # - 7 # Gaming | ||
296 | # - 8 # People | ||
297 | # - 9 # Comedy | ||
298 | # - 10 # Entertainment | ||
299 | # - 11 # News & Politics | ||
300 | # - 12 # How To | ||
301 | # - 13 # Education | ||
302 | # - 14 # Activism | ||
303 | # - 15 # Science & Technology | ||
304 | # - 16 # Animals | ||
305 | # - 17 # Kids | ||
306 | # - 18 # Food | ||
307 | |||
256 | default_client_route: '/videos/trending' | 308 | default_client_route: '/videos/trending' |
309 | |||
257 | # Whether or not the instance is dedicated to NSFW content | 310 | # Whether or not the instance is dedicated to NSFW content |
258 | # Enabling it will allow other administrators to know that you are mainly federating sensitive content | 311 | # Enabling it will allow other administrators to know that you are mainly federating sensitive content |
259 | # Moreover, the NSFW checkbox on video upload will be automatically checked by default | 312 | # Moreover, the NSFW checkbox on video upload will be automatically checked by default |
@@ -261,6 +314,7 @@ instance: | |||
261 | # By default, "do_not_list" or "blur" or "display" NSFW videos | 314 | # By default, "do_not_list" or "blur" or "display" NSFW videos |
262 | # Could be overridden per user with a setting | 315 | # Could be overridden per user with a setting |
263 | default_nsfw_policy: 'do_not_list' | 316 | default_nsfw_policy: 'do_not_list' |
317 | |||
264 | customizations: | 318 | customizations: |
265 | javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime | 319 | javascript: '' # Directly your JavaScript code (without <script> tags). Will be eval at runtime |
266 | css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime | 320 | css: '' # Directly your CSS code (without <style> tags). Will be injected at runtime |
@@ -278,7 +332,7 @@ services: | |||
278 | username: '@Chocobozzz' # Indicates the Twitter account for the website or platform on which the content was published | 332 | username: '@Chocobozzz' # Indicates the Twitter account for the website or platform on which the content was published |
279 | # If true, a video player will be embedded in the Twitter feed on PeerTube video share | 333 | # If true, a video player will be embedded in the Twitter feed on PeerTube video share |
280 | # If false, we use an image link card that will redirect on your PeerTube instance | 334 | # If false, we use an image link card that will redirect on your PeerTube instance |
281 | # Test on https://cards-dev.twitter.com/validator to see if you are whitelisted | 335 | # Change it to "true", and then test on https://cards-dev.twitter.com/validator to see if you are whitelisted |
282 | whitelisted: false | 336 | whitelisted: false |
283 | 337 | ||
284 | followers: | 338 | followers: |
@@ -288,5 +342,20 @@ followers: | |||
288 | # Whether or not an administrator must manually validate a new follower | 342 | # Whether or not an administrator must manually validate a new follower |
289 | manual_approval: false | 343 | manual_approval: false |
290 | 344 | ||
345 | followings: | ||
346 | instance: | ||
347 | # If you want to automatically follow back new instance followers | ||
348 | # If this option is enabled, use the mute feature instead of deleting followings | ||
349 | # /!\ Don't enable this if you don't have a reactive moderation team /!\ | ||
350 | auto_follow_back: | ||
351 | enabled: false | ||
352 | |||
353 | # If you want to automatically follow instances of the public index | ||
354 | # If this option is enabled, use the mute feature instead of deleting followings | ||
355 | # /!\ Don't enable this if you don't have a reactive moderation team /!\ | ||
356 | auto_follow_index: | ||
357 | enabled: false | ||
358 | index_url: 'https://instances.joinpeertube.org' | ||
359 | |||
291 | theme: | 360 | theme: |
292 | default: 'default' | 361 | default: 'default' |
diff --git a/package.json b/package.json index ce689a4b3..38c40bcf0 100644 --- a/package.json +++ b/package.json | |||
@@ -128,11 +128,11 @@ | |||
128 | "iso-639-3": "^1.0.1", | 128 | "iso-639-3": "^1.0.1", |
129 | "js-yaml": "^3.5.4", | 129 | "js-yaml": "^3.5.4", |
130 | "jsonld": "~1.1.0", | 130 | "jsonld": "~1.1.0", |
131 | "jsonld-signatures": "https://github.com/Chocobozzz/jsonld-signatures#rsa2017", | ||
132 | "lodash": "^4.17.10", | 131 | "lodash": "^4.17.10", |
133 | "lru-cache": "^5.1.1", | 132 | "lru-cache": "^5.1.1", |
134 | "magnet-uri": "^5.1.4", | 133 | "magnet-uri": "^5.1.4", |
135 | "memoizee": "^0.4.14", | 134 | "memoizee": "^0.4.14", |
135 | "module-alias": "^2.2.1", | ||
136 | "morgan": "^1.5.3", | 136 | "morgan": "^1.5.3", |
137 | "multer": "^1.1.0", | 137 | "multer": "^1.1.0", |
138 | "nodemailer": "^6.0.0", | 138 | "nodemailer": "^6.0.0", |
@@ -225,5 +225,8 @@ | |||
225 | "scripty": { | 225 | "scripty": { |
226 | "silent": true | 226 | "silent": true |
227 | }, | 227 | }, |
228 | "sasslintConfig": "client/.sass-lint.yml" | 228 | "sasslintConfig": "client/.sass-lint.yml", |
229 | "_moduleAliases": { | ||
230 | "@server": "dist/server" | ||
231 | } | ||
229 | } | 232 | } |
diff --git a/scripts/client-report.sh b/scripts/client-report.sh index df7ccda27..a758a211c 100755 --- a/scripts/client-report.sh +++ b/scripts/client-report.sh | |||
@@ -5,5 +5,5 @@ set -eu | |||
5 | gawk -i inplace 'BEGIN { found=0 } { if (found || $0 ~ /^{/) { found=1; print }}' ./client/dist/embed-stats.json | 5 | gawk -i inplace 'BEGIN { found=0 } { if (found || $0 ~ /^{/) { found=1; print }}' ./client/dist/embed-stats.json |
6 | 6 | ||
7 | npm run concurrently -- -k \ | 7 | npm run concurrently -- -k \ |
8 | "cd client && npm run webpack-bundle-analyzer -- -p 8888 ./dist/en_US/stats.json" \ | 8 | "cd client && npm run webpack-bundle-analyzer -- -p 8888 ./dist/en_US/stats-es2015.json" \ |
9 | "cd client && npm run webpack-bundle-analyzer -- -p 8889 ./dist/embed-stats.json" \ No newline at end of file | 9 | "cd client && npm run webpack-bundle-analyzer -- -p 8889 ./dist/embed-stats.json" |
@@ -1,3 +1,5 @@ | |||
1 | require('module-alias/register') | ||
2 | |||
1 | // FIXME: https://github.com/nodejs/node/pull/16853 | 3 | // FIXME: https://github.com/nodejs/node/pull/16853 |
2 | import { PluginManager } from './server/lib/plugins/plugin-manager' | 4 | import { PluginManager } from './server/lib/plugins/plugin-manager' |
3 | 5 | ||
@@ -113,6 +115,7 @@ import { UpdateVideosScheduler } from './server/lib/schedulers/update-videos-sch | |||
113 | import { YoutubeDlUpdateScheduler } from './server/lib/schedulers/youtube-dl-update-scheduler' | 115 | import { YoutubeDlUpdateScheduler } from './server/lib/schedulers/youtube-dl-update-scheduler' |
114 | import { VideosRedundancyScheduler } from './server/lib/schedulers/videos-redundancy-scheduler' | 116 | import { VideosRedundancyScheduler } from './server/lib/schedulers/videos-redundancy-scheduler' |
115 | import { RemoveOldHistoryScheduler } from './server/lib/schedulers/remove-old-history-scheduler' | 117 | import { RemoveOldHistoryScheduler } from './server/lib/schedulers/remove-old-history-scheduler' |
118 | import { AutoFollowIndexInstances } from './server/lib/schedulers/auto-follow-index-instances' | ||
116 | import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto' | 119 | import { isHTTPSignatureDigestValid } from './server/helpers/peertube-crypto' |
117 | import { PeerTubeSocket } from './server/lib/peertube-socket' | 120 | import { PeerTubeSocket } from './server/lib/peertube-socket' |
118 | import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls' | 121 | import { updateStreamingPlaylistsInfohashesIfNeeded } from './server/lib/hls' |
@@ -258,6 +261,7 @@ async function startApplication () { | |||
258 | RemoveOldHistoryScheduler.Instance.enable() | 261 | RemoveOldHistoryScheduler.Instance.enable() |
259 | RemoveOldViewsScheduler.Instance.enable() | 262 | RemoveOldViewsScheduler.Instance.enable() |
260 | PluginsCheckScheduler.Instance.enable() | 263 | PluginsCheckScheduler.Instance.enable() |
264 | AutoFollowIndexInstances.Instance.enable() | ||
261 | 265 | ||
262 | // Redis initialization | 266 | // Redis initialization |
263 | Redis.Instance.init() | 267 | Redis.Instance.init() |
diff --git a/server/controllers/activitypub/client.ts b/server/controllers/activitypub/client.ts index 11504b354..453ced8bf 100644 --- a/server/controllers/activitypub/client.ts +++ b/server/controllers/activitypub/client.ts | |||
@@ -16,7 +16,6 @@ import { | |||
16 | } from '../../middlewares' | 16 | } from '../../middlewares' |
17 | import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators' | 17 | import { getAccountVideoRateValidator, videoCommentGetValidator } from '../../middlewares/validators' |
18 | import { AccountModel } from '../../models/account/account' | 18 | import { AccountModel } from '../../models/account/account' |
19 | import { ActorModel } from '../../models/activitypub/actor' | ||
20 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | 19 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' |
21 | import { VideoModel } from '../../models/video/video' | 20 | import { VideoModel } from '../../models/video/video' |
22 | import { VideoCommentModel } from '../../models/video/video-comment' | 21 | import { VideoCommentModel } from '../../models/video/video-comment' |
@@ -38,6 +37,7 @@ import { buildDislikeActivity } from '../../lib/activitypub/send/send-dislike' | |||
38 | import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' | 37 | import { videoPlaylistElementAPGetValidator, videoPlaylistsGetValidator } from '../../middlewares/validators/videos/video-playlists' |
39 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | 38 | import { VideoPlaylistModel } from '../../models/video/video-playlist' |
40 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | 39 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' |
40 | import { MAccountId, MActorId, MVideo, MVideoAPWithoutCaption } from '@server/typings/models' | ||
41 | 41 | ||
42 | const activityPubClientRouter = express.Router() | 42 | const activityPubClientRouter = express.Router() |
43 | 43 | ||
@@ -148,7 +148,7 @@ activityPubClientRouter.get('/redundancy/streaming-playlists/:streamingPlaylistT | |||
148 | 148 | ||
149 | activityPubClientRouter.get('/video-playlists/:playlistId', | 149 | activityPubClientRouter.get('/video-playlists/:playlistId', |
150 | executeIfActivityPub, | 150 | executeIfActivityPub, |
151 | asyncMiddleware(videoPlaylistsGetValidator), | 151 | asyncMiddleware(videoPlaylistsGetValidator('all')), |
152 | asyncMiddleware(videoPlaylistController) | 152 | asyncMiddleware(videoPlaylistController) |
153 | ) | 153 | ) |
154 | activityPubClientRouter.get('/video-playlists/:playlistId/:videoId', | 154 | activityPubClientRouter.get('/video-playlists/:playlistId/:videoId', |
@@ -208,18 +208,19 @@ function getAccountVideoRate (rateType: VideoRateType) { | |||
208 | 208 | ||
209 | async function videoController (req: express.Request, res: express.Response) { | 209 | async function videoController (req: express.Request, res: express.Response) { |
210 | // We need more attributes | 210 | // We need more attributes |
211 | const video = await VideoModel.loadForGetAPI({ id: res.locals.video.id }) | 211 | const video = await VideoModel.loadForGetAPI({ id: res.locals.onlyVideoWithRights.id }) as MVideoAPWithoutCaption |
212 | 212 | ||
213 | if (video.url.startsWith(WEBSERVER.URL) === false) return res.redirect(video.url) | 213 | if (video.url.startsWith(WEBSERVER.URL) === false) return res.redirect(video.url) |
214 | 214 | ||
215 | // We need captions to render AP object | 215 | // We need captions to render AP object |
216 | video.VideoCaptions = await VideoCaptionModel.listVideoCaptions(video.id) | 216 | const captions = await VideoCaptionModel.listVideoCaptions(video.id) |
217 | const videoWithCaptions = Object.assign(video, { VideoCaptions: captions }) | ||
217 | 218 | ||
218 | const audience = getAudience(video.VideoChannel.Account.Actor, video.privacy === VideoPrivacy.PUBLIC) | 219 | const audience = getAudience(videoWithCaptions.VideoChannel.Account.Actor, videoWithCaptions.privacy === VideoPrivacy.PUBLIC) |
219 | const videoObject = audiencify(video.toActivityPubObject(), audience) | 220 | const videoObject = audiencify(videoWithCaptions.toActivityPubObject(), audience) |
220 | 221 | ||
221 | if (req.path.endsWith('/activity')) { | 222 | if (req.path.endsWith('/activity')) { |
222 | const data = buildCreateActivity(video.url, video.VideoChannel.Account.Actor, videoObject, audience) | 223 | const data = buildCreateActivity(videoWithCaptions.url, video.VideoChannel.Account.Actor, videoObject, audience) |
223 | return activityPubResponse(activityPubContextify(data), res) | 224 | return activityPubResponse(activityPubContextify(data), res) |
224 | } | 225 | } |
225 | 226 | ||
@@ -231,13 +232,13 @@ async function videoAnnounceController (req: express.Request, res: express.Respo | |||
231 | 232 | ||
232 | if (share.url.startsWith(WEBSERVER.URL) === false) return res.redirect(share.url) | 233 | if (share.url.startsWith(WEBSERVER.URL) === false) return res.redirect(share.url) |
233 | 234 | ||
234 | const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.video, undefined) | 235 | const { activity } = await buildAnnounceWithVideoAudience(share.Actor, share, res.locals.videoAll, undefined) |
235 | 236 | ||
236 | return activityPubResponse(activityPubContextify(activity), res) | 237 | return activityPubResponse(activityPubContextify(activity), res) |
237 | } | 238 | } |
238 | 239 | ||
239 | async function videoAnnouncesController (req: express.Request, res: express.Response) { | 240 | async function videoAnnouncesController (req: express.Request, res: express.Response) { |
240 | const video = res.locals.video | 241 | const video = res.locals.onlyVideo |
241 | 242 | ||
242 | const handler = async (start: number, count: number) => { | 243 | const handler = async (start: number, count: number) => { |
243 | const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) | 244 | const result = await VideoShareModel.listAndCountByVideoId(video.id, start, count) |
@@ -252,21 +253,21 @@ async function videoAnnouncesController (req: express.Request, res: express.Resp | |||
252 | } | 253 | } |
253 | 254 | ||
254 | async function videoLikesController (req: express.Request, res: express.Response) { | 255 | async function videoLikesController (req: express.Request, res: express.Response) { |
255 | const video = res.locals.video | 256 | const video = res.locals.onlyVideo |
256 | const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video)) | 257 | const json = await videoRates(req, 'like', video, getVideoLikesActivityPubUrl(video)) |
257 | 258 | ||
258 | return activityPubResponse(activityPubContextify(json), res) | 259 | return activityPubResponse(activityPubContextify(json), res) |
259 | } | 260 | } |
260 | 261 | ||
261 | async function videoDislikesController (req: express.Request, res: express.Response) { | 262 | async function videoDislikesController (req: express.Request, res: express.Response) { |
262 | const video = res.locals.video | 263 | const video = res.locals.onlyVideo |
263 | const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video)) | 264 | const json = await videoRates(req, 'dislike', video, getVideoDislikesActivityPubUrl(video)) |
264 | 265 | ||
265 | return activityPubResponse(activityPubContextify(json), res) | 266 | return activityPubResponse(activityPubContextify(json), res) |
266 | } | 267 | } |
267 | 268 | ||
268 | async function videoCommentsController (req: express.Request, res: express.Response) { | 269 | async function videoCommentsController (req: express.Request, res: express.Response) { |
269 | const video = res.locals.video | 270 | const video = res.locals.onlyVideo |
270 | 271 | ||
271 | const handler = async (start: number, count: number) => { | 272 | const handler = async (start: number, count: number) => { |
272 | const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) | 273 | const result = await VideoCommentModel.listAndCountByVideoId(video.id, start, count) |
@@ -301,7 +302,7 @@ async function videoChannelFollowingController (req: express.Request, res: expre | |||
301 | } | 302 | } |
302 | 303 | ||
303 | async function videoCommentController (req: express.Request, res: express.Response) { | 304 | async function videoCommentController (req: express.Request, res: express.Response) { |
304 | const videoComment = res.locals.videoComment | 305 | const videoComment = res.locals.videoCommentFull |
305 | 306 | ||
306 | if (videoComment.url.startsWith(WEBSERVER.URL) === false) return res.redirect(videoComment.url) | 307 | if (videoComment.url.startsWith(WEBSERVER.URL) === false) return res.redirect(videoComment.url) |
307 | 308 | ||
@@ -337,7 +338,7 @@ async function videoRedundancyController (req: express.Request, res: express.Res | |||
337 | } | 338 | } |
338 | 339 | ||
339 | async function videoPlaylistController (req: express.Request, res: express.Response) { | 340 | async function videoPlaylistController (req: express.Request, res: express.Response) { |
340 | const playlist = res.locals.videoPlaylist | 341 | const playlist = res.locals.videoPlaylistFull |
341 | 342 | ||
342 | // We need more attributes | 343 | // We need more attributes |
343 | playlist.OwnerAccount = await AccountModel.load(playlist.ownerAccountId) | 344 | playlist.OwnerAccount = await AccountModel.load(playlist.ownerAccountId) |
@@ -350,7 +351,7 @@ async function videoPlaylistController (req: express.Request, res: express.Respo | |||
350 | } | 351 | } |
351 | 352 | ||
352 | async function videoPlaylistElementController (req: express.Request, res: express.Response) { | 353 | async function videoPlaylistElementController (req: express.Request, res: express.Response) { |
353 | const videoPlaylistElement = res.locals.videoPlaylistElement | 354 | const videoPlaylistElement = res.locals.videoPlaylistElementAP |
354 | 355 | ||
355 | const json = videoPlaylistElement.toActivityPubObject() | 356 | const json = videoPlaylistElement.toActivityPubObject() |
356 | return activityPubResponse(activityPubContextify(json), res) | 357 | return activityPubResponse(activityPubContextify(json), res) |
@@ -358,7 +359,7 @@ async function videoPlaylistElementController (req: express.Request, res: expres | |||
358 | 359 | ||
359 | // --------------------------------------------------------------------------- | 360 | // --------------------------------------------------------------------------- |
360 | 361 | ||
361 | async function actorFollowing (req: express.Request, actor: ActorModel) { | 362 | async function actorFollowing (req: express.Request, actor: MActorId) { |
362 | const handler = (start: number, count: number) => { | 363 | const handler = (start: number, count: number) => { |
363 | return ActorFollowModel.listAcceptedFollowingUrlsForApi([ actor.id ], undefined, start, count) | 364 | return ActorFollowModel.listAcceptedFollowingUrlsForApi([ actor.id ], undefined, start, count) |
364 | } | 365 | } |
@@ -366,7 +367,7 @@ async function actorFollowing (req: express.Request, actor: ActorModel) { | |||
366 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) | 367 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) |
367 | } | 368 | } |
368 | 369 | ||
369 | async function actorFollowers (req: express.Request, actor: ActorModel) { | 370 | async function actorFollowers (req: express.Request, actor: MActorId) { |
370 | const handler = (start: number, count: number) => { | 371 | const handler = (start: number, count: number) => { |
371 | return ActorFollowModel.listAcceptedFollowerUrlsForAP([ actor.id ], undefined, start, count) | 372 | return ActorFollowModel.listAcceptedFollowerUrlsForAP([ actor.id ], undefined, start, count) |
372 | } | 373 | } |
@@ -374,7 +375,7 @@ async function actorFollowers (req: express.Request, actor: ActorModel) { | |||
374 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) | 375 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) |
375 | } | 376 | } |
376 | 377 | ||
377 | async function actorPlaylists (req: express.Request, account: AccountModel) { | 378 | async function actorPlaylists (req: express.Request, account: MAccountId) { |
378 | const handler = (start: number, count: number) => { | 379 | const handler = (start: number, count: number) => { |
379 | return VideoPlaylistModel.listPublicUrlsOfForAP(account.id, start, count) | 380 | return VideoPlaylistModel.listPublicUrlsOfForAP(account.id, start, count) |
380 | } | 381 | } |
@@ -382,7 +383,7 @@ async function actorPlaylists (req: express.Request, account: AccountModel) { | |||
382 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) | 383 | return activityPubCollectionPagination(WEBSERVER.URL + req.path, handler, req.query.page) |
383 | } | 384 | } |
384 | 385 | ||
385 | function videoRates (req: express.Request, rateType: VideoRateType, video: VideoModel, url: string) { | 386 | function videoRates (req: express.Request, rateType: VideoRateType, video: MVideo, url: string) { |
386 | const handler = async (start: number, count: number) => { | 387 | const handler = async (start: number, count: number) => { |
387 | const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count) | 388 | const result = await AccountVideoRateModel.listAndCountAccountUrlsByVideoId(rateType, video.id, start, count) |
388 | return { | 389 | return { |
diff --git a/server/controllers/activitypub/inbox.ts b/server/controllers/activitypub/inbox.ts index 2d3eef222..d9df253aa 100644 --- a/server/controllers/activitypub/inbox.ts +++ b/server/controllers/activitypub/inbox.ts | |||
@@ -7,7 +7,7 @@ import { asyncMiddleware, checkSignature, localAccountValidator, localVideoChann | |||
7 | import { activityPubValidator } from '../../middlewares/validators/activitypub/activity' | 7 | import { activityPubValidator } from '../../middlewares/validators/activitypub/activity' |
8 | import { queue } from 'async' | 8 | import { queue } from 'async' |
9 | import { ActorModel } from '../../models/activitypub/actor' | 9 | import { ActorModel } from '../../models/activitypub/actor' |
10 | import { SignatureActorModel } from '../../typings/models' | 10 | import { MActorDefault, MActorSignature } from '../../typings/models' |
11 | 11 | ||
12 | const inboxRouter = express.Router() | 12 | const inboxRouter = express.Router() |
13 | 13 | ||
@@ -41,7 +41,8 @@ export { | |||
41 | 41 | ||
42 | // --------------------------------------------------------------------------- | 42 | // --------------------------------------------------------------------------- |
43 | 43 | ||
44 | const inboxQueue = queue<{ activities: Activity[], signatureActor?: SignatureActorModel, inboxActor?: ActorModel }, Error>((task, cb) => { | 44 | type QueueParam = { activities: Activity[], signatureActor?: MActorSignature, inboxActor?: MActorDefault } |
45 | const inboxQueue = queue<QueueParam, Error>((task, cb) => { | ||
45 | const options = { signatureActor: task.signatureActor, inboxActor: task.inboxActor } | 46 | const options = { signatureActor: task.signatureActor, inboxActor: task.inboxActor } |
46 | 47 | ||
47 | processActivities(task.activities, options) | 48 | processActivities(task.activities, options) |
diff --git a/server/controllers/activitypub/outbox.ts b/server/controllers/activitypub/outbox.ts index 38b6ec976..f3dd2ad7d 100644 --- a/server/controllers/activitypub/outbox.ts +++ b/server/controllers/activitypub/outbox.ts | |||
@@ -6,11 +6,9 @@ import { logger } from '../../helpers/logger' | |||
6 | import { buildAnnounceActivity, buildCreateActivity } from '../../lib/activitypub/send' | 6 | import { buildAnnounceActivity, buildCreateActivity } from '../../lib/activitypub/send' |
7 | import { buildAudience } from '../../lib/activitypub/audience' | 7 | import { buildAudience } from '../../lib/activitypub/audience' |
8 | import { asyncMiddleware, localAccountValidator, localVideoChannelValidator } from '../../middlewares' | 8 | import { asyncMiddleware, localAccountValidator, localVideoChannelValidator } from '../../middlewares' |
9 | import { AccountModel } from '../../models/account/account' | ||
10 | import { ActorModel } from '../../models/activitypub/actor' | ||
11 | import { VideoModel } from '../../models/video/video' | 9 | import { VideoModel } from '../../models/video/video' |
12 | import { activityPubResponse } from './utils' | 10 | import { activityPubResponse } from './utils' |
13 | import { VideoChannelModel } from '../../models/video/video-channel' | 11 | import { MActorLight } from '@server/typings/models' |
14 | 12 | ||
15 | const outboxRouter = express.Router() | 13 | const outboxRouter = express.Router() |
16 | 14 | ||
@@ -45,14 +43,10 @@ async function outboxController (req: express.Request, res: express.Response) { | |||
45 | return activityPubResponse(activityPubContextify(json), res) | 43 | return activityPubResponse(activityPubContextify(json), res) |
46 | } | 44 | } |
47 | 45 | ||
48 | async function buildActivities (actor: ActorModel, start: number, count: number) { | 46 | async function buildActivities (actor: MActorLight, start: number, count: number) { |
49 | const data = await VideoModel.listAllAndSharedByActorForOutbox(actor.id, start, count) | 47 | const data = await VideoModel.listAllAndSharedByActorForOutbox(actor.id, start, count) |
50 | const activities: Activity[] = [] | 48 | const activities: Activity[] = [] |
51 | 49 | ||
52 | // Avoid too many SQL requests | ||
53 | const actors = data.data.map(v => v.VideoChannel.Account.Actor) | ||
54 | actors.push(actor) | ||
55 | |||
56 | for (const video of data.data) { | 50 | for (const video of data.data) { |
57 | const byActor = video.VideoChannel.Account.Actor | 51 | const byActor = video.VideoChannel.Account.Actor |
58 | const createActivityAudience = buildAudience([ byActor.followersUrl ], video.privacy === VideoPrivacy.PUBLIC) | 52 | const createActivityAudience = buildAudience([ byActor.followersUrl ], video.privacy === VideoPrivacy.PUBLIC) |
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts index 21fa85a08..39a124fc5 100644 --- a/server/controllers/api/config.ts +++ b/server/controllers/api/config.ts | |||
@@ -158,7 +158,19 @@ function getAbout (req: express.Request, res: express.Response) { | |||
158 | name: CONFIG.INSTANCE.NAME, | 158 | name: CONFIG.INSTANCE.NAME, |
159 | shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, | 159 | shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, |
160 | description: CONFIG.INSTANCE.DESCRIPTION, | 160 | description: CONFIG.INSTANCE.DESCRIPTION, |
161 | terms: CONFIG.INSTANCE.TERMS | 161 | terms: CONFIG.INSTANCE.TERMS, |
162 | codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT, | ||
163 | |||
164 | hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION, | ||
165 | |||
166 | creationReason: CONFIG.INSTANCE.CREATION_REASON, | ||
167 | moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION, | ||
168 | administrator: CONFIG.INSTANCE.ADMINISTRATOR, | ||
169 | maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME, | ||
170 | businessModel: CONFIG.INSTANCE.BUSINESS_MODEL, | ||
171 | |||
172 | languages: CONFIG.INSTANCE.LANGUAGES, | ||
173 | categories: CONFIG.INSTANCE.CATEGORIES | ||
162 | } | 174 | } |
163 | } | 175 | } |
164 | 176 | ||
@@ -221,6 +233,18 @@ function customConfig (): CustomConfig { | |||
221 | shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, | 233 | shortDescription: CONFIG.INSTANCE.SHORT_DESCRIPTION, |
222 | description: CONFIG.INSTANCE.DESCRIPTION, | 234 | description: CONFIG.INSTANCE.DESCRIPTION, |
223 | terms: CONFIG.INSTANCE.TERMS, | 235 | terms: CONFIG.INSTANCE.TERMS, |
236 | codeOfConduct: CONFIG.INSTANCE.CODE_OF_CONDUCT, | ||
237 | |||
238 | creationReason: CONFIG.INSTANCE.CREATION_REASON, | ||
239 | moderationInformation: CONFIG.INSTANCE.MODERATION_INFORMATION, | ||
240 | administrator: CONFIG.INSTANCE.ADMINISTRATOR, | ||
241 | maintenanceLifetime: CONFIG.INSTANCE.MAINTENANCE_LIFETIME, | ||
242 | businessModel: CONFIG.INSTANCE.BUSINESS_MODEL, | ||
243 | hardwareInformation: CONFIG.INSTANCE.HARDWARE_INFORMATION, | ||
244 | |||
245 | languages: CONFIG.INSTANCE.LANGUAGES, | ||
246 | categories: CONFIG.INSTANCE.CATEGORIES, | ||
247 | |||
224 | isNSFW: CONFIG.INSTANCE.IS_NSFW, | 248 | isNSFW: CONFIG.INSTANCE.IS_NSFW, |
225 | defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, | 249 | defaultClientRoute: CONFIG.INSTANCE.DEFAULT_CLIENT_ROUTE, |
226 | defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, | 250 | defaultNSFWPolicy: CONFIG.INSTANCE.DEFAULT_NSFW_POLICY, |
@@ -300,6 +324,18 @@ function customConfig (): CustomConfig { | |||
300 | enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED, | 324 | enabled: CONFIG.FOLLOWERS.INSTANCE.ENABLED, |
301 | manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL | 325 | manualApproval: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL |
302 | } | 326 | } |
327 | }, | ||
328 | followings: { | ||
329 | instance: { | ||
330 | autoFollowBack: { | ||
331 | enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED | ||
332 | }, | ||
333 | |||
334 | autoFollowIndex: { | ||
335 | enabled: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.ENABLED, | ||
336 | indexUrl: CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL | ||
337 | } | ||
338 | } | ||
303 | } | 339 | } |
304 | } | 340 | } |
305 | } | 341 | } |
diff --git a/server/controllers/api/search.ts b/server/controllers/api/search.ts index 9a1e30b83..349650aca 100644 --- a/server/controllers/api/search.ts +++ b/server/controllers/api/search.ts | |||
@@ -19,6 +19,7 @@ import { getOrCreateActorAndServerAndModel, getOrCreateVideoAndAccountAndChannel | |||
19 | import { logger } from '../../helpers/logger' | 19 | import { logger } from '../../helpers/logger' |
20 | import { VideoChannelModel } from '../../models/video/video-channel' | 20 | import { VideoChannelModel } from '../../models/video/video-channel' |
21 | import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' | 21 | import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' |
22 | import { MChannelAccountDefault, MVideoAccountLightBlacklistAllFiles } from '../../typings/models' | ||
22 | 23 | ||
23 | const searchRouter = express.Router() | 24 | const searchRouter = express.Router() |
24 | 25 | ||
@@ -84,7 +85,7 @@ async function searchVideoChannelsDB (query: VideoChannelsSearchQuery, res: expr | |||
84 | } | 85 | } |
85 | 86 | ||
86 | async function searchVideoChannelURI (search: string, isWebfingerSearch: boolean, res: express.Response) { | 87 | async function searchVideoChannelURI (search: string, isWebfingerSearch: boolean, res: express.Response) { |
87 | let videoChannel: VideoChannelModel | 88 | let videoChannel: MChannelAccountDefault |
88 | let uri = search | 89 | let uri = search |
89 | 90 | ||
90 | if (isWebfingerSearch) { | 91 | if (isWebfingerSearch) { |
@@ -137,7 +138,7 @@ async function searchVideosDB (query: VideosSearchQuery, res: express.Response) | |||
137 | } | 138 | } |
138 | 139 | ||
139 | async function searchVideoURI (url: string, res: express.Response) { | 140 | async function searchVideoURI (url: string, res: express.Response) { |
140 | let video: VideoModel | 141 | let video: MVideoAccountLightBlacklistAllFiles |
141 | 142 | ||
142 | // Check if we can fetch a remote video with the URL | 143 | // Check if we can fetch a remote video with the URL |
143 | if (isUserAbleToSearchRemoteURI(res)) { | 144 | if (isUserAbleToSearchRemoteURI(res)) { |
diff --git a/server/controllers/api/server/follows.ts b/server/controllers/api/server/follows.ts index d38ce91de..37647622b 100644 --- a/server/controllers/api/server/follows.ts +++ b/server/controllers/api/server/follows.ts | |||
@@ -25,6 +25,7 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | |||
25 | import { JobQueue } from '../../../lib/job-queue' | 25 | import { JobQueue } from '../../../lib/job-queue' |
26 | import { removeRedundancyOf } from '../../../lib/redundancy' | 26 | import { removeRedundancyOf } from '../../../lib/redundancy' |
27 | import { sequelizeTypescript } from '../../../initializers/database' | 27 | import { sequelizeTypescript } from '../../../initializers/database' |
28 | import { autoFollowBackIfNeeded } from '../../../lib/activitypub/follow' | ||
28 | 29 | ||
29 | const serverFollowsRouter = express.Router() | 30 | const serverFollowsRouter = express.Router() |
30 | serverFollowsRouter.get('/following', | 31 | serverFollowsRouter.get('/following', |
@@ -172,5 +173,7 @@ async function acceptFollower (req: express.Request, res: express.Response) { | |||
172 | follow.state = 'accepted' | 173 | follow.state = 'accepted' |
173 | await follow.save() | 174 | await follow.save() |
174 | 175 | ||
176 | await autoFollowBackIfNeeded(follow) | ||
177 | |||
175 | return res.status(204).end() | 178 | return res.status(204).end() |
176 | } | 179 | } |
diff --git a/server/controllers/api/users/index.ts b/server/controllers/api/users/index.ts index ae40e86f8..27351c1a9 100644 --- a/server/controllers/api/users/index.ts +++ b/server/controllers/api/users/index.ts | |||
@@ -48,6 +48,7 @@ import { CONFIG } from '../../../initializers/config' | |||
48 | import { sequelizeTypescript } from '../../../initializers/database' | 48 | import { sequelizeTypescript } from '../../../initializers/database' |
49 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' | 49 | import { UserAdminFlag } from '../../../../shared/models/users/user-flag.model' |
50 | import { UserRegister } from '../../../../shared/models/users/user-register.model' | 50 | import { UserRegister } from '../../../../shared/models/users/user-register.model' |
51 | import { MUser, MUserAccountDefault } from '@server/typings/models' | ||
51 | 52 | ||
52 | const auditLogger = auditLoggerFactory('users') | 53 | const auditLogger = auditLoggerFactory('users') |
53 | 54 | ||
@@ -195,7 +196,7 @@ async function createUser (req: express.Request, res: express.Response) { | |||
195 | videoQuota: body.videoQuota, | 196 | videoQuota: body.videoQuota, |
196 | videoQuotaDaily: body.videoQuotaDaily, | 197 | videoQuotaDaily: body.videoQuotaDaily, |
197 | adminFlags: body.adminFlags || UserAdminFlag.NONE | 198 | adminFlags: body.adminFlags || UserAdminFlag.NONE |
198 | }) | 199 | }) as MUser |
199 | 200 | ||
200 | const { user, account } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate }) | 201 | const { user, account } = await createUserAccountAndChannelAndPlaylist({ userToCreate: userToCreate }) |
201 | 202 | ||
@@ -359,7 +360,7 @@ function success (req: express.Request, res: express.Response) { | |||
359 | res.end() | 360 | res.end() |
360 | } | 361 | } |
361 | 362 | ||
362 | async function changeUserBlock (res: express.Response, user: UserModel, block: boolean, reason?: string) { | 363 | async function changeUserBlock (res: express.Response, user: MUserAccountDefault, block: boolean, reason?: string) { |
363 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) | 364 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON()) |
364 | 365 | ||
365 | user.blocked = block | 366 | user.blocked = block |
diff --git a/server/controllers/api/users/me.ts b/server/controllers/api/users/me.ts index e7ed3de64..bf872ca52 100644 --- a/server/controllers/api/users/me.ts +++ b/server/controllers/api/users/me.ts | |||
@@ -23,15 +23,12 @@ import { createReqFiles } from '../../../helpers/express-utils' | |||
23 | import { UserVideoQuota } from '../../../../shared/models/users/user-video-quota.model' | 23 | import { UserVideoQuota } from '../../../../shared/models/users/user-video-quota.model' |
24 | import { updateAvatarValidator } from '../../../middlewares/validators/avatar' | 24 | import { updateAvatarValidator } from '../../../middlewares/validators/avatar' |
25 | import { updateActorAvatarFile } from '../../../lib/avatar' | 25 | import { updateActorAvatarFile } from '../../../lib/avatar' |
26 | import { auditLoggerFactory, getAuditIdFromRes, UserAuditView } from '../../../helpers/audit-logger' | ||
27 | import { VideoImportModel } from '../../../models/video/video-import' | 26 | import { VideoImportModel } from '../../../models/video/video-import' |
28 | import { AccountModel } from '../../../models/account/account' | 27 | import { AccountModel } from '../../../models/account/account' |
29 | import { CONFIG } from '../../../initializers/config' | 28 | import { CONFIG } from '../../../initializers/config' |
30 | import { sequelizeTypescript } from '../../../initializers/database' | 29 | import { sequelizeTypescript } from '../../../initializers/database' |
31 | import { sendVerifyUserEmail } from '../../../lib/user' | 30 | import { sendVerifyUserEmail } from '../../../lib/user' |
32 | 31 | ||
33 | const auditLogger = auditLoggerFactory('users-me') | ||
34 | |||
35 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) | 32 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) |
36 | 33 | ||
37 | const meRouter = express.Router() | 34 | const meRouter = express.Router() |
@@ -130,7 +127,7 @@ async function getUserInformation (req: express.Request, res: express.Response) | |||
130 | // We did not load channels in res.locals.user | 127 | // We did not load channels in res.locals.user |
131 | const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) | 128 | const user = await UserModel.loadByUsernameAndPopulateChannels(res.locals.oauth.token.user.username) |
132 | 129 | ||
133 | return res.json(user.toFormattedJSON({})) | 130 | return res.json(user.toFormattedJSON()) |
134 | } | 131 | } |
135 | 132 | ||
136 | async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) { | 133 | async function getUserVideoQuotaUsed (req: express.Request, res: express.Response) { |
@@ -147,7 +144,7 @@ async function getUserVideoQuotaUsed (req: express.Request, res: express.Respons | |||
147 | } | 144 | } |
148 | 145 | ||
149 | async function getUserVideoRating (req: express.Request, res: express.Response) { | 146 | async function getUserVideoRating (req: express.Request, res: express.Response) { |
150 | const videoId = res.locals.video.id | 147 | const videoId = res.locals.videoId.id |
151 | const accountId = +res.locals.oauth.token.User.Account.id | 148 | const accountId = +res.locals.oauth.token.User.Account.id |
152 | 149 | ||
153 | const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null) | 150 | const ratingObj = await AccountVideoRateModel.load(accountId, videoId, null) |
@@ -165,8 +162,6 @@ async function deleteMe (req: express.Request, res: express.Response) { | |||
165 | 162 | ||
166 | await user.destroy() | 163 | await user.destroy() |
167 | 164 | ||
168 | auditLogger.delete(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({}))) | ||
169 | |||
170 | return res.sendStatus(204) | 165 | return res.sendStatus(204) |
171 | } | 166 | } |
172 | 167 | ||
@@ -175,7 +170,6 @@ async function updateMe (req: express.Request, res: express.Response) { | |||
175 | let sendVerificationEmail = false | 170 | let sendVerificationEmail = false |
176 | 171 | ||
177 | const user = res.locals.oauth.token.user | 172 | const user = res.locals.oauth.token.user |
178 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON({})) | ||
179 | 173 | ||
180 | if (body.password !== undefined) user.password = body.password | 174 | if (body.password !== undefined) user.password = body.password |
181 | if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy | 175 | if (body.nsfwPolicy !== undefined) user.nsfwPolicy = body.nsfwPolicy |
@@ -184,6 +178,8 @@ async function updateMe (req: express.Request, res: express.Response) { | |||
184 | if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled | 178 | if (body.videosHistoryEnabled !== undefined) user.videosHistoryEnabled = body.videosHistoryEnabled |
185 | if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages | 179 | if (body.videoLanguages !== undefined) user.videoLanguages = body.videoLanguages |
186 | if (body.theme !== undefined) user.theme = body.theme | 180 | if (body.theme !== undefined) user.theme = body.theme |
181 | if (body.noInstanceConfigWarningModal !== undefined) user.noInstanceConfigWarningModal = body.noInstanceConfigWarningModal | ||
182 | if (body.noWelcomeModal !== undefined) user.noWelcomeModal = body.noWelcomeModal | ||
187 | 183 | ||
188 | if (body.email !== undefined) { | 184 | if (body.email !== undefined) { |
189 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { | 185 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION) { |
@@ -195,17 +191,17 @@ async function updateMe (req: express.Request, res: express.Response) { | |||
195 | } | 191 | } |
196 | 192 | ||
197 | await sequelizeTypescript.transaction(async t => { | 193 | await sequelizeTypescript.transaction(async t => { |
198 | const userAccount = await AccountModel.load(user.Account.id) | ||
199 | |||
200 | await user.save({ transaction: t }) | 194 | await user.save({ transaction: t }) |
201 | 195 | ||
202 | if (body.displayName !== undefined) userAccount.name = body.displayName | 196 | if (body.displayName !== undefined || body.description !== undefined) { |
203 | if (body.description !== undefined) userAccount.description = body.description | 197 | const userAccount = await AccountModel.load(user.Account.id, t) |
204 | await userAccount.save({ transaction: t }) | ||
205 | 198 | ||
206 | await sendUpdateActor(userAccount, t) | 199 | if (body.displayName !== undefined) userAccount.name = body.displayName |
200 | if (body.description !== undefined) userAccount.description = body.description | ||
201 | await userAccount.save({ transaction: t }) | ||
207 | 202 | ||
208 | auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView) | 203 | await sendUpdateActor(userAccount, t) |
204 | } | ||
209 | }) | 205 | }) |
210 | 206 | ||
211 | if (sendVerificationEmail === true) { | 207 | if (sendVerificationEmail === true) { |
@@ -218,13 +214,10 @@ async function updateMe (req: express.Request, res: express.Response) { | |||
218 | async function updateMyAvatar (req: express.Request, res: express.Response) { | 214 | async function updateMyAvatar (req: express.Request, res: express.Response) { |
219 | const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] | 215 | const avatarPhysicalFile = req.files[ 'avatarfile' ][ 0 ] |
220 | const user = res.locals.oauth.token.user | 216 | const user = res.locals.oauth.token.user |
221 | const oldUserAuditView = new UserAuditView(user.toFormattedJSON({})) | ||
222 | 217 | ||
223 | const userAccount = await AccountModel.load(user.Account.id) | 218 | const userAccount = await AccountModel.load(user.Account.id) |
224 | 219 | ||
225 | const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount) | 220 | const avatar = await updateActorAvatarFile(avatarPhysicalFile, userAccount) |
226 | 221 | ||
227 | auditLogger.update(getAuditIdFromRes(res), new UserAuditView(user.toFormattedJSON({})), oldUserAuditView) | ||
228 | |||
229 | return res.json({ avatar: avatar.toFormattedJSON() }) | 222 | return res.json({ avatar: avatar.toFormattedJSON() }) |
230 | } | 223 | } |
diff --git a/server/controllers/api/users/my-history.ts b/server/controllers/api/users/my-history.ts index 7025c0ff1..4da1f3496 100644 --- a/server/controllers/api/users/my-history.ts +++ b/server/controllers/api/users/my-history.ts | |||
@@ -7,7 +7,6 @@ import { | |||
7 | setDefaultPagination, | 7 | setDefaultPagination, |
8 | userHistoryRemoveValidator | 8 | userHistoryRemoveValidator |
9 | } from '../../../middlewares' | 9 | } from '../../../middlewares' |
10 | import { UserModel } from '../../../models/account/user' | ||
11 | import { getFormattedObjects } from '../../../helpers/utils' | 10 | import { getFormattedObjects } from '../../../helpers/utils' |
12 | import { UserVideoHistoryModel } from '../../../models/account/user-video-history' | 11 | import { UserVideoHistoryModel } from '../../../models/account/user-video-history' |
13 | import { sequelizeTypescript } from '../../../initializers' | 12 | import { sequelizeTypescript } from '../../../initializers' |
diff --git a/server/controllers/api/users/my-notifications.ts b/server/controllers/api/users/my-notifications.ts index f146284e4..017f5219e 100644 --- a/server/controllers/api/users/my-notifications.ts +++ b/server/controllers/api/users/my-notifications.ts | |||
@@ -76,7 +76,8 @@ async function updateNotificationSettings (req: express.Request, res: express.Re | |||
76 | newFollow: body.newFollow, | 76 | newFollow: body.newFollow, |
77 | newUserRegistration: body.newUserRegistration, | 77 | newUserRegistration: body.newUserRegistration, |
78 | commentMention: body.commentMention, | 78 | commentMention: body.commentMention, |
79 | newInstanceFollower: body.newInstanceFollower | 79 | newInstanceFollower: body.newInstanceFollower, |
80 | autoInstanceFollowing: body.autoInstanceFollowing | ||
80 | } | 81 | } |
81 | 82 | ||
82 | await UserNotificationSettingModel.update(values, query) | 83 | await UserNotificationSettingModel.update(values, query) |
diff --git a/server/controllers/api/video-channel.ts b/server/controllers/api/video-channel.ts index 81a03a62b..acc5b2987 100644 --- a/server/controllers/api/video-channel.ts +++ b/server/controllers/api/video-channel.ts | |||
@@ -19,7 +19,7 @@ import { VideoChannelModel } from '../../models/video/video-channel' | |||
19 | import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators' | 19 | import { videoChannelsNameWithHostValidator, videosSortValidator } from '../../middlewares/validators' |
20 | import { sendUpdateActor } from '../../lib/activitypub/send' | 20 | import { sendUpdateActor } from '../../lib/activitypub/send' |
21 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' | 21 | import { VideoChannelCreate, VideoChannelUpdate } from '../../../shared' |
22 | import { createVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel' | 22 | import { createLocalVideoChannel, federateAllVideosOfChannel } from '../../lib/video-channel' |
23 | import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' | 23 | import { buildNSFWFilter, createReqFiles, isUserAbleToSearchRemoteURI } from '../../helpers/express-utils' |
24 | import { setAsyncActorKeys } from '../../lib/activitypub' | 24 | import { setAsyncActorKeys } from '../../lib/activitypub' |
25 | import { AccountModel } from '../../models/account/account' | 25 | import { AccountModel } from '../../models/account/account' |
@@ -35,6 +35,7 @@ import { VideoPlaylistModel } from '../../models/video/video-playlist' | |||
35 | import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists' | 35 | import { commonVideoPlaylistFiltersValidator } from '../../middlewares/validators/videos/video-playlists' |
36 | import { CONFIG } from '../../initializers/config' | 36 | import { CONFIG } from '../../initializers/config' |
37 | import { sequelizeTypescript } from '../../initializers/database' | 37 | import { sequelizeTypescript } from '../../initializers/database' |
38 | import { MChannelAccountDefault } from '@server/typings/models' | ||
38 | 39 | ||
39 | const auditLogger = auditLoggerFactory('channels') | 40 | const auditLogger = auditLoggerFactory('channels') |
40 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) | 41 | const reqAvatarFile = createReqFiles([ 'avatarfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { avatarfile: CONFIG.STORAGE.TMP_DIR }) |
@@ -136,10 +137,10 @@ async function updateVideoChannelAvatar (req: express.Request, res: express.Resp | |||
136 | async function addVideoChannel (req: express.Request, res: express.Response) { | 137 | async function addVideoChannel (req: express.Request, res: express.Response) { |
137 | const videoChannelInfo: VideoChannelCreate = req.body | 138 | const videoChannelInfo: VideoChannelCreate = req.body |
138 | 139 | ||
139 | const videoChannelCreated: VideoChannelModel = await sequelizeTypescript.transaction(async t => { | 140 | const videoChannelCreated = await sequelizeTypescript.transaction(async t => { |
140 | const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) | 141 | const account = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) |
141 | 142 | ||
142 | return createVideoChannel(videoChannelInfo, account, t) | 143 | return createLocalVideoChannel(videoChannelInfo, account, t) |
143 | }) | 144 | }) |
144 | 145 | ||
145 | setAsyncActorKeys(videoChannelCreated.Actor) | 146 | setAsyncActorKeys(videoChannelCreated.Actor) |
@@ -181,7 +182,7 @@ async function updateVideoChannel (req: express.Request, res: express.Response) | |||
181 | } | 182 | } |
182 | } | 183 | } |
183 | 184 | ||
184 | const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) | 185 | const videoChannelInstanceUpdated = await videoChannelInstance.save(sequelizeOptions) as MChannelAccountDefault |
185 | await sendUpdateActor(videoChannelInstanceUpdated, t) | 186 | await sendUpdateActor(videoChannelInstanceUpdated, t) |
186 | 187 | ||
187 | auditLogger.update( | 188 | auditLogger.update( |
diff --git a/server/controllers/api/video-playlist.ts b/server/controllers/api/video-playlist.ts index bd454f553..d9f0ff925 100644 --- a/server/controllers/api/video-playlist.ts +++ b/server/controllers/api/video-playlist.ts | |||
@@ -40,7 +40,7 @@ import { JobQueue } from '../../lib/job-queue' | |||
40 | import { CONFIG } from '../../initializers/config' | 40 | import { CONFIG } from '../../initializers/config' |
41 | import { sequelizeTypescript } from '../../initializers/database' | 41 | import { sequelizeTypescript } from '../../initializers/database' |
42 | import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail' | 42 | import { createPlaylistMiniatureFromExisting } from '../../lib/thumbnail' |
43 | import { VideoModel } from '../../models/video/video' | 43 | import { MVideoPlaylistFull, MVideoPlaylistThumbnail, MVideoThumbnail } from '@server/typings/models' |
44 | 44 | ||
45 | const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR }) | 45 | const reqThumbnailFile = createReqFiles([ 'thumbnailfile' ], MIMETYPES.IMAGE.MIMETYPE_EXT, { thumbnailfile: CONFIG.STORAGE.TMP_DIR }) |
46 | 46 | ||
@@ -58,7 +58,7 @@ videoPlaylistRouter.get('/', | |||
58 | ) | 58 | ) |
59 | 59 | ||
60 | videoPlaylistRouter.get('/:playlistId', | 60 | videoPlaylistRouter.get('/:playlistId', |
61 | asyncMiddleware(videoPlaylistsGetValidator), | 61 | asyncMiddleware(videoPlaylistsGetValidator('summary')), |
62 | getVideoPlaylist | 62 | getVideoPlaylist |
63 | ) | 63 | ) |
64 | 64 | ||
@@ -83,7 +83,7 @@ videoPlaylistRouter.delete('/:playlistId', | |||
83 | ) | 83 | ) |
84 | 84 | ||
85 | videoPlaylistRouter.get('/:playlistId/videos', | 85 | videoPlaylistRouter.get('/:playlistId/videos', |
86 | asyncMiddleware(videoPlaylistsGetValidator), | 86 | asyncMiddleware(videoPlaylistsGetValidator('summary')), |
87 | paginationValidator, | 87 | paginationValidator, |
88 | setDefaultPagination, | 88 | setDefaultPagination, |
89 | optionalAuthenticate, | 89 | optionalAuthenticate, |
@@ -140,7 +140,7 @@ async function listVideoPlaylists (req: express.Request, res: express.Response) | |||
140 | } | 140 | } |
141 | 141 | ||
142 | function getVideoPlaylist (req: express.Request, res: express.Response) { | 142 | function getVideoPlaylist (req: express.Request, res: express.Response) { |
143 | const videoPlaylist = res.locals.videoPlaylist | 143 | const videoPlaylist = res.locals.videoPlaylistSummary |
144 | 144 | ||
145 | if (videoPlaylist.isOutdated()) { | 145 | if (videoPlaylist.isOutdated()) { |
146 | JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } }) | 146 | JobQueue.Instance.createJob({ type: 'activitypub-refresher', payload: { type: 'video-playlist', url: videoPlaylist.url } }) |
@@ -159,7 +159,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) { | |||
159 | description: videoPlaylistInfo.description, | 159 | description: videoPlaylistInfo.description, |
160 | privacy: videoPlaylistInfo.privacy || VideoPlaylistPrivacy.PRIVATE, | 160 | privacy: videoPlaylistInfo.privacy || VideoPlaylistPrivacy.PRIVATE, |
161 | ownerAccountId: user.Account.id | 161 | ownerAccountId: user.Account.id |
162 | }) | 162 | }) as MVideoPlaylistFull |
163 | 163 | ||
164 | videoPlaylist.url = getVideoPlaylistActivityPubUrl(videoPlaylist) // We use the UUID, so set the URL after building the object | 164 | videoPlaylist.url = getVideoPlaylistActivityPubUrl(videoPlaylist) // We use the UUID, so set the URL after building the object |
165 | 165 | ||
@@ -175,8 +175,8 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) { | |||
175 | ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist, false) | 175 | ? await createPlaylistMiniatureFromExisting(thumbnailField[0].path, videoPlaylist, false) |
176 | : undefined | 176 | : undefined |
177 | 177 | ||
178 | const videoPlaylistCreated: VideoPlaylistModel = await sequelizeTypescript.transaction(async t => { | 178 | const videoPlaylistCreated = await sequelizeTypescript.transaction(async t => { |
179 | const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) | 179 | const videoPlaylistCreated = await videoPlaylist.save({ transaction: t }) as MVideoPlaylistFull |
180 | 180 | ||
181 | if (thumbnailModel) { | 181 | if (thumbnailModel) { |
182 | thumbnailModel.automaticallyGenerated = false | 182 | thumbnailModel.automaticallyGenerated = false |
@@ -201,7 +201,7 @@ async function addVideoPlaylist (req: express.Request, res: express.Response) { | |||
201 | } | 201 | } |
202 | 202 | ||
203 | async function updateVideoPlaylist (req: express.Request, res: express.Response) { | 203 | async function updateVideoPlaylist (req: express.Request, res: express.Response) { |
204 | const videoPlaylistInstance = res.locals.videoPlaylist | 204 | const videoPlaylistInstance = res.locals.videoPlaylistFull |
205 | const videoPlaylistFieldsSave = videoPlaylistInstance.toJSON() | 205 | const videoPlaylistFieldsSave = videoPlaylistInstance.toJSON() |
206 | const videoPlaylistInfoToUpdate = req.body as VideoPlaylistUpdate | 206 | const videoPlaylistInfoToUpdate = req.body as VideoPlaylistUpdate |
207 | 207 | ||
@@ -275,7 +275,7 @@ async function updateVideoPlaylist (req: express.Request, res: express.Response) | |||
275 | } | 275 | } |
276 | 276 | ||
277 | async function removeVideoPlaylist (req: express.Request, res: express.Response) { | 277 | async function removeVideoPlaylist (req: express.Request, res: express.Response) { |
278 | const videoPlaylistInstance = res.locals.videoPlaylist | 278 | const videoPlaylistInstance = res.locals.videoPlaylistSummary |
279 | 279 | ||
280 | await sequelizeTypescript.transaction(async t => { | 280 | await sequelizeTypescript.transaction(async t => { |
281 | await videoPlaylistInstance.destroy({ transaction: t }) | 281 | await videoPlaylistInstance.destroy({ transaction: t }) |
@@ -290,10 +290,10 @@ async function removeVideoPlaylist (req: express.Request, res: express.Response) | |||
290 | 290 | ||
291 | async function addVideoInPlaylist (req: express.Request, res: express.Response) { | 291 | async function addVideoInPlaylist (req: express.Request, res: express.Response) { |
292 | const body: VideoPlaylistElementCreate = req.body | 292 | const body: VideoPlaylistElementCreate = req.body |
293 | const videoPlaylist = res.locals.videoPlaylist | 293 | const videoPlaylist = res.locals.videoPlaylistFull |
294 | const video = res.locals.video | 294 | const video = res.locals.onlyVideo |
295 | 295 | ||
296 | const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => { | 296 | const playlistElement = await sequelizeTypescript.transaction(async t => { |
297 | const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t) | 297 | const position = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id, t) |
298 | 298 | ||
299 | const playlistElement = await VideoPlaylistElementModel.create({ | 299 | const playlistElement = await VideoPlaylistElementModel.create({ |
@@ -330,7 +330,7 @@ async function addVideoInPlaylist (req: express.Request, res: express.Response) | |||
330 | 330 | ||
331 | async function updateVideoPlaylistElement (req: express.Request, res: express.Response) { | 331 | async function updateVideoPlaylistElement (req: express.Request, res: express.Response) { |
332 | const body: VideoPlaylistElementUpdate = req.body | 332 | const body: VideoPlaylistElementUpdate = req.body |
333 | const videoPlaylist = res.locals.videoPlaylist | 333 | const videoPlaylist = res.locals.videoPlaylistFull |
334 | const videoPlaylistElement = res.locals.videoPlaylistElement | 334 | const videoPlaylistElement = res.locals.videoPlaylistElement |
335 | 335 | ||
336 | const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => { | 336 | const playlistElement: VideoPlaylistElementModel = await sequelizeTypescript.transaction(async t => { |
@@ -354,7 +354,7 @@ async function updateVideoPlaylistElement (req: express.Request, res: express.Re | |||
354 | 354 | ||
355 | async function removeVideoFromPlaylist (req: express.Request, res: express.Response) { | 355 | async function removeVideoFromPlaylist (req: express.Request, res: express.Response) { |
356 | const videoPlaylistElement = res.locals.videoPlaylistElement | 356 | const videoPlaylistElement = res.locals.videoPlaylistElement |
357 | const videoPlaylist = res.locals.videoPlaylist | 357 | const videoPlaylist = res.locals.videoPlaylistFull |
358 | const positionToDelete = videoPlaylistElement.position | 358 | const positionToDelete = videoPlaylistElement.position |
359 | 359 | ||
360 | await sequelizeTypescript.transaction(async t => { | 360 | await sequelizeTypescript.transaction(async t => { |
@@ -381,7 +381,7 @@ async function removeVideoFromPlaylist (req: express.Request, res: express.Respo | |||
381 | } | 381 | } |
382 | 382 | ||
383 | async function reorderVideosPlaylist (req: express.Request, res: express.Response) { | 383 | async function reorderVideosPlaylist (req: express.Request, res: express.Response) { |
384 | const videoPlaylist = res.locals.videoPlaylist | 384 | const videoPlaylist = res.locals.videoPlaylistFull |
385 | const body: VideoPlaylistReorder = req.body | 385 | const body: VideoPlaylistReorder = req.body |
386 | 386 | ||
387 | const start: number = body.startPosition | 387 | const start: number = body.startPosition |
@@ -434,7 +434,7 @@ async function reorderVideosPlaylist (req: express.Request, res: express.Respons | |||
434 | } | 434 | } |
435 | 435 | ||
436 | async function getVideoPlaylistVideos (req: express.Request, res: express.Response) { | 436 | async function getVideoPlaylistVideos (req: express.Request, res: express.Response) { |
437 | const videoPlaylistInstance = res.locals.videoPlaylist | 437 | const videoPlaylistInstance = res.locals.videoPlaylistSummary |
438 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined | 438 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined |
439 | const server = await getServerActor() | 439 | const server = await getServerActor() |
440 | 440 | ||
@@ -453,7 +453,7 @@ async function getVideoPlaylistVideos (req: express.Request, res: express.Respon | |||
453 | return res.json(getFormattedObjects(resultList.data, resultList.total, options)) | 453 | return res.json(getFormattedObjects(resultList.data, resultList.total, options)) |
454 | } | 454 | } |
455 | 455 | ||
456 | async function regeneratePlaylistThumbnail (videoPlaylist: VideoPlaylistModel) { | 456 | async function regeneratePlaylistThumbnail (videoPlaylist: MVideoPlaylistThumbnail) { |
457 | await videoPlaylist.Thumbnail.destroy() | 457 | await videoPlaylist.Thumbnail.destroy() |
458 | videoPlaylist.Thumbnail = null | 458 | videoPlaylist.Thumbnail = null |
459 | 459 | ||
@@ -461,7 +461,7 @@ async function regeneratePlaylistThumbnail (videoPlaylist: VideoPlaylistModel) { | |||
461 | if (firstElement) await generateThumbnailForPlaylist(videoPlaylist, firstElement.Video) | 461 | if (firstElement) await generateThumbnailForPlaylist(videoPlaylist, firstElement.Video) |
462 | } | 462 | } |
463 | 463 | ||
464 | async function generateThumbnailForPlaylist (videoPlaylist: VideoPlaylistModel, video: VideoModel) { | 464 | async function generateThumbnailForPlaylist (videoPlaylist: MVideoPlaylistThumbnail, video: MVideoThumbnail) { |
465 | logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url) | 465 | logger.info('Generating default thumbnail to playlist %s.', videoPlaylist.url) |
466 | 466 | ||
467 | const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename) | 467 | const inputPath = join(CONFIG.STORAGE.THUMBNAILS_DIR, video.getMiniature().filename) |
diff --git a/server/controllers/api/videos/abuse.ts b/server/controllers/api/videos/abuse.ts index 77808466c..4ae899b7e 100644 --- a/server/controllers/api/videos/abuse.ts +++ b/server/controllers/api/videos/abuse.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared' | 2 | import { UserRight, VideoAbuseCreate, VideoAbuseState } from '../../../../shared' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects } from '../../../helpers/utils' | 4 | import { getFormattedObjects, getServerActor } from '../../../helpers/utils' |
5 | import { sequelizeTypescript } from '../../../initializers' | 5 | import { sequelizeTypescript } from '../../../initializers' |
6 | import { | 6 | import { |
7 | asyncMiddleware, | 7 | asyncMiddleware, |
@@ -21,6 +21,7 @@ import { VideoAbuseModel } from '../../../models/video/video-abuse' | |||
21 | import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' | 21 | import { auditLoggerFactory, VideoAbuseAuditView } from '../../../helpers/audit-logger' |
22 | import { Notifier } from '../../../lib/notifier' | 22 | import { Notifier } from '../../../lib/notifier' |
23 | import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag' | 23 | import { sendVideoAbuse } from '../../../lib/activitypub/send/send-flag' |
24 | import { MVideoAbuseAccountVideo } from '../../../typings/models/video' | ||
24 | 25 | ||
25 | const auditLogger = auditLoggerFactory('abuse') | 26 | const auditLogger = auditLoggerFactory('abuse') |
26 | const abuseVideoRouter = express.Router() | 27 | const abuseVideoRouter = express.Router() |
@@ -61,7 +62,16 @@ export { | |||
61 | // --------------------------------------------------------------------------- | 62 | // --------------------------------------------------------------------------- |
62 | 63 | ||
63 | async function listVideoAbuses (req: express.Request, res: express.Response) { | 64 | async function listVideoAbuses (req: express.Request, res: express.Response) { |
64 | const resultList = await VideoAbuseModel.listForApi(req.query.start, req.query.count, req.query.sort) | 65 | const user = res.locals.oauth.token.user |
66 | const serverActor = await getServerActor() | ||
67 | |||
68 | const resultList = await VideoAbuseModel.listForApi({ | ||
69 | start: req.query.start, | ||
70 | count: req.query.count, | ||
71 | sort: req.query.sort, | ||
72 | serverAccountId: serverActor.Account.id, | ||
73 | user | ||
74 | }) | ||
65 | 75 | ||
66 | return res.json(getFormattedObjects(resultList.data, resultList.total)) | 76 | return res.json(getFormattedObjects(resultList.data, resultList.total)) |
67 | } | 77 | } |
@@ -94,10 +104,10 @@ async function deleteVideoAbuse (req: express.Request, res: express.Response) { | |||
94 | } | 104 | } |
95 | 105 | ||
96 | async function reportVideoAbuse (req: express.Request, res: express.Response) { | 106 | async function reportVideoAbuse (req: express.Request, res: express.Response) { |
97 | const videoInstance = res.locals.video | 107 | const videoInstance = res.locals.videoAll |
98 | const body: VideoAbuseCreate = req.body | 108 | const body: VideoAbuseCreate = req.body |
99 | 109 | ||
100 | const videoAbuse: VideoAbuseModel = await sequelizeTypescript.transaction(async t => { | 110 | const videoAbuse = await sequelizeTypescript.transaction(async t => { |
101 | const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) | 111 | const reporterAccount = await AccountModel.load(res.locals.oauth.token.User.Account.id, t) |
102 | 112 | ||
103 | const abuseToCreate = { | 113 | const abuseToCreate = { |
@@ -107,7 +117,7 @@ async function reportVideoAbuse (req: express.Request, res: express.Response) { | |||
107 | state: VideoAbuseState.PENDING | 117 | state: VideoAbuseState.PENDING |
108 | } | 118 | } |
109 | 119 | ||
110 | const videoAbuseInstance = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) | 120 | const videoAbuseInstance: MVideoAbuseAccountVideo = await VideoAbuseModel.create(abuseToCreate, { transaction: t }) |
111 | videoAbuseInstance.Video = videoInstance | 121 | videoAbuseInstance.Video = videoInstance |
112 | videoAbuseInstance.Account = reporterAccount | 122 | videoAbuseInstance.Account = reporterAccount |
113 | 123 | ||
diff --git a/server/controllers/api/videos/blacklist.ts b/server/controllers/api/videos/blacklist.ts index 9ff494def..2a667480d 100644 --- a/server/controllers/api/videos/blacklist.ts +++ b/server/controllers/api/videos/blacklist.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { VideoBlacklist, UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared' | 2 | import { UserRight, VideoBlacklistCreate, VideoBlacklistType } from '../../../../shared' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { getFormattedObjects } from '../../../helpers/utils' | 4 | import { getFormattedObjects } from '../../../helpers/utils' |
5 | import { | 5 | import { |
@@ -11,15 +11,16 @@ import { | |||
11 | setBlacklistSort, | 11 | setBlacklistSort, |
12 | setDefaultPagination, | 12 | setDefaultPagination, |
13 | videosBlacklistAddValidator, | 13 | videosBlacklistAddValidator, |
14 | videosBlacklistFiltersValidator, | ||
14 | videosBlacklistRemoveValidator, | 15 | videosBlacklistRemoveValidator, |
15 | videosBlacklistUpdateValidator, | 16 | videosBlacklistUpdateValidator |
16 | videosBlacklistFiltersValidator | ||
17 | } from '../../../middlewares' | 17 | } from '../../../middlewares' |
18 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' | 18 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' |
19 | import { sequelizeTypescript } from '../../../initializers' | 19 | import { sequelizeTypescript } from '../../../initializers' |
20 | import { Notifier } from '../../../lib/notifier' | 20 | import { Notifier } from '../../../lib/notifier' |
21 | import { sendDeleteVideo } from '../../../lib/activitypub/send' | 21 | import { sendDeleteVideo } from '../../../lib/activitypub/send' |
22 | import { federateVideoIfNeeded } from '../../../lib/activitypub' | 22 | import { federateVideoIfNeeded } from '../../../lib/activitypub' |
23 | import { MVideoBlacklistVideo } from '@server/typings/models' | ||
23 | 24 | ||
24 | const blacklistRouter = express.Router() | 25 | const blacklistRouter = express.Router() |
25 | 26 | ||
@@ -64,7 +65,7 @@ export { | |||
64 | // --------------------------------------------------------------------------- | 65 | // --------------------------------------------------------------------------- |
65 | 66 | ||
66 | async function addVideoToBlacklist (req: express.Request, res: express.Response) { | 67 | async function addVideoToBlacklist (req: express.Request, res: express.Response) { |
67 | const videoInstance = res.locals.video | 68 | const videoInstance = res.locals.videoAll |
68 | const body: VideoBlacklistCreate = req.body | 69 | const body: VideoBlacklistCreate = req.body |
69 | 70 | ||
70 | const toCreate = { | 71 | const toCreate = { |
@@ -74,7 +75,7 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response) | |||
74 | type: VideoBlacklistType.MANUAL | 75 | type: VideoBlacklistType.MANUAL |
75 | } | 76 | } |
76 | 77 | ||
77 | const blacklist = await VideoBlacklistModel.create(toCreate) | 78 | const blacklist: MVideoBlacklistVideo = await VideoBlacklistModel.create(toCreate) |
78 | blacklist.Video = videoInstance | 79 | blacklist.Video = videoInstance |
79 | 80 | ||
80 | if (body.unfederate === true) { | 81 | if (body.unfederate === true) { |
@@ -83,7 +84,7 @@ async function addVideoToBlacklist (req: express.Request, res: express.Response) | |||
83 | 84 | ||
84 | Notifier.Instance.notifyOnVideoBlacklist(blacklist) | 85 | Notifier.Instance.notifyOnVideoBlacklist(blacklist) |
85 | 86 | ||
86 | logger.info('Video %s blacklisted.', res.locals.video.uuid) | 87 | logger.info('Video %s blacklisted.', videoInstance.uuid) |
87 | 88 | ||
88 | return res.type('json').status(204).end() | 89 | return res.type('json').status(204).end() |
89 | } | 90 | } |
@@ -108,7 +109,7 @@ async function listBlacklist (req: express.Request, res: express.Response) { | |||
108 | 109 | ||
109 | async function removeVideoFromBlacklistController (req: express.Request, res: express.Response) { | 110 | async function removeVideoFromBlacklistController (req: express.Request, res: express.Response) { |
110 | const videoBlacklist = res.locals.videoBlacklist | 111 | const videoBlacklist = res.locals.videoBlacklist |
111 | const video = res.locals.video | 112 | const video = res.locals.videoAll |
112 | 113 | ||
113 | const videoBlacklistType = await sequelizeTypescript.transaction(async t => { | 114 | const videoBlacklistType = await sequelizeTypescript.transaction(async t => { |
114 | const unfederated = videoBlacklist.unfederated | 115 | const unfederated = videoBlacklist.unfederated |
@@ -135,7 +136,7 @@ async function removeVideoFromBlacklistController (req: express.Request, res: ex | |||
135 | Notifier.Instance.notifyOnNewVideoIfNeeded(video) | 136 | Notifier.Instance.notifyOnNewVideoIfNeeded(video) |
136 | } | 137 | } |
137 | 138 | ||
138 | logger.info('Video %s removed from blacklist.', res.locals.video.uuid) | 139 | logger.info('Video %s removed from blacklist.', video.uuid) |
139 | 140 | ||
140 | return res.type('json').status(204).end() | 141 | return res.type('json').status(204).end() |
141 | } | 142 | } |
diff --git a/server/controllers/api/videos/captions.ts b/server/controllers/api/videos/captions.ts index 44c255232..37481d12f 100644 --- a/server/controllers/api/videos/captions.ts +++ b/server/controllers/api/videos/captions.ts | |||
@@ -10,6 +10,7 @@ import { federateVideoIfNeeded } from '../../../lib/activitypub' | |||
10 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' | 10 | import { moveAndProcessCaptionFile } from '../../../helpers/captions-utils' |
11 | import { CONFIG } from '../../../initializers/config' | 11 | import { CONFIG } from '../../../initializers/config' |
12 | import { sequelizeTypescript } from '../../../initializers/database' | 12 | import { sequelizeTypescript } from '../../../initializers/database' |
13 | import { MVideoCaptionVideo } from '@server/typings/models' | ||
13 | 14 | ||
14 | const reqVideoCaptionAdd = createReqFiles( | 15 | const reqVideoCaptionAdd = createReqFiles( |
15 | [ 'captionfile' ], | 16 | [ 'captionfile' ], |
@@ -46,19 +47,19 @@ export { | |||
46 | // --------------------------------------------------------------------------- | 47 | // --------------------------------------------------------------------------- |
47 | 48 | ||
48 | async function listVideoCaptions (req: express.Request, res: express.Response) { | 49 | async function listVideoCaptions (req: express.Request, res: express.Response) { |
49 | const data = await VideoCaptionModel.listVideoCaptions(res.locals.video.id) | 50 | const data = await VideoCaptionModel.listVideoCaptions(res.locals.videoId.id) |
50 | 51 | ||
51 | return res.json(getFormattedObjects(data, data.length)) | 52 | return res.json(getFormattedObjects(data, data.length)) |
52 | } | 53 | } |
53 | 54 | ||
54 | async function addVideoCaption (req: express.Request, res: express.Response) { | 55 | async function addVideoCaption (req: express.Request, res: express.Response) { |
55 | const videoCaptionPhysicalFile = req.files['captionfile'][0] | 56 | const videoCaptionPhysicalFile = req.files['captionfile'][0] |
56 | const video = res.locals.video | 57 | const video = res.locals.videoAll |
57 | 58 | ||
58 | const videoCaption = new VideoCaptionModel({ | 59 | const videoCaption = new VideoCaptionModel({ |
59 | videoId: video.id, | 60 | videoId: video.id, |
60 | language: req.params.captionLanguage | 61 | language: req.params.captionLanguage |
61 | }) | 62 | }) as MVideoCaptionVideo |
62 | videoCaption.Video = video | 63 | videoCaption.Video = video |
63 | 64 | ||
64 | // Move physical file | 65 | // Move physical file |
@@ -75,7 +76,7 @@ async function addVideoCaption (req: express.Request, res: express.Response) { | |||
75 | } | 76 | } |
76 | 77 | ||
77 | async function deleteVideoCaption (req: express.Request, res: express.Response) { | 78 | async function deleteVideoCaption (req: express.Request, res: express.Response) { |
78 | const video = res.locals.video | 79 | const video = res.locals.videoAll |
79 | const videoCaption = res.locals.videoCaption | 80 | const videoCaption = res.locals.videoCaption |
80 | 81 | ||
81 | await sequelizeTypescript.transaction(async t => { | 82 | await sequelizeTypescript.transaction(async t => { |
diff --git a/server/controllers/api/videos/comment.ts b/server/controllers/api/videos/comment.ts index bc6d81a7c..b2b06b170 100644 --- a/server/controllers/api/videos/comment.ts +++ b/server/controllers/api/videos/comment.ts | |||
@@ -27,9 +27,6 @@ import { auditLoggerFactory, CommentAuditView, getAuditIdFromRes } from '../../. | |||
27 | import { AccountModel } from '../../../models/account/account' | 27 | import { AccountModel } from '../../../models/account/account' |
28 | import { Notifier } from '../../../lib/notifier' | 28 | import { Notifier } from '../../../lib/notifier' |
29 | import { Hooks } from '../../../lib/plugins/hooks' | 29 | import { Hooks } from '../../../lib/plugins/hooks' |
30 | import { ActorModel } from '../../../models/activitypub/actor' | ||
31 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
32 | import { VideoModel } from '../../../models/video/video' | ||
33 | import { sendDeleteVideoComment } from '../../../lib/activitypub/send' | 30 | import { sendDeleteVideoComment } from '../../../lib/activitypub/send' |
34 | 31 | ||
35 | const auditLogger = auditLoggerFactory('comments') | 32 | const auditLogger = auditLoggerFactory('comments') |
@@ -75,7 +72,7 @@ export { | |||
75 | // --------------------------------------------------------------------------- | 72 | // --------------------------------------------------------------------------- |
76 | 73 | ||
77 | async function listVideoThreads (req: express.Request, res: express.Response) { | 74 | async function listVideoThreads (req: express.Request, res: express.Response) { |
78 | const video = res.locals.video | 75 | const video = res.locals.onlyVideo |
79 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined | 76 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined |
80 | 77 | ||
81 | let resultList: ResultList<VideoCommentModel> | 78 | let resultList: ResultList<VideoCommentModel> |
@@ -86,7 +83,7 @@ async function listVideoThreads (req: express.Request, res: express.Response) { | |||
86 | start: req.query.start, | 83 | start: req.query.start, |
87 | count: req.query.count, | 84 | count: req.query.count, |
88 | sort: req.query.sort, | 85 | sort: req.query.sort, |
89 | user: user | 86 | user |
90 | }, 'filter:api.video-threads.list.params') | 87 | }, 'filter:api.video-threads.list.params') |
91 | 88 | ||
92 | resultList = await Hooks.wrapPromiseFun( | 89 | resultList = await Hooks.wrapPromiseFun( |
@@ -105,7 +102,7 @@ async function listVideoThreads (req: express.Request, res: express.Response) { | |||
105 | } | 102 | } |
106 | 103 | ||
107 | async function listVideoThreadComments (req: express.Request, res: express.Response) { | 104 | async function listVideoThreadComments (req: express.Request, res: express.Response) { |
108 | const video = res.locals.video | 105 | const video = res.locals.onlyVideo |
109 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined | 106 | const user = res.locals.oauth ? res.locals.oauth.token.User : undefined |
110 | 107 | ||
111 | let resultList: ResultList<VideoCommentModel> | 108 | let resultList: ResultList<VideoCommentModel> |
@@ -141,7 +138,7 @@ async function addVideoCommentThread (req: express.Request, res: express.Respons | |||
141 | return createVideoComment({ | 138 | return createVideoComment({ |
142 | text: videoCommentInfo.text, | 139 | text: videoCommentInfo.text, |
143 | inReplyToComment: null, | 140 | inReplyToComment: null, |
144 | video: res.locals.video, | 141 | video: res.locals.videoAll, |
145 | account | 142 | account |
146 | }, t) | 143 | }, t) |
147 | }) | 144 | }) |
@@ -164,8 +161,8 @@ async function addVideoCommentReply (req: express.Request, res: express.Response | |||
164 | 161 | ||
165 | return createVideoComment({ | 162 | return createVideoComment({ |
166 | text: videoCommentInfo.text, | 163 | text: videoCommentInfo.text, |
167 | inReplyToComment: res.locals.videoComment, | 164 | inReplyToComment: res.locals.videoCommentFull, |
168 | video: res.locals.video, | 165 | video: res.locals.videoAll, |
169 | account | 166 | account |
170 | }, t) | 167 | }, t) |
171 | }) | 168 | }) |
@@ -179,7 +176,7 @@ async function addVideoCommentReply (req: express.Request, res: express.Response | |||
179 | } | 176 | } |
180 | 177 | ||
181 | async function removeVideoComment (req: express.Request, res: express.Response) { | 178 | async function removeVideoComment (req: express.Request, res: express.Response) { |
182 | const videoCommentInstance = res.locals.videoComment | 179 | const videoCommentInstance = res.locals.videoCommentFull |
183 | 180 | ||
184 | await sequelizeTypescript.transaction(async t => { | 181 | await sequelizeTypescript.transaction(async t => { |
185 | await videoCommentInstance.destroy({ transaction: t }) | 182 | await videoCommentInstance.destroy({ transaction: t }) |
diff --git a/server/controllers/api/videos/import.ts b/server/controllers/api/videos/import.ts index 04c9b547b..28ced5836 100644 --- a/server/controllers/api/videos/import.ts +++ b/server/controllers/api/videos/import.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import * as magnetUtil from 'magnet-uri' | 2 | import * as magnetUtil from 'magnet-uri' |
3 | import 'multer' | ||
4 | import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' | 3 | import { auditLoggerFactory, getAuditIdFromRes, VideoImportAuditView } from '../../../helpers/audit-logger' |
5 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' | 4 | import { asyncMiddleware, asyncRetryTransactionMiddleware, authenticate, videoImportAddValidator } from '../../../middlewares' |
6 | import { MIMETYPES } from '../../../initializers/constants' | 5 | import { MIMETYPES } from '../../../initializers/constants' |
@@ -15,7 +14,6 @@ import { VideoImportModel } from '../../../models/video/video-import' | |||
15 | import { JobQueue } from '../../../lib/job-queue/job-queue' | 14 | import { JobQueue } from '../../../lib/job-queue/job-queue' |
16 | import { join } from 'path' | 15 | import { join } from 'path' |
17 | import { isArray } from '../../../helpers/custom-validators/misc' | 16 | import { isArray } from '../../../helpers/custom-validators/misc' |
18 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
19 | import * as Bluebird from 'bluebird' | 17 | import * as Bluebird from 'bluebird' |
20 | import * as parseTorrent from 'parse-torrent' | 18 | import * as parseTorrent from 'parse-torrent' |
21 | import { getSecureTorrentName } from '../../../helpers/utils' | 19 | import { getSecureTorrentName } from '../../../helpers/utils' |
@@ -25,8 +23,16 @@ import { CONFIG } from '../../../initializers/config' | |||
25 | import { sequelizeTypescript } from '../../../initializers/database' | 23 | import { sequelizeTypescript } from '../../../initializers/database' |
26 | import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail' | 24 | import { createVideoMiniatureFromExisting } from '../../../lib/thumbnail' |
27 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | 25 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' |
28 | import { ThumbnailModel } from '../../../models/video/thumbnail' | 26 | import { |
29 | import { UserModel } from '../../../models/account/user' | 27 | MChannelAccountDefault, |
28 | MThumbnail, | ||
29 | MUser, | ||
30 | MVideoAccountDefault, | ||
31 | MVideoTag, | ||
32 | MVideoThumbnailAccountDefault, | ||
33 | MVideoWithBlacklistLight | ||
34 | } from '@server/typings/models' | ||
35 | import { MVideoImport, MVideoImportFormattable } from '@server/typings/models/video/video-import' | ||
30 | 36 | ||
31 | const auditLogger = auditLoggerFactory('video-imports') | 37 | const auditLogger = auditLoggerFactory('video-imports') |
32 | const videoImportsRouter = express.Router() | 38 | const videoImportsRouter = express.Router() |
@@ -184,8 +190,8 @@ function buildVideo (channelId: number, body: VideoImportCreate, importData: You | |||
184 | category: body.category || importData.category, | 190 | category: body.category || importData.category, |
185 | licence: body.licence || importData.licence, | 191 | licence: body.licence || importData.licence, |
186 | language: body.language || undefined, | 192 | language: body.language || undefined, |
187 | commentsEnabled: body.commentsEnabled || true, | 193 | commentsEnabled: body.commentsEnabled !== false, // If the value is not "false", the default is "true" |
188 | downloadEnabled: body.downloadEnabled || true, | 194 | downloadEnabled: body.downloadEnabled !== false, |
189 | waitTranscoding: body.waitTranscoding || false, | 195 | waitTranscoding: body.waitTranscoding || false, |
190 | state: VideoState.TO_IMPORT, | 196 | state: VideoState.TO_IMPORT, |
191 | nsfw: body.nsfw || importData.nsfw || false, | 197 | nsfw: body.nsfw || importData.nsfw || false, |
@@ -225,28 +231,28 @@ async function processPreview (req: express.Request, video: VideoModel) { | |||
225 | } | 231 | } |
226 | 232 | ||
227 | function insertIntoDB (parameters: { | 233 | function insertIntoDB (parameters: { |
228 | video: VideoModel, | 234 | video: MVideoThumbnailAccountDefault, |
229 | thumbnailModel: ThumbnailModel, | 235 | thumbnailModel: MThumbnail, |
230 | previewModel: ThumbnailModel, | 236 | previewModel: MThumbnail, |
231 | videoChannel: VideoChannelModel, | 237 | videoChannel: MChannelAccountDefault, |
232 | tags: string[], | 238 | tags: string[], |
233 | videoImportAttributes: Partial<VideoImportModel>, | 239 | videoImportAttributes: Partial<MVideoImport>, |
234 | user: UserModel | 240 | user: MUser |
235 | }): Bluebird<VideoImportModel> { | 241 | }): Bluebird<MVideoImportFormattable> { |
236 | const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters | 242 | const { video, thumbnailModel, previewModel, videoChannel, tags, videoImportAttributes, user } = parameters |
237 | 243 | ||
238 | return sequelizeTypescript.transaction(async t => { | 244 | return sequelizeTypescript.transaction(async t => { |
239 | const sequelizeOptions = { transaction: t } | 245 | const sequelizeOptions = { transaction: t } |
240 | 246 | ||
241 | // Save video object in database | 247 | // Save video object in database |
242 | const videoCreated = await video.save(sequelizeOptions) | 248 | const videoCreated = await video.save(sequelizeOptions) as (MVideoAccountDefault & MVideoWithBlacklistLight & MVideoTag) |
243 | videoCreated.VideoChannel = videoChannel | 249 | videoCreated.VideoChannel = videoChannel |
244 | 250 | ||
245 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) | 251 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) |
246 | if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t) | 252 | if (previewModel) await videoCreated.addAndSaveThumbnail(previewModel, t) |
247 | 253 | ||
248 | await autoBlacklistVideoIfNeeded({ | 254 | await autoBlacklistVideoIfNeeded({ |
249 | video, | 255 | video: videoCreated, |
250 | user, | 256 | user, |
251 | notify: false, | 257 | notify: false, |
252 | isRemote: false, | 258 | isRemote: false, |
@@ -268,7 +274,7 @@ function insertIntoDB (parameters: { | |||
268 | const videoImport = await VideoImportModel.create( | 274 | const videoImport = await VideoImportModel.create( |
269 | Object.assign({ videoId: videoCreated.id }, videoImportAttributes), | 275 | Object.assign({ videoId: videoCreated.id }, videoImportAttributes), |
270 | sequelizeOptions | 276 | sequelizeOptions |
271 | ) | 277 | ) as MVideoImportFormattable |
272 | videoImport.Video = videoCreated | 278 | videoImport.Video = videoCreated |
273 | 279 | ||
274 | return videoImport | 280 | return videoImport |
diff --git a/server/controllers/api/videos/index.ts b/server/controllers/api/videos/index.ts index 155ca4678..19da504c7 100644 --- a/server/controllers/api/videos/index.ts +++ b/server/controllers/api/videos/index.ts | |||
@@ -63,6 +63,7 @@ import { createVideoMiniatureFromExisting, generateVideoMiniature } from '../../ | |||
63 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | 63 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' |
64 | import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding' | 64 | import { VideoTranscodingPayload } from '../../../lib/job-queue/handlers/video-transcoding' |
65 | import { Hooks } from '../../../lib/plugins/hooks' | 65 | import { Hooks } from '../../../lib/plugins/hooks' |
66 | import { MVideoDetails, MVideoFullLight } from '@server/typings/models' | ||
66 | 67 | ||
67 | const auditLogger = auditLoggerFactory('videos') | 68 | const auditLogger = auditLoggerFactory('videos') |
68 | const videosRouter = express.Router() | 69 | const videosRouter = express.Router() |
@@ -185,7 +186,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
185 | licence: videoInfo.licence, | 186 | licence: videoInfo.licence, |
186 | language: videoInfo.language, | 187 | language: videoInfo.language, |
187 | commentsEnabled: videoInfo.commentsEnabled || false, | 188 | commentsEnabled: videoInfo.commentsEnabled || false, |
188 | downloadEnabled: videoInfo.downloadEnabled || true, | 189 | downloadEnabled: videoInfo.downloadEnabled !== false, // If the value is not "false", the default is "true" |
189 | waitTranscoding: videoInfo.waitTranscoding || false, | 190 | waitTranscoding: videoInfo.waitTranscoding || false, |
190 | state: CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED, | 191 | state: CONFIG.TRANSCODING.ENABLED ? VideoState.TO_TRANSCODE : VideoState.PUBLISHED, |
191 | nsfw: videoInfo.nsfw || false, | 192 | nsfw: videoInfo.nsfw || false, |
@@ -197,7 +198,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
197 | originallyPublishedAt: videoInfo.originallyPublishedAt | 198 | originallyPublishedAt: videoInfo.originallyPublishedAt |
198 | } | 199 | } |
199 | 200 | ||
200 | const video = new VideoModel(videoData) | 201 | const video = new VideoModel(videoData) as MVideoDetails |
201 | video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object | 202 | video.url = getVideoActivityPubUrl(video) // We use the UUID, so set the URL after building the object |
202 | 203 | ||
203 | const videoFile = new VideoFileModel({ | 204 | const videoFile = new VideoFileModel({ |
@@ -238,7 +239,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
238 | const { videoCreated } = await sequelizeTypescript.transaction(async t => { | 239 | const { videoCreated } = await sequelizeTypescript.transaction(async t => { |
239 | const sequelizeOptions = { transaction: t } | 240 | const sequelizeOptions = { transaction: t } |
240 | 241 | ||
241 | const videoCreated = await video.save(sequelizeOptions) | 242 | const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight |
242 | 243 | ||
243 | await videoCreated.addAndSaveThumbnail(thumbnailModel, t) | 244 | await videoCreated.addAndSaveThumbnail(thumbnailModel, t) |
244 | await videoCreated.addAndSaveThumbnail(previewModel, t) | 245 | await videoCreated.addAndSaveThumbnail(previewModel, t) |
@@ -318,7 +319,7 @@ async function addVideo (req: express.Request, res: express.Response) { | |||
318 | } | 319 | } |
319 | 320 | ||
320 | async function updateVideo (req: express.Request, res: express.Response) { | 321 | async function updateVideo (req: express.Request, res: express.Response) { |
321 | const videoInstance = res.locals.video | 322 | const videoInstance = res.locals.videoAll |
322 | const videoFieldsSave = videoInstance.toJSON() | 323 | const videoFieldsSave = videoInstance.toJSON() |
323 | const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) | 324 | const oldVideoAuditView = new VideoAuditView(videoInstance.toFormattedDetailsJSON()) |
324 | const videoInfoToUpdate: VideoUpdate = req.body | 325 | const videoInfoToUpdate: VideoUpdate = req.body |
@@ -371,7 +372,7 @@ async function updateVideo (req: express.Request, res: express.Response) { | |||
371 | } | 372 | } |
372 | } | 373 | } |
373 | 374 | ||
374 | const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) | 375 | const videoInstanceUpdated = await videoInstance.save(sequelizeOptions) as MVideoFullLight |
375 | 376 | ||
376 | if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t) | 377 | if (thumbnailModel) await videoInstanceUpdated.addAndSaveThumbnail(thumbnailModel, t) |
377 | if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t) | 378 | if (previewModel) await videoInstanceUpdated.addAndSaveThumbnail(previewModel, t) |
@@ -447,7 +448,7 @@ async function getVideo (req: express.Request, res: express.Response) { | |||
447 | 448 | ||
448 | const video = await Hooks.wrapPromiseFun( | 449 | const video = await Hooks.wrapPromiseFun( |
449 | VideoModel.loadForGetAPI, | 450 | VideoModel.loadForGetAPI, |
450 | { id: res.locals.video.id, userId }, | 451 | { id: res.locals.onlyVideoWithRights.id, userId }, |
451 | 'filter:api.video.get.result' | 452 | 'filter:api.video.get.result' |
452 | ) | 453 | ) |
453 | 454 | ||
@@ -460,7 +461,7 @@ async function getVideo (req: express.Request, res: express.Response) { | |||
460 | } | 461 | } |
461 | 462 | ||
462 | async function viewVideo (req: express.Request, res: express.Response) { | 463 | async function viewVideo (req: express.Request, res: express.Response) { |
463 | const videoInstance = res.locals.video | 464 | const videoInstance = res.locals.videoAll |
464 | 465 | ||
465 | const ip = req.ip | 466 | const ip = req.ip |
466 | const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid) | 467 | const exists = await Redis.Instance.doesVideoIPViewExist(ip, videoInstance.uuid) |
@@ -483,7 +484,7 @@ async function viewVideo (req: express.Request, res: express.Response) { | |||
483 | } | 484 | } |
484 | 485 | ||
485 | async function getVideoDescription (req: express.Request, res: express.Response) { | 486 | async function getVideoDescription (req: express.Request, res: express.Response) { |
486 | const videoInstance = res.locals.video | 487 | const videoInstance = res.locals.videoAll |
487 | let description = '' | 488 | let description = '' |
488 | 489 | ||
489 | if (videoInstance.isOwned()) { | 490 | if (videoInstance.isOwned()) { |
@@ -522,7 +523,7 @@ async function listVideos (req: express.Request, res: express.Response) { | |||
522 | } | 523 | } |
523 | 524 | ||
524 | async function removeVideo (req: express.Request, res: express.Response) { | 525 | async function removeVideo (req: express.Request, res: express.Response) { |
525 | const videoInstance = res.locals.video | 526 | const videoInstance = res.locals.videoAll |
526 | 527 | ||
527 | await sequelizeTypescript.transaction(async t => { | 528 | await sequelizeTypescript.transaction(async t => { |
528 | await videoInstance.destroy({ transaction: t }) | 529 | await videoInstance.destroy({ transaction: t }) |
diff --git a/server/controllers/api/videos/ownership.ts b/server/controllers/api/videos/ownership.ts index 5272c1385..abb34082e 100644 --- a/server/controllers/api/videos/ownership.ts +++ b/server/controllers/api/videos/ownership.ts | |||
@@ -18,6 +18,7 @@ import { getFormattedObjects } from '../../../helpers/utils' | |||
18 | import { changeVideoChannelShare } from '../../../lib/activitypub' | 18 | import { changeVideoChannelShare } from '../../../lib/activitypub' |
19 | import { sendUpdateVideo } from '../../../lib/activitypub/send' | 19 | import { sendUpdateVideo } from '../../../lib/activitypub/send' |
20 | import { VideoModel } from '../../../models/video/video' | 20 | import { VideoModel } from '../../../models/video/video' |
21 | import { MVideoFullLight } from '@server/typings/models' | ||
21 | 22 | ||
22 | const ownershipVideoRouter = express.Router() | 23 | const ownershipVideoRouter = express.Router() |
23 | 24 | ||
@@ -56,7 +57,7 @@ export { | |||
56 | // --------------------------------------------------------------------------- | 57 | // --------------------------------------------------------------------------- |
57 | 58 | ||
58 | async function giveVideoOwnership (req: express.Request, res: express.Response) { | 59 | async function giveVideoOwnership (req: express.Request, res: express.Response) { |
59 | const videoInstance = res.locals.video | 60 | const videoInstance = res.locals.videoAll |
60 | const initiatorAccountId = res.locals.oauth.token.User.Account.id | 61 | const initiatorAccountId = res.locals.oauth.token.User.Account.id |
61 | const nextOwner = res.locals.nextOwner | 62 | const nextOwner = res.locals.nextOwner |
62 | 63 | ||
@@ -107,7 +108,7 @@ async function acceptOwnership (req: express.Request, res: express.Response) { | |||
107 | 108 | ||
108 | targetVideo.channelId = channel.id | 109 | targetVideo.channelId = channel.id |
109 | 110 | ||
110 | const targetVideoUpdated = await targetVideo.save({ transaction: t }) | 111 | const targetVideoUpdated = await targetVideo.save({ transaction: t }) as MVideoFullLight |
111 | targetVideoUpdated.VideoChannel = channel | 112 | targetVideoUpdated.VideoChannel = channel |
112 | 113 | ||
113 | if (targetVideoUpdated.privacy !== VideoPrivacy.PRIVATE && targetVideoUpdated.state === VideoState.PUBLISHED) { | 114 | if (targetVideoUpdated.privacy !== VideoPrivacy.PRIVATE && targetVideoUpdated.state === VideoState.PUBLISHED) { |
diff --git a/server/controllers/api/videos/rate.ts b/server/controllers/api/videos/rate.ts index b65babedf..3d2f3d728 100644 --- a/server/controllers/api/videos/rate.ts +++ b/server/controllers/api/videos/rate.ts | |||
@@ -27,7 +27,7 @@ export { | |||
27 | async function rateVideo (req: express.Request, res: express.Response) { | 27 | async function rateVideo (req: express.Request, res: express.Response) { |
28 | const body: UserVideoRateUpdate = req.body | 28 | const body: UserVideoRateUpdate = req.body |
29 | const rateType = body.rating | 29 | const rateType = body.rating |
30 | const videoInstance = res.locals.video | 30 | const videoInstance = res.locals.videoAll |
31 | const userAccount = res.locals.oauth.token.User.Account | 31 | const userAccount = res.locals.oauth.token.User.Account |
32 | 32 | ||
33 | await sequelizeTypescript.transaction(async t => { | 33 | await sequelizeTypescript.transaction(async t => { |
diff --git a/server/controllers/api/videos/watching.ts b/server/controllers/api/videos/watching.ts index dcd1f070d..036e16f3a 100644 --- a/server/controllers/api/videos/watching.ts +++ b/server/controllers/api/videos/watching.ts | |||
@@ -23,7 +23,7 @@ async function userWatchVideo (req: express.Request, res: express.Response) { | |||
23 | const user = res.locals.oauth.token.User | 23 | const user = res.locals.oauth.token.User |
24 | 24 | ||
25 | const body: UserWatchingVideo = req.body | 25 | const body: UserWatchingVideo = req.body |
26 | const { id: videoId } = res.locals.video as { id: number } | 26 | const { id: videoId } = res.locals.videoId |
27 | 27 | ||
28 | await UserVideoHistoryModel.upsert({ | 28 | await UserVideoHistoryModel.upsert({ |
29 | videoId, | 29 | videoId, |
diff --git a/server/controllers/feeds.ts b/server/controllers/feeds.ts index d3f581615..468f7a668 100644 --- a/server/controllers/feeds.ts +++ b/server/controllers/feeds.ts | |||
@@ -43,7 +43,7 @@ export { | |||
43 | async function generateVideoCommentsFeed (req: express.Request, res: express.Response) { | 43 | async function generateVideoCommentsFeed (req: express.Request, res: express.Response) { |
44 | const start = 0 | 44 | const start = 0 |
45 | 45 | ||
46 | const video = res.locals.video | 46 | const video = res.locals.videoAll |
47 | const videoId: number = video ? video.id : undefined | 47 | const videoId: number = video ? video.id : undefined |
48 | 48 | ||
49 | const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId) | 49 | const comments = await VideoCommentModel.listForFeed(start, FEEDS.COUNT, videoId) |
diff --git a/server/controllers/services.ts b/server/controllers/services.ts index c1c53c3fc..ec057235f 100644 --- a/server/controllers/services.ts +++ b/server/controllers/services.ts | |||
@@ -23,7 +23,7 @@ export { | |||
23 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
24 | 24 | ||
25 | function generateOEmbed (req: express.Request, res: express.Response) { | 25 | function generateOEmbed (req: express.Request, res: express.Response) { |
26 | const video = res.locals.video | 26 | const video = res.locals.videoAll |
27 | const webserverUrl = WEBSERVER.URL | 27 | const webserverUrl = WEBSERVER.URL |
28 | const maxHeight = parseInt(req.query.maxheight, 10) | 28 | const maxHeight = parseInt(req.query.maxheight, 10) |
29 | const maxWidth = parseInt(req.query.maxwidth, 10) | 29 | const maxWidth = parseInt(req.query.maxwidth, 10) |
diff --git a/server/controllers/static.ts b/server/controllers/static.ts index 8979ef5f3..0f4772310 100644 --- a/server/controllers/static.ts +++ b/server/controllers/static.ts | |||
@@ -226,14 +226,14 @@ async function generateNodeinfo (req: express.Request, res: express.Response) { | |||
226 | return res.send(json).end() | 226 | return res.send(json).end() |
227 | } | 227 | } |
228 | 228 | ||
229 | async function downloadTorrent (req: express.Request, res: express.Response, next: express.NextFunction) { | 229 | async function downloadTorrent (req: express.Request, res: express.Response) { |
230 | const { video, videoFile } = getVideoAndFile(req, res) | 230 | const { video, videoFile } = getVideoAndFile(req, res) |
231 | if (!videoFile) return res.status(404).end() | 231 | if (!videoFile) return res.status(404).end() |
232 | 232 | ||
233 | return res.download(video.getTorrentFilePath(videoFile), `${video.name}-${videoFile.resolution}p.torrent`) | 233 | return res.download(video.getTorrentFilePath(videoFile), `${video.name}-${videoFile.resolution}p.torrent`) |
234 | } | 234 | } |
235 | 235 | ||
236 | async function downloadVideoFile (req: express.Request, res: express.Response, next: express.NextFunction) { | 236 | async function downloadVideoFile (req: express.Request, res: express.Response) { |
237 | const { video, videoFile } = getVideoAndFile(req, res) | 237 | const { video, videoFile } = getVideoAndFile(req, res) |
238 | if (!videoFile) return res.status(404).end() | 238 | if (!videoFile) return res.status(404).end() |
239 | 239 | ||
@@ -242,7 +242,7 @@ async function downloadVideoFile (req: express.Request, res: express.Response, n | |||
242 | 242 | ||
243 | function getVideoAndFile (req: express.Request, res: express.Response) { | 243 | function getVideoAndFile (req: express.Request, res: express.Response) { |
244 | const resolution = parseInt(req.params.resolution, 10) | 244 | const resolution = parseInt(req.params.resolution, 10) |
245 | const video = res.locals.video | 245 | const video = res.locals.videoAll |
246 | 246 | ||
247 | const videoFile = video.VideoFiles.find(f => f.resolution === resolution) | 247 | const videoFile = video.VideoFiles.find(f => f.resolution === resolution) |
248 | 248 | ||
diff --git a/server/controllers/webfinger.ts b/server/controllers/webfinger.ts index f2ba3c826..fc9575160 100644 --- a/server/controllers/webfinger.ts +++ b/server/controllers/webfinger.ts | |||
@@ -18,7 +18,7 @@ export { | |||
18 | // --------------------------------------------------------------------------- | 18 | // --------------------------------------------------------------------------- |
19 | 19 | ||
20 | function webfingerController (req: express.Request, res: express.Response) { | 20 | function webfingerController (req: express.Request, res: express.Response) { |
21 | const actor = res.locals.actor | 21 | const actor = res.locals.actorFull |
22 | 22 | ||
23 | const json = { | 23 | const json = { |
24 | subject: req.query.resource, | 24 | subject: req.query.resource, |
diff --git a/server/helpers/activitypub.ts b/server/helpers/activitypub.ts index 951a25669..97c809a0c 100644 --- a/server/helpers/activitypub.ts +++ b/server/helpers/activitypub.ts | |||
@@ -7,6 +7,7 @@ import { ActorModel } from '../models/activitypub/actor' | |||
7 | import { signJsonLDObject } from './peertube-crypto' | 7 | import { signJsonLDObject } from './peertube-crypto' |
8 | import { pageToStartAndCount } from './core-utils' | 8 | import { pageToStartAndCount } from './core-utils' |
9 | import { parse } from 'url' | 9 | import { parse } from 'url' |
10 | import { MActor } from '../typings/models' | ||
10 | 11 | ||
11 | function activityPubContextify <T> (data: T) { | 12 | function activityPubContextify <T> (data: T) { |
12 | return Object.assign(data, { | 13 | return Object.assign(data, { |
@@ -143,7 +144,7 @@ async function activityPubCollectionPagination (baseUrl: string, handler: Activi | |||
143 | 144 | ||
144 | } | 145 | } |
145 | 146 | ||
146 | function buildSignedActivity (byActor: ActorModel, data: Object) { | 147 | function buildSignedActivity (byActor: MActor, data: Object) { |
147 | const activity = activityPubContextify(data) | 148 | const activity = activityPubContextify(data) |
148 | 149 | ||
149 | return signJsonLDObject(byActor, activity) as Promise<Activity> | 150 | return signJsonLDObject(byActor, activity) as Promise<Activity> |
diff --git a/server/helpers/actor.ts b/server/helpers/actor.ts index 12a7ace9f..117548a60 100644 --- a/server/helpers/actor.ts +++ b/server/helpers/actor.ts | |||
@@ -1,10 +1,13 @@ | |||
1 | import { ActorModel } from '../models/activitypub/actor' | 1 | import { ActorModel } from '../models/activitypub/actor' |
2 | import * as Bluebird from 'bluebird' | ||
3 | import { MActorFull, MActorAccountChannelId } from '../typings/models' | ||
2 | 4 | ||
3 | type ActorFetchByUrlType = 'all' | 'actor-and-association-ids' | 5 | type ActorFetchByUrlType = 'all' | 'association-ids' |
4 | function fetchActorByUrl (url: string, fetchType: ActorFetchByUrlType) { | 6 | |
7 | function fetchActorByUrl (url: string, fetchType: ActorFetchByUrlType): Bluebird<MActorFull | MActorAccountChannelId> { | ||
5 | if (fetchType === 'all') return ActorModel.loadByUrlAndPopulateAccountAndChannel(url) | 8 | if (fetchType === 'all') return ActorModel.loadByUrlAndPopulateAccountAndChannel(url) |
6 | 9 | ||
7 | if (fetchType === 'actor-and-association-ids') return ActorModel.loadByUrl(url) | 10 | if (fetchType === 'association-ids') return ActorModel.loadByUrl(url) |
8 | } | 11 | } |
9 | 12 | ||
10 | export { | 13 | export { |
diff --git a/server/helpers/captions-utils.ts b/server/helpers/captions-utils.ts index 7174d4654..2830ae017 100644 --- a/server/helpers/captions-utils.ts +++ b/server/helpers/captions-utils.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { join } from 'path' | 1 | import { join } from 'path' |
2 | import { CONFIG } from '../initializers/config' | 2 | import { CONFIG } from '../initializers/config' |
3 | import { VideoCaptionModel } from '../models/video/video-caption' | ||
4 | import * as srt2vtt from 'srt-to-vtt' | 3 | import * as srt2vtt from 'srt-to-vtt' |
5 | import { createReadStream, createWriteStream, remove, move } from 'fs-extra' | 4 | import { createReadStream, createWriteStream, move, remove } from 'fs-extra' |
5 | import { MVideoCaptionFormattable } from '@server/typings/models' | ||
6 | 6 | ||
7 | async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: VideoCaptionModel) { | 7 | async function moveAndProcessCaptionFile (physicalFile: { filename: string, path: string }, videoCaption: MVideoCaptionFormattable) { |
8 | const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR | 8 | const videoCaptionsDir = CONFIG.STORAGE.CAPTIONS_DIR |
9 | const destination = join(videoCaptionsDir, videoCaption.getCaptionName()) | 9 | const destination = join(videoCaptionsDir, videoCaption.getCaptionName()) |
10 | 10 | ||
diff --git a/server/helpers/custom-jsonld-signature.ts b/server/helpers/custom-jsonld-signature.ts index a3bceb047..cb07fa3b2 100644 --- a/server/helpers/custom-jsonld-signature.ts +++ b/server/helpers/custom-jsonld-signature.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import * as AsyncLRU from 'async-lru' | 1 | import * as AsyncLRU from 'async-lru' |
2 | import * as jsonld from 'jsonld' | 2 | import * as jsonld from 'jsonld' |
3 | import * as jsig from 'jsonld-signatures' | ||
4 | import { logger } from './logger' | 3 | import { logger } from './logger' |
5 | 4 | ||
6 | const CACHE = { | 5 | const CACHE = { |
@@ -79,6 +78,4 @@ jsonld.documentLoader = (url, cb) => { | |||
79 | lru.get(url, cb) | 78 | lru.get(url, cb) |
80 | } | 79 | } |
81 | 80 | ||
82 | jsig.use('jsonld', jsonld) | 81 | export { jsonld } |
83 | |||
84 | export { jsig, jsonld } | ||
diff --git a/server/helpers/custom-validators/activitypub/actor.ts b/server/helpers/custom-validators/activitypub/actor.ts index deb331abb..55bc8cc96 100644 --- a/server/helpers/custom-validators/activitypub/actor.ts +++ b/server/helpers/custom-validators/activitypub/actor.ts | |||
@@ -27,7 +27,7 @@ function isActorPublicKeyValid (publicKey: string) { | |||
27 | validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY) | 27 | validator.isLength(publicKey, CONSTRAINTS_FIELDS.ACTORS.PUBLIC_KEY) |
28 | } | 28 | } |
29 | 29 | ||
30 | const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.]' | 30 | const actorNameAlphabet = '[ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\\-_.:]' |
31 | const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`) | 31 | const actorNameRegExp = new RegExp(`^${actorNameAlphabet}+$`) |
32 | function isActorPreferredUsernameValid (preferredUsername: string) { | 32 | function isActorPreferredUsernameValid (preferredUsername: string) { |
33 | return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp) | 33 | return exists(preferredUsername) && validator.matches(preferredUsername, actorNameRegExp) |
@@ -46,19 +46,20 @@ function isActorObjectValid (actor: any) { | |||
46 | return exists(actor) && | 46 | return exists(actor) && |
47 | isActivityPubUrlValid(actor.id) && | 47 | isActivityPubUrlValid(actor.id) && |
48 | isActorTypeValid(actor.type) && | 48 | isActorTypeValid(actor.type) && |
49 | isActivityPubUrlValid(actor.following) && | ||
50 | isActivityPubUrlValid(actor.followers) && | ||
51 | isActivityPubUrlValid(actor.inbox) && | 49 | isActivityPubUrlValid(actor.inbox) && |
52 | isActivityPubUrlValid(actor.outbox) && | ||
53 | isActorPreferredUsernameValid(actor.preferredUsername) && | 50 | isActorPreferredUsernameValid(actor.preferredUsername) && |
54 | isActivityPubUrlValid(actor.url) && | 51 | isActivityPubUrlValid(actor.url) && |
55 | isActorPublicKeyObjectValid(actor.publicKey) && | 52 | isActorPublicKeyObjectValid(actor.publicKey) && |
56 | isActorEndpointsObjectValid(actor.endpoints) && | 53 | isActorEndpointsObjectValid(actor.endpoints) && |
57 | setValidAttributedTo(actor) && | ||
58 | 54 | ||
59 | // If this is not an account, it should be attributed to an account | 55 | (!actor.outbox || isActivityPubUrlValid(actor.outbox)) && |
56 | (!actor.following || isActivityPubUrlValid(actor.following)) && | ||
57 | (!actor.followers || isActivityPubUrlValid(actor.followers)) && | ||
58 | |||
59 | setValidAttributedTo(actor) && | ||
60 | // If this is a group (a channel), it should be attributed to an account | ||
60 | // In PeerTube we use this to attach a video channel to a specific account | 61 | // In PeerTube we use this to attach a video channel to a specific account |
61 | (actor.type === 'Person' || actor.attributedTo.length !== 0) | 62 | (actor.type !== 'Group' || actor.attributedTo.length !== 0) |
62 | } | 63 | } |
63 | 64 | ||
64 | function isActorFollowingCountValid (value: string) { | 65 | function isActorFollowingCountValid (value: string) { |
diff --git a/server/helpers/custom-validators/users.ts b/server/helpers/custom-validators/users.ts index c56ae14ef..68e84d9eb 100644 --- a/server/helpers/custom-validators/users.ts +++ b/server/helpers/custom-validators/users.ts | |||
@@ -65,6 +65,14 @@ function isUserBlockedValid (value: any) { | |||
65 | return isBooleanValid(value) | 65 | return isBooleanValid(value) |
66 | } | 66 | } |
67 | 67 | ||
68 | function isNoInstanceConfigWarningModal (value: any) { | ||
69 | return isBooleanValid(value) | ||
70 | } | ||
71 | |||
72 | function isNoWelcomeModal (value: any) { | ||
73 | return isBooleanValid(value) | ||
74 | } | ||
75 | |||
68 | function isUserBlockedReasonValid (value: any) { | 76 | function isUserBlockedReasonValid (value: any) { |
69 | return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.BLOCKED_REASON)) | 77 | return value === null || (exists(value) && validator.isLength(value, CONSTRAINTS_FIELDS.USERS.BLOCKED_REASON)) |
70 | } | 78 | } |
@@ -100,5 +108,7 @@ export { | |||
100 | isUserAutoPlayVideoValid, | 108 | isUserAutoPlayVideoValid, |
101 | isUserDisplayNameValid, | 109 | isUserDisplayNameValid, |
102 | isUserDescriptionValid, | 110 | isUserDescriptionValid, |
111 | isNoInstanceConfigWarningModal, | ||
112 | isNoWelcomeModal, | ||
103 | isAvatarFile | 113 | isAvatarFile |
104 | } | 114 | } |
diff --git a/server/helpers/custom-validators/video-ownership.ts b/server/helpers/custom-validators/video-ownership.ts index a7771e07b..9570b2799 100644 --- a/server/helpers/custom-validators/video-ownership.ts +++ b/server/helpers/custom-validators/video-ownership.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import * as validator from 'validator' | ||
3 | import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership' | 2 | import { VideoChangeOwnershipModel } from '../../models/video/video-change-ownership' |
4 | import { UserModel } from '../../models/account/user' | 3 | import { MVideoChangeOwnershipFull } from '@server/typings/models/video/video-change-ownership' |
4 | import { MUserId } from '@server/typings/models' | ||
5 | 5 | ||
6 | export async function doesChangeVideoOwnershipExist (id: string, res: Response): Promise<boolean> { | 6 | export async function doesChangeVideoOwnershipExist (id: number, res: Response) { |
7 | const videoChangeOwnership = await loadVideoChangeOwnership(id) | 7 | const videoChangeOwnership = await VideoChangeOwnershipModel.load(id) |
8 | 8 | ||
9 | if (!videoChangeOwnership) { | 9 | if (!videoChangeOwnership) { |
10 | res.status(404) | 10 | res.status(404) |
@@ -18,19 +18,7 @@ export async function doesChangeVideoOwnershipExist (id: string, res: Response): | |||
18 | return true | 18 | return true |
19 | } | 19 | } |
20 | 20 | ||
21 | async function loadVideoChangeOwnership (id: string): Promise<VideoChangeOwnershipModel | undefined> { | 21 | export function checkUserCanTerminateOwnershipChange (user: MUserId, videoChangeOwnership: MVideoChangeOwnershipFull, res: Response) { |
22 | if (validator.isInt(id)) { | ||
23 | return VideoChangeOwnershipModel.load(parseInt(id, 10)) | ||
24 | } | ||
25 | |||
26 | return undefined | ||
27 | } | ||
28 | |||
29 | export function checkUserCanTerminateOwnershipChange ( | ||
30 | user: UserModel, | ||
31 | videoChangeOwnership: VideoChangeOwnershipModel, | ||
32 | res: Response | ||
33 | ): boolean { | ||
34 | if (videoChangeOwnership.NextOwner.userId === user.id) { | 22 | if (videoChangeOwnership.NextOwner.userId === user.id) { |
35 | return true | 23 | return true |
36 | } | 24 | } |
diff --git a/server/helpers/middlewares/accounts.ts b/server/helpers/middlewares/accounts.ts index 791022b97..f5aa0bada 100644 --- a/server/helpers/middlewares/accounts.ts +++ b/server/helpers/middlewares/accounts.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import { AccountModel } from '../../models/account/account' | 2 | import { AccountModel } from '../../models/account/account' |
3 | import * as Bluebird from 'bluebird' | 3 | import * as Bluebird from 'bluebird' |
4 | import { MAccountDefault } from '../../typings/models' | ||
4 | 5 | ||
5 | function doesAccountIdExist (id: number, res: Response, sendNotFound = true) { | 6 | function doesAccountIdExist (id: number, res: Response, sendNotFound = true) { |
6 | const promise = AccountModel.load(id) | 7 | const promise = AccountModel.load(id) |
@@ -15,10 +16,12 @@ function doesLocalAccountNameExist (name: string, res: Response, sendNotFound = | |||
15 | } | 16 | } |
16 | 17 | ||
17 | function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) { | 18 | function doesAccountNameWithHostExist (nameWithDomain: string, res: Response, sendNotFound = true) { |
18 | return doesAccountExist(AccountModel.loadByNameWithHost(nameWithDomain), res, sendNotFound) | 19 | const promise = AccountModel.loadByNameWithHost(nameWithDomain) |
20 | |||
21 | return doesAccountExist(promise, res, sendNotFound) | ||
19 | } | 22 | } |
20 | 23 | ||
21 | async function doesAccountExist (p: Bluebird<AccountModel>, res: Response, sendNotFound: boolean) { | 24 | async function doesAccountExist (p: Bluebird<MAccountDefault>, res: Response, sendNotFound: boolean) { |
22 | const account = await p | 25 | const account = await p |
23 | 26 | ||
24 | if (!account) { | 27 | if (!account) { |
diff --git a/server/helpers/middlewares/video-abuses.ts b/server/helpers/middlewares/video-abuses.ts index b23f1f021..1b573ca37 100644 --- a/server/helpers/middlewares/video-abuses.ts +++ b/server/helpers/middlewares/video-abuses.ts | |||
@@ -1,41 +1,23 @@ | |||
1 | import * as express from 'express' | 1 | import { Response } from 'express' |
2 | import { VideoChannelModel } from '../../models/video/video-channel' | 2 | import { VideoAbuseModel } from '../../models/video/video-abuse' |
3 | 3 | ||
4 | async function doesLocalVideoChannelNameExist (name: string, res: express.Response) { | 4 | async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) { |
5 | const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) | 5 | const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId) |
6 | 6 | ||
7 | return processVideoChannelExist(videoChannel, res) | 7 | if (videoAbuse === null) { |
8 | } | ||
9 | |||
10 | async function doesVideoChannelIdExist (id: number, res: express.Response) { | ||
11 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) | ||
12 | |||
13 | return processVideoChannelExist(videoChannel, res) | ||
14 | } | ||
15 | |||
16 | async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) { | ||
17 | const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain) | ||
18 | |||
19 | return processVideoChannelExist(videoChannel, res) | ||
20 | } | ||
21 | |||
22 | // --------------------------------------------------------------------------- | ||
23 | |||
24 | export { | ||
25 | doesLocalVideoChannelNameExist, | ||
26 | doesVideoChannelIdExist, | ||
27 | doesVideoChannelNameWithHostExist | ||
28 | } | ||
29 | |||
30 | function processVideoChannelExist (videoChannel: VideoChannelModel, res: express.Response) { | ||
31 | if (!videoChannel) { | ||
32 | res.status(404) | 8 | res.status(404) |
33 | .json({ error: 'Video channel not found' }) | 9 | .json({ error: 'Video abuse not found' }) |
34 | .end() | 10 | .end() |
35 | 11 | ||
36 | return false | 12 | return false |
37 | } | 13 | } |
38 | 14 | ||
39 | res.locals.videoChannel = videoChannel | 15 | res.locals.videoAbuse = videoAbuse |
40 | return true | 16 | return true |
41 | } | 17 | } |
18 | |||
19 | // --------------------------------------------------------------------------- | ||
20 | |||
21 | export { | ||
22 | doesVideoAbuseExist | ||
23 | } | ||
diff --git a/server/helpers/middlewares/video-captions.ts b/server/helpers/middlewares/video-captions.ts index dc3d0144b..1b2513b60 100644 --- a/server/helpers/middlewares/video-captions.ts +++ b/server/helpers/middlewares/video-captions.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { VideoModel } from '../../models/video/video' | ||
2 | import { Response } from 'express' | 1 | import { Response } from 'express' |
3 | import { VideoCaptionModel } from '../../models/video/video-caption' | 2 | import { VideoCaptionModel } from '../../models/video/video-caption' |
3 | import { MVideoId } from '@server/typings/models' | ||
4 | 4 | ||
5 | async function doesVideoCaptionExist (video: VideoModel, language: string, res: Response) { | 5 | async function doesVideoCaptionExist (video: MVideoId, language: string, res: Response) { |
6 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language) | 6 | const videoCaption = await VideoCaptionModel.loadByVideoIdAndLanguage(video.id, language) |
7 | 7 | ||
8 | if (!videoCaption) { | 8 | if (!videoCaption) { |
diff --git a/server/helpers/middlewares/video-channels.ts b/server/helpers/middlewares/video-channels.ts index 1b573ca37..1595ecd94 100644 --- a/server/helpers/middlewares/video-channels.ts +++ b/server/helpers/middlewares/video-channels.ts | |||
@@ -1,23 +1,42 @@ | |||
1 | import { Response } from 'express' | 1 | import * as express from 'express' |
2 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 2 | import { VideoChannelModel } from '../../models/video/video-channel' |
3 | import { MChannelAccountDefault } from '@server/typings/models' | ||
3 | 4 | ||
4 | async function doesVideoAbuseExist (abuseId: number, videoId: number, res: Response) { | 5 | async function doesLocalVideoChannelNameExist (name: string, res: express.Response) { |
5 | const videoAbuse = await VideoAbuseModel.loadByIdAndVideoId(abuseId, videoId) | 6 | const videoChannel = await VideoChannelModel.loadLocalByNameAndPopulateAccount(name) |
6 | 7 | ||
7 | if (videoAbuse === null) { | 8 | return processVideoChannelExist(videoChannel, res) |
8 | res.status(404) | 9 | } |
9 | .json({ error: 'Video abuse not found' }) | ||
10 | .end() | ||
11 | 10 | ||
12 | return false | 11 | async function doesVideoChannelIdExist (id: number, res: express.Response) { |
13 | } | 12 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(+id) |
14 | 13 | ||
15 | res.locals.videoAbuse = videoAbuse | 14 | return processVideoChannelExist(videoChannel, res) |
16 | return true | 15 | } |
16 | |||
17 | async function doesVideoChannelNameWithHostExist (nameWithDomain: string, res: express.Response) { | ||
18 | const videoChannel = await VideoChannelModel.loadByNameWithHostAndPopulateAccount(nameWithDomain) | ||
19 | |||
20 | return processVideoChannelExist(videoChannel, res) | ||
17 | } | 21 | } |
18 | 22 | ||
19 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
20 | 24 | ||
21 | export { | 25 | export { |
22 | doesVideoAbuseExist | 26 | doesLocalVideoChannelNameExist, |
27 | doesVideoChannelIdExist, | ||
28 | doesVideoChannelNameWithHostExist | ||
29 | } | ||
30 | |||
31 | function processVideoChannelExist (videoChannel: MChannelAccountDefault, res: express.Response) { | ||
32 | if (!videoChannel) { | ||
33 | res.status(404) | ||
34 | .json({ error: 'Video channel not found' }) | ||
35 | .end() | ||
36 | |||
37 | return false | ||
38 | } | ||
39 | |||
40 | res.locals.videoChannel = videoChannel | ||
41 | return true | ||
23 | } | 42 | } |
diff --git a/server/helpers/middlewares/video-playlists.ts b/server/helpers/middlewares/video-playlists.ts index 735bf362f..8e7484483 100644 --- a/server/helpers/middlewares/video-playlists.ts +++ b/server/helpers/middlewares/video-playlists.ts | |||
@@ -1,11 +1,31 @@ | |||
1 | import * as express from 'express' | 1 | import * as express from 'express' |
2 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | 2 | import { VideoPlaylistModel } from '../../models/video/video-playlist' |
3 | import { MVideoPlaylist } from '../../typings/models/video/video-playlist' | ||
3 | 4 | ||
4 | async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: 'summary' | 'all' = 'summary') { | 5 | export type VideoPlaylistFetchType = 'summary' | 'all' |
5 | const videoPlaylist = fetchType === 'summary' | 6 | async function doesVideoPlaylistExist (id: number | string, res: express.Response, fetchType: VideoPlaylistFetchType = 'summary') { |
6 | ? await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined) | 7 | if (fetchType === 'summary') { |
7 | : await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined) | 8 | const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannelSummary(id, undefined) |
9 | res.locals.videoPlaylistSummary = videoPlaylist | ||
8 | 10 | ||
11 | return handleVideoPlaylist(videoPlaylist, res) | ||
12 | } | ||
13 | |||
14 | const videoPlaylist = await VideoPlaylistModel.loadWithAccountAndChannel(id, undefined) | ||
15 | res.locals.videoPlaylistFull = videoPlaylist | ||
16 | |||
17 | return handleVideoPlaylist(videoPlaylist, res) | ||
18 | } | ||
19 | |||
20 | // --------------------------------------------------------------------------- | ||
21 | |||
22 | export { | ||
23 | doesVideoPlaylistExist | ||
24 | } | ||
25 | |||
26 | // --------------------------------------------------------------------------- | ||
27 | |||
28 | function handleVideoPlaylist (videoPlaylist: MVideoPlaylist, res: express.Response) { | ||
9 | if (!videoPlaylist) { | 29 | if (!videoPlaylist) { |
10 | res.status(404) | 30 | res.status(404) |
11 | .json({ error: 'Video playlist not found' }) | 31 | .json({ error: 'Video playlist not found' }) |
@@ -14,12 +34,5 @@ async function doesVideoPlaylistExist (id: number | string, res: express.Respons | |||
14 | return false | 34 | return false |
15 | } | 35 | } |
16 | 36 | ||
17 | res.locals.videoPlaylist = videoPlaylist | ||
18 | return true | 37 | return true |
19 | } | 38 | } |
20 | |||
21 | // --------------------------------------------------------------------------- | ||
22 | |||
23 | export { | ||
24 | doesVideoPlaylistExist | ||
25 | } | ||
diff --git a/server/helpers/middlewares/videos.ts b/server/helpers/middlewares/videos.ts index ceb1058ec..74f529804 100644 --- a/server/helpers/middlewares/videos.ts +++ b/server/helpers/middlewares/videos.ts | |||
@@ -1,9 +1,8 @@ | |||
1 | import { Response } from 'express' | 1 | import { Response } from 'express' |
2 | import { fetchVideo, VideoFetchType } from '../video' | 2 | import { fetchVideo, VideoFetchType } from '../video' |
3 | import { UserModel } from '../../models/account/user' | ||
4 | import { UserRight } from '../../../shared/models/users' | 3 | import { UserRight } from '../../../shared/models/users' |
5 | import { VideoChannelModel } from '../../models/video/video-channel' | 4 | import { VideoChannelModel } from '../../models/video/video-channel' |
6 | import { VideoModel } from '../../models/video/video' | 5 | import { MUser, MUserAccountId, MVideoAccountLight, MVideoFullLight, MVideoThumbnail, MVideoWithRights } from '@server/typings/models' |
7 | 6 | ||
8 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') { | 7 | async function doesVideoExist (id: number | string, res: Response, fetchType: VideoFetchType = 'all') { |
9 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined | 8 | const userId = res.locals.oauth ? res.locals.oauth.token.User.id : undefined |
@@ -18,11 +17,28 @@ async function doesVideoExist (id: number | string, res: Response, fetchType: Vi | |||
18 | return false | 17 | return false |
19 | } | 18 | } |
20 | 19 | ||
21 | if (fetchType !== 'none') res.locals.video = video | 20 | switch (fetchType) { |
21 | case 'all': | ||
22 | res.locals.videoAll = video as MVideoFullLight | ||
23 | break | ||
24 | |||
25 | case 'id': | ||
26 | res.locals.videoId = video | ||
27 | break | ||
28 | |||
29 | case 'only-video': | ||
30 | res.locals.onlyVideo = video as MVideoThumbnail | ||
31 | break | ||
32 | |||
33 | case 'only-video-with-rights': | ||
34 | res.locals.onlyVideoWithRights = video as MVideoWithRights | ||
35 | break | ||
36 | } | ||
37 | |||
22 | return true | 38 | return true |
23 | } | 39 | } |
24 | 40 | ||
25 | async function doesVideoChannelOfAccountExist (channelId: number, user: UserModel, res: Response) { | 41 | async function doesVideoChannelOfAccountExist (channelId: number, user: MUserAccountId, res: Response) { |
26 | if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { | 42 | if (user.hasRight(UserRight.UPDATE_ANY_VIDEO) === true) { |
27 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) | 43 | const videoChannel = await VideoChannelModel.loadAndPopulateAccount(channelId) |
28 | if (videoChannel === null) { | 44 | if (videoChannel === null) { |
@@ -50,7 +66,7 @@ async function doesVideoChannelOfAccountExist (channelId: number, user: UserMode | |||
50 | return true | 66 | return true |
51 | } | 67 | } |
52 | 68 | ||
53 | function checkUserCanManageVideo (user: UserModel, video: VideoModel, right: UserRight, res: Response) { | 69 | function checkUserCanManageVideo (user: MUser, video: MVideoAccountLight, right: UserRight, res: Response) { |
54 | // Retrieve the user who did the request | 70 | // Retrieve the user who did the request |
55 | if (video.isOwned() === false) { | 71 | if (video.isOwned() === false) { |
56 | res.status(403) | 72 | res.status(403) |
diff --git a/server/helpers/peertube-crypto.ts b/server/helpers/peertube-crypto.ts index 1424949d0..9eb782302 100644 --- a/server/helpers/peertube-crypto.ts +++ b/server/helpers/peertube-crypto.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import { Request } from 'express' | 1 | import { Request } from 'express' |
2 | import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' | 2 | import { BCRYPT_SALT_SIZE, HTTP_SIGNATURE, PRIVATE_RSA_KEY_SIZE } from '../initializers/constants' |
3 | import { ActorModel } from '../models/activitypub/actor' | ||
4 | import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils' | 3 | import { createPrivateKey, getPublicKey, promisify1, promisify2, sha256 } from './core-utils' |
5 | import { jsig, jsonld } from './custom-jsonld-signature' | 4 | import { jsonld } from './custom-jsonld-signature' |
6 | import { logger } from './logger' | 5 | import { logger } from './logger' |
7 | import { cloneDeep } from 'lodash' | 6 | import { cloneDeep } from 'lodash' |
8 | import { createVerify } from 'crypto' | 7 | import { createSign, createVerify } from 'crypto' |
9 | import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' | 8 | import { buildDigest } from '../lib/job-queue/handlers/utils/activitypub-http-utils' |
10 | import * as bcrypt from 'bcrypt' | 9 | import * as bcrypt from 'bcrypt' |
10 | import { MActor } from '../typings/models' | ||
11 | 11 | ||
12 | const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare) | 12 | const bcryptComparePromise = promisify2<any, string, boolean>(bcrypt.compare) |
13 | const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt) | 13 | const bcryptGenSaltPromise = promisify1<number, string>(bcrypt.genSalt) |
@@ -46,7 +46,7 @@ function isHTTPSignatureDigestValid (rawBody: Buffer, req: Request): boolean { | |||
46 | return true | 46 | return true |
47 | } | 47 | } |
48 | 48 | ||
49 | function isHTTPSignatureVerified (httpSignatureParsed: any, actor: ActorModel): boolean { | 49 | function isHTTPSignatureVerified (httpSignatureParsed: any, actor: MActor): boolean { |
50 | return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true | 50 | return httpSignature.verifySignature(httpSignatureParsed, actor.publicKey) === true |
51 | } | 51 | } |
52 | 52 | ||
@@ -56,70 +56,21 @@ function parseHTTPSignature (req: Request, clockSkew?: number) { | |||
56 | 56 | ||
57 | // JSONLD | 57 | // JSONLD |
58 | 58 | ||
59 | async function isJsonLDSignatureVerified (fromActor: ActorModel, signedDocument: any): Promise<boolean> { | 59 | function isJsonLDSignatureVerified (fromActor: MActor, signedDocument: any): Promise<boolean> { |
60 | if (signedDocument.signature.type === 'RsaSignature2017') { | 60 | if (signedDocument.signature.type === 'RsaSignature2017') { |
61 | // Mastodon algorithm | 61 | return isJsonLDRSA2017Verified(fromActor, signedDocument) |
62 | const res = await isJsonLDRSA2017Verified(fromActor, signedDocument) | ||
63 | // Success? If no, try with our library | ||
64 | if (res === true) return true | ||
65 | } | 62 | } |
66 | 63 | ||
67 | const publicKeyObject = { | 64 | logger.warn('Unknown JSON LD signature %s.', signedDocument.signature.type, signedDocument) |
68 | '@context': jsig.SECURITY_CONTEXT_URL, | ||
69 | id: fromActor.url, | ||
70 | type: 'CryptographicKey', | ||
71 | owner: fromActor.url, | ||
72 | publicKeyPem: fromActor.publicKey | ||
73 | } | ||
74 | |||
75 | const publicKeyOwnerObject = { | ||
76 | '@context': jsig.SECURITY_CONTEXT_URL, | ||
77 | id: fromActor.url, | ||
78 | publicKey: [ publicKeyObject ] | ||
79 | } | ||
80 | 65 | ||
81 | const options = { | 66 | return Promise.resolve(false) |
82 | publicKey: publicKeyObject, | ||
83 | publicKeyOwner: publicKeyOwnerObject | ||
84 | } | ||
85 | |||
86 | return jsig.promises | ||
87 | .verify(signedDocument, options) | ||
88 | .then((result: { verified: boolean }) => result.verified) | ||
89 | .catch(err => { | ||
90 | logger.error('Cannot check signature.', { err }) | ||
91 | return false | ||
92 | }) | ||
93 | } | 67 | } |
94 | 68 | ||
95 | // Backward compatibility with "other" implementations | 69 | // Backward compatibility with "other" implementations |
96 | async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: any) { | 70 | async function isJsonLDRSA2017Verified (fromActor: MActor, signedDocument: any) { |
97 | function hash (obj: any): Promise<any> { | ||
98 | return jsonld.promises | ||
99 | .normalize(obj, { | ||
100 | algorithm: 'URDNA2015', | ||
101 | format: 'application/n-quads' | ||
102 | }) | ||
103 | .then(res => sha256(res)) | ||
104 | } | ||
105 | |||
106 | const signatureCopy = cloneDeep(signedDocument.signature) | ||
107 | Object.assign(signatureCopy, { | ||
108 | '@context': [ | ||
109 | 'https://w3id.org/security/v1', | ||
110 | { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' } | ||
111 | ] | ||
112 | }) | ||
113 | delete signatureCopy.type | ||
114 | delete signatureCopy.id | ||
115 | delete signatureCopy.signatureValue | ||
116 | |||
117 | const docWithoutSignature = cloneDeep(signedDocument) | ||
118 | delete docWithoutSignature.signature | ||
119 | |||
120 | const [ documentHash, optionsHash ] = await Promise.all([ | 71 | const [ documentHash, optionsHash ] = await Promise.all([ |
121 | hash(docWithoutSignature), | 72 | createDocWithoutSignatureHash(signedDocument), |
122 | hash(signatureCopy) | 73 | createSignatureHash(signedDocument.signature) |
123 | ]) | 74 | ]) |
124 | 75 | ||
125 | const toVerify = optionsHash + documentHash | 76 | const toVerify = optionsHash + documentHash |
@@ -130,14 +81,27 @@ async function isJsonLDRSA2017Verified (fromActor: ActorModel, signedDocument: a | |||
130 | return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64') | 81 | return verify.verify(fromActor.publicKey, signedDocument.signature.signatureValue, 'base64') |
131 | } | 82 | } |
132 | 83 | ||
133 | function signJsonLDObject (byActor: ActorModel, data: any) { | 84 | async function signJsonLDObject (byActor: MActor, data: any) { |
134 | const options = { | 85 | const signature = { |
135 | privateKeyPem: byActor.privateKey, | 86 | type: 'RsaSignature2017', |
136 | creator: byActor.url, | 87 | creator: byActor.url, |
137 | algorithm: 'RsaSignature2017' | 88 | created: new Date().toISOString() |
138 | } | 89 | } |
139 | 90 | ||
140 | return jsig.promises.sign(data, options) | 91 | const [ documentHash, optionsHash ] = await Promise.all([ |
92 | createDocWithoutSignatureHash(data), | ||
93 | createSignatureHash(signature) | ||
94 | ]) | ||
95 | |||
96 | const toSign = optionsHash + documentHash | ||
97 | |||
98 | const sign = createSign('RSA-SHA256') | ||
99 | sign.update(toSign, 'utf8') | ||
100 | |||
101 | const signatureValue = sign.sign(byActor.privateKey, 'base64') | ||
102 | Object.assign(signature, { signatureValue }) | ||
103 | |||
104 | return Object.assign(data, { signature }) | ||
141 | } | 105 | } |
142 | 106 | ||
143 | // --------------------------------------------------------------------------- | 107 | // --------------------------------------------------------------------------- |
@@ -154,3 +118,35 @@ export { | |||
154 | } | 118 | } |
155 | 119 | ||
156 | // --------------------------------------------------------------------------- | 120 | // --------------------------------------------------------------------------- |
121 | |||
122 | function hash (obj: any): Promise<any> { | ||
123 | return jsonld.promises | ||
124 | .normalize(obj, { | ||
125 | algorithm: 'URDNA2015', | ||
126 | format: 'application/n-quads' | ||
127 | }) | ||
128 | .then(res => sha256(res)) | ||
129 | } | ||
130 | |||
131 | function createSignatureHash (signature: any) { | ||
132 | const signatureCopy = cloneDeep(signature) | ||
133 | Object.assign(signatureCopy, { | ||
134 | '@context': [ | ||
135 | 'https://w3id.org/security/v1', | ||
136 | { RsaSignature2017: 'https://w3id.org/security#RsaSignature2017' } | ||
137 | ] | ||
138 | }) | ||
139 | |||
140 | delete signatureCopy.type | ||
141 | delete signatureCopy.id | ||
142 | delete signatureCopy.signatureValue | ||
143 | |||
144 | return hash(signatureCopy) | ||
145 | } | ||
146 | |||
147 | function createDocWithoutSignatureHash (doc: any) { | ||
148 | const docWithoutSignature = cloneDeep(doc) | ||
149 | delete docWithoutSignature.signature | ||
150 | |||
151 | return hash(docWithoutSignature) | ||
152 | } | ||
diff --git a/server/helpers/utils.ts b/server/helpers/utils.ts index 1464b1477..ba07eaaf3 100644 --- a/server/helpers/utils.ts +++ b/server/helpers/utils.ts | |||
@@ -19,7 +19,10 @@ async function generateRandomString (size: number) { | |||
19 | return raw.toString('hex') | 19 | return raw.toString('hex') |
20 | } | 20 | } |
21 | 21 | ||
22 | interface FormattableToJSON<U, V> { toFormattedJSON (args?: U): V } | 22 | interface FormattableToJSON<U, V> { |
23 | toFormattedJSON (args?: U): V | ||
24 | } | ||
25 | |||
23 | function getFormattedObjects<U, V, T extends FormattableToJSON<U, V>> (objects: T[], objectsTotal: number, formattedArg?: U) { | 26 | function getFormattedObjects<U, V, T extends FormattableToJSON<U, V>> (objects: T[], objectsTotal: number, formattedArg?: U) { |
24 | const formattedObjects = objects.map(o => o.toFormattedJSON(formattedArg)) | 27 | const formattedObjects = objects.map(o => o.toFormattedJSON(formattedArg)) |
25 | 28 | ||
diff --git a/server/helpers/video.ts b/server/helpers/video.ts index c90fe06c7..d066e2b1f 100644 --- a/server/helpers/video.ts +++ b/server/helpers/video.ts | |||
@@ -1,8 +1,30 @@ | |||
1 | import { VideoModel } from '../models/video/video' | 1 | import { VideoModel } from '../models/video/video' |
2 | import * as Bluebird from 'bluebird' | ||
3 | import { | ||
4 | MVideoAccountLightBlacklistAllFiles, | ||
5 | MVideoFullLight, | ||
6 | MVideoIdThumbnail, | ||
7 | MVideoThumbnail, | ||
8 | MVideoWithRights | ||
9 | } from '@server/typings/models' | ||
10 | import { Response } from 'express' | ||
2 | 11 | ||
3 | type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' | 12 | type VideoFetchType = 'all' | 'only-video' | 'only-video-with-rights' | 'id' | 'none' |
4 | 13 | ||
5 | function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: number) { | 14 | function fetchVideo (id: number | string, fetchType: 'all', userId?: number): Bluebird<MVideoFullLight> |
15 | function fetchVideo (id: number | string, fetchType: 'only-video', userId?: number): Bluebird<MVideoThumbnail> | ||
16 | function fetchVideo (id: number | string, fetchType: 'only-video-with-rights', userId?: number): Bluebird<MVideoWithRights> | ||
17 | function fetchVideo (id: number | string, fetchType: 'id' | 'none', userId?: number): Bluebird<MVideoIdThumbnail> | ||
18 | function fetchVideo ( | ||
19 | id: number | string, | ||
20 | fetchType: VideoFetchType, | ||
21 | userId?: number | ||
22 | ): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> | ||
23 | function fetchVideo ( | ||
24 | id: number | string, | ||
25 | fetchType: VideoFetchType, | ||
26 | userId?: number | ||
27 | ): Bluebird<MVideoFullLight | MVideoThumbnail | MVideoWithRights | MVideoIdThumbnail> { | ||
6 | if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) | 28 | if (fetchType === 'all') return VideoModel.loadAndPopulateAccountAndServerAndTags(id, undefined, userId) |
7 | 29 | ||
8 | if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) | 30 | if (fetchType === 'only-video-with-rights') return VideoModel.loadWithRights(id) |
@@ -13,15 +35,29 @@ function fetchVideo (id: number | string, fetchType: VideoFetchType, userId?: nu | |||
13 | } | 35 | } |
14 | 36 | ||
15 | type VideoFetchByUrlType = 'all' | 'only-video' | 37 | type VideoFetchByUrlType = 'all' | 'only-video' |
16 | function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType) { | 38 | |
39 | function fetchVideoByUrl (url: string, fetchType: 'all'): Bluebird<MVideoAccountLightBlacklistAllFiles> | ||
40 | function fetchVideoByUrl (url: string, fetchType: 'only-video'): Bluebird<MVideoThumbnail> | ||
41 | function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> | ||
42 | function fetchVideoByUrl (url: string, fetchType: VideoFetchByUrlType): Bluebird<MVideoAccountLightBlacklistAllFiles | MVideoThumbnail> { | ||
17 | if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url) | 43 | if (fetchType === 'all') return VideoModel.loadByUrlAndPopulateAccount(url) |
18 | 44 | ||
19 | if (fetchType === 'only-video') return VideoModel.loadByUrl(url) | 45 | if (fetchType === 'only-video') return VideoModel.loadByUrl(url) |
20 | } | 46 | } |
21 | 47 | ||
48 | function getVideo (res: Response) { | ||
49 | return res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights || res.locals.videoId | ||
50 | } | ||
51 | |||
52 | function getVideoWithAttributes (res: Response) { | ||
53 | return res.locals.videoAll || res.locals.onlyVideo || res.locals.onlyVideoWithRights | ||
54 | } | ||
55 | |||
22 | export { | 56 | export { |
23 | VideoFetchType, | 57 | VideoFetchType, |
24 | VideoFetchByUrlType, | 58 | VideoFetchByUrlType, |
25 | fetchVideo, | 59 | fetchVideo, |
60 | getVideo, | ||
61 | getVideoWithAttributes, | ||
26 | fetchVideoByUrl | 62 | fetchVideoByUrl |
27 | } | 63 | } |
diff --git a/server/helpers/webfinger.ts b/server/helpers/webfinger.ts index d1229e28f..5443a266b 100644 --- a/server/helpers/webfinger.ts +++ b/server/helpers/webfinger.ts | |||
@@ -4,6 +4,7 @@ import { ActorModel } from '../models/activitypub/actor' | |||
4 | import { isTestInstance } from './core-utils' | 4 | import { isTestInstance } from './core-utils' |
5 | import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' | 5 | import { isActivityPubUrlValid } from './custom-validators/activitypub/misc' |
6 | import { WEBSERVER } from '../initializers/constants' | 6 | import { WEBSERVER } from '../initializers/constants' |
7 | import { MActorFull } from '../typings/models' | ||
7 | 8 | ||
8 | const webfinger = new WebFinger({ | 9 | const webfinger = new WebFinger({ |
9 | webfist_fallback: false, | 10 | webfist_fallback: false, |
@@ -17,7 +18,7 @@ async function loadActorUrlOrGetFromWebfinger (uriArg: string) { | |||
17 | const uri = uriArg.startsWith('@') ? uriArg.slice(1) : uriArg | 18 | const uri = uriArg.startsWith('@') ? uriArg.slice(1) : uriArg |
18 | 19 | ||
19 | const [ name, host ] = uri.split('@') | 20 | const [ name, host ] = uri.split('@') |
20 | let actor: ActorModel | 21 | let actor: MActorFull |
21 | 22 | ||
22 | if (!host || host === WEBSERVER.HOST) { | 23 | if (!host || host === WEBSERVER.HOST) { |
23 | actor = await ActorModel.loadLocalByName(name) | 24 | actor = await ActorModel.loadLocalByName(name) |
diff --git a/server/initializers/config.ts b/server/initializers/config.ts index 510f7d64d..164d714d6 100644 --- a/server/initializers/config.ts +++ b/server/initializers/config.ts | |||
@@ -209,6 +209,19 @@ const CONFIG = { | |||
209 | get SHORT_DESCRIPTION () { return config.get<string>('instance.short_description') }, | 209 | get SHORT_DESCRIPTION () { return config.get<string>('instance.short_description') }, |
210 | get DESCRIPTION () { return config.get<string>('instance.description') }, | 210 | get DESCRIPTION () { return config.get<string>('instance.description') }, |
211 | get TERMS () { return config.get<string>('instance.terms') }, | 211 | get TERMS () { return config.get<string>('instance.terms') }, |
212 | get CODE_OF_CONDUCT () { return config.get<string>('instance.code_of_conduct') }, | ||
213 | |||
214 | get CREATION_REASON () { return config.get<string>('instance.creation_reason') }, | ||
215 | |||
216 | get MODERATION_INFORMATION () { return config.get<string>('instance.moderation_information') }, | ||
217 | get ADMINISTRATOR () { return config.get<string>('instance.administrator') }, | ||
218 | get MAINTENANCE_LIFETIME () { return config.get<string>('instance.maintenance_lifetime') }, | ||
219 | get BUSINESS_MODEL () { return config.get<string>('instance.business_model') }, | ||
220 | get HARDWARE_INFORMATION () { return config.get<string>('instance.hardware_information') }, | ||
221 | |||
222 | get LANGUAGES () { return config.get<string[]>('instance.languages') || [] }, | ||
223 | get CATEGORIES () { return config.get<number[]>('instance.categories') || [] }, | ||
224 | |||
212 | get IS_NSFW () { return config.get<boolean>('instance.is_nsfw') }, | 225 | get IS_NSFW () { return config.get<boolean>('instance.is_nsfw') }, |
213 | get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') }, | 226 | get DEFAULT_CLIENT_ROUTE () { return config.get<string>('instance.default_client_route') }, |
214 | get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') }, | 227 | get DEFAULT_NSFW_POLICY () { return config.get<NSFWPolicyType>('instance.default_nsfw_policy') }, |
@@ -232,6 +245,23 @@ const CONFIG = { | |||
232 | get MANUAL_APPROVAL () { return config.get<boolean>('followers.instance.manual_approval') } | 245 | get MANUAL_APPROVAL () { return config.get<boolean>('followers.instance.manual_approval') } |
233 | } | 246 | } |
234 | }, | 247 | }, |
248 | FOLLOWINGS: { | ||
249 | INSTANCE: { | ||
250 | AUTO_FOLLOW_BACK: { | ||
251 | get ENABLED () { | ||
252 | return config.get<boolean>('followings.instance.auto_follow_back.enabled') | ||
253 | } | ||
254 | }, | ||
255 | AUTO_FOLLOW_INDEX: { | ||
256 | get ENABLED () { | ||
257 | return config.get<boolean>('followings.instance.auto_follow_index.enabled') | ||
258 | }, | ||
259 | get INDEX_URL () { | ||
260 | return config.get<string>('followings.instance.auto_follow_index.index_url') | ||
261 | } | ||
262 | } | ||
263 | } | ||
264 | }, | ||
235 | THEME: { | 265 | THEME: { |
236 | get DEFAULT () { return config.get<string>('theme.default') } | 266 | get DEFAULT () { return config.get<string>('theme.default') } |
237 | } | 267 | } |
diff --git a/server/initializers/constants.ts b/server/initializers/constants.ts index 3dc178b11..01d4f1d74 100644 --- a/server/initializers/constants.ts +++ b/server/initializers/constants.ts | |||
@@ -14,7 +14,7 @@ import { CONFIG, registerConfigChangedHandler } from './config' | |||
14 | 14 | ||
15 | // --------------------------------------------------------------------------- | 15 | // --------------------------------------------------------------------------- |
16 | 16 | ||
17 | const LAST_MIGRATION_VERSION = 420 | 17 | const LAST_MIGRATION_VERSION = 430 |
18 | 18 | ||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
20 | 20 | ||
@@ -168,10 +168,15 @@ const SCHEDULER_INTERVALS_MS = { | |||
168 | updateVideos: 60000, // 1 minute | 168 | updateVideos: 60000, // 1 minute |
169 | youtubeDLUpdate: 60000 * 60 * 24, // 1 day | 169 | youtubeDLUpdate: 60000 * 60 * 24, // 1 day |
170 | checkPlugins: CONFIG.PLUGINS.INDEX.CHECK_LATEST_VERSIONS_INTERVAL, | 170 | checkPlugins: CONFIG.PLUGINS.INDEX.CHECK_LATEST_VERSIONS_INTERVAL, |
171 | autoFollowIndexInstances: 60000 * 60 * 24, // 1 day | ||
171 | removeOldViews: 60000 * 60 * 24, // 1 day | 172 | removeOldViews: 60000 * 60 * 24, // 1 day |
172 | removeOldHistory: 60000 * 60 * 24 // 1 day | 173 | removeOldHistory: 60000 * 60 * 24 // 1 day |
173 | } | 174 | } |
174 | 175 | ||
176 | const INSTANCES_INDEX = { | ||
177 | HOSTS_PATH: '/api/v1/instances/hosts' | ||
178 | } | ||
179 | |||
175 | // --------------------------------------------------------------------------- | 180 | // --------------------------------------------------------------------------- |
176 | 181 | ||
177 | const CONSTRAINTS_FIELDS = { | 182 | const CONSTRAINTS_FIELDS = { |
@@ -633,6 +638,7 @@ if (isTestInstance() === true) { | |||
633 | SCHEDULER_INTERVALS_MS.removeOldHistory = 5000 | 638 | SCHEDULER_INTERVALS_MS.removeOldHistory = 5000 |
634 | SCHEDULER_INTERVALS_MS.removeOldViews = 5000 | 639 | SCHEDULER_INTERVALS_MS.removeOldViews = 5000 |
635 | SCHEDULER_INTERVALS_MS.updateVideos = 5000 | 640 | SCHEDULER_INTERVALS_MS.updateVideos = 5000 |
641 | SCHEDULER_INTERVALS_MS.autoFollowIndexInstances = 5000 | ||
636 | REPEAT_JOBS[ 'videos-views' ] = { every: 5000 } | 642 | REPEAT_JOBS[ 'videos-views' ] = { every: 5000 } |
637 | 643 | ||
638 | REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 | 644 | REDUNDANCY.VIDEOS.RANDOMIZED_FACTOR = 1 |
@@ -683,6 +689,7 @@ export { | |||
683 | PREVIEWS_SIZE, | 689 | PREVIEWS_SIZE, |
684 | REMOTE_SCHEME, | 690 | REMOTE_SCHEME, |
685 | FOLLOW_STATES, | 691 | FOLLOW_STATES, |
692 | INSTANCES_INDEX, | ||
686 | DEFAULT_USER_THEME_NAME, | 693 | DEFAULT_USER_THEME_NAME, |
687 | SERVER_ACTOR_NAME, | 694 | SERVER_ACTOR_NAME, |
688 | PLUGIN_GLOBAL_CSS_FILE_NAME, | 695 | PLUGIN_GLOBAL_CSS_FILE_NAME, |
diff --git a/server/initializers/migrations/0425-nullable-actor-fields.ts b/server/initializers/migrations/0425-nullable-actor-fields.ts new file mode 100644 index 000000000..4e5f9e6ab --- /dev/null +++ b/server/initializers/migrations/0425-nullable-actor-fields.ts | |||
@@ -0,0 +1,26 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | const data = { | ||
10 | type: Sequelize.STRING, | ||
11 | allowNull: true | ||
12 | } | ||
13 | |||
14 | await utils.queryInterface.changeColumn('actor', 'outboxUrl', data) | ||
15 | await utils.queryInterface.changeColumn('actor', 'followersUrl', data) | ||
16 | await utils.queryInterface.changeColumn('actor', 'followingUrl', data) | ||
17 | } | ||
18 | |||
19 | function down (options) { | ||
20 | throw new Error('Not implemented.') | ||
21 | } | ||
22 | |||
23 | export { | ||
24 | up, | ||
25 | down | ||
26 | } | ||
diff --git a/server/initializers/migrations/0425-user-modals.ts b/server/initializers/migrations/0425-user-modals.ts new file mode 100644 index 000000000..5c2aa85b5 --- /dev/null +++ b/server/initializers/migrations/0425-user-modals.ts | |||
@@ -0,0 +1,40 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | { | ||
10 | const data = { | ||
11 | type: Sequelize.BOOLEAN, | ||
12 | allowNull: false, | ||
13 | defaultValue: false | ||
14 | } | ||
15 | |||
16 | await utils.queryInterface.addColumn('user', 'noInstanceConfigWarningModal', data) | ||
17 | } | ||
18 | |||
19 | { | ||
20 | const data = { | ||
21 | type: Sequelize.BOOLEAN, | ||
22 | allowNull: false, | ||
23 | defaultValue: true | ||
24 | } | ||
25 | |||
26 | await utils.queryInterface.addColumn('user', 'noWelcomeModal', data) | ||
27 | data.defaultValue = false | ||
28 | |||
29 | await utils.queryInterface.changeColumn('user', 'noWelcomeModal', data) | ||
30 | } | ||
31 | } | ||
32 | |||
33 | function down (options) { | ||
34 | throw new Error('Not implemented.') | ||
35 | } | ||
36 | |||
37 | export { | ||
38 | up, | ||
39 | down | ||
40 | } | ||
diff --git a/server/initializers/migrations/0430-auto-follow-notification-setting.ts b/server/initializers/migrations/0430-auto-follow-notification-setting.ts new file mode 100644 index 000000000..034bdd46d --- /dev/null +++ b/server/initializers/migrations/0430-auto-follow-notification-setting.ts | |||
@@ -0,0 +1,40 @@ | |||
1 | import * as Sequelize from 'sequelize' | ||
2 | |||
3 | async function up (utils: { | ||
4 | transaction: Sequelize.Transaction, | ||
5 | queryInterface: Sequelize.QueryInterface, | ||
6 | sequelize: Sequelize.Sequelize, | ||
7 | db: any | ||
8 | }): Promise<void> { | ||
9 | { | ||
10 | const data = { | ||
11 | type: Sequelize.INTEGER, | ||
12 | defaultValue: null, | ||
13 | allowNull: true | ||
14 | } | ||
15 | await utils.queryInterface.addColumn('userNotificationSetting', 'autoInstanceFollowing', data) | ||
16 | } | ||
17 | |||
18 | { | ||
19 | const query = 'UPDATE "userNotificationSetting" SET "autoInstanceFollowing" = 1' | ||
20 | await utils.sequelize.query(query) | ||
21 | } | ||
22 | |||
23 | { | ||
24 | const data = { | ||
25 | type: Sequelize.INTEGER, | ||
26 | defaultValue: null, | ||
27 | allowNull: false | ||
28 | } | ||
29 | await utils.queryInterface.changeColumn('userNotificationSetting', 'autoInstanceFollowing', data) | ||
30 | } | ||
31 | } | ||
32 | |||
33 | function down (options) { | ||
34 | throw new Error('Not implemented.') | ||
35 | } | ||
36 | |||
37 | export { | ||
38 | up, | ||
39 | down | ||
40 | } | ||
diff --git a/server/lib/activitypub/actor.ts b/server/lib/activitypub/actor.ts index 9f5d12eb4..13b73077e 100644 --- a/server/lib/activitypub/actor.ts +++ b/server/lib/activitypub/actor.ts | |||
@@ -22,13 +22,27 @@ import { JobQueue } from '../job-queue' | |||
22 | import { getServerActor } from '../../helpers/utils' | 22 | import { getServerActor } from '../../helpers/utils' |
23 | import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor' | 23 | import { ActorFetchByUrlType, fetchActorByUrl } from '../../helpers/actor' |
24 | import { sequelizeTypescript } from '../../initializers/database' | 24 | import { sequelizeTypescript } from '../../initializers/database' |
25 | import { | ||
26 | MAccount, | ||
27 | MAccountDefault, | ||
28 | MActor, | ||
29 | MActorAccountChannelId, | ||
30 | MActorAccountChannelIdActor, | ||
31 | MActorAccountId, | ||
32 | MActorDefault, | ||
33 | MActorFull, | ||
34 | MActorFullActor, | ||
35 | MActorId, | ||
36 | MChannel, | ||
37 | MChannelAccountDefault | ||
38 | } from '../../typings/models' | ||
25 | 39 | ||
26 | // Set account keys, this could be long so process after the account creation and do not block the client | 40 | // Set account keys, this could be long so process after the account creation and do not block the client |
27 | function setAsyncActorKeys (actor: ActorModel) { | 41 | function setAsyncActorKeys <T extends MActor> (actor: T) { |
28 | return createPrivateAndPublicKeys() | 42 | return createPrivateAndPublicKeys() |
29 | .then(({ publicKey, privateKey }) => { | 43 | .then(({ publicKey, privateKey }) => { |
30 | actor.set('publicKey', publicKey) | 44 | actor.publicKey = publicKey |
31 | actor.set('privateKey', privateKey) | 45 | actor.privateKey = privateKey |
32 | return actor.save() | 46 | return actor.save() |
33 | }) | 47 | }) |
34 | .catch(err => { | 48 | .catch(err => { |
@@ -37,12 +51,26 @@ function setAsyncActorKeys (actor: ActorModel) { | |||
37 | }) | 51 | }) |
38 | } | 52 | } |
39 | 53 | ||
54 | function getOrCreateActorAndServerAndModel ( | ||
55 | activityActor: string | ActivityPubActor, | ||
56 | fetchType: 'all', | ||
57 | recurseIfNeeded?: boolean, | ||
58 | updateCollections?: boolean | ||
59 | ): Promise<MActorFullActor> | ||
60 | |||
61 | function getOrCreateActorAndServerAndModel ( | ||
62 | activityActor: string | ActivityPubActor, | ||
63 | fetchType?: 'association-ids', | ||
64 | recurseIfNeeded?: boolean, | ||
65 | updateCollections?: boolean | ||
66 | ): Promise<MActorAccountChannelId> | ||
67 | |||
40 | async function getOrCreateActorAndServerAndModel ( | 68 | async function getOrCreateActorAndServerAndModel ( |
41 | activityActor: string | ActivityPubActor, | 69 | activityActor: string | ActivityPubActor, |
42 | fetchType: ActorFetchByUrlType = 'actor-and-association-ids', | 70 | fetchType: ActorFetchByUrlType = 'association-ids', |
43 | recurseIfNeeded = true, | 71 | recurseIfNeeded = true, |
44 | updateCollections = false | 72 | updateCollections = false |
45 | ) { | 73 | ): Promise<MActorFullActor | MActorAccountChannelId> { |
46 | const actorUrl = getAPId(activityActor) | 74 | const actorUrl = getAPId(activityActor) |
47 | let created = false | 75 | let created = false |
48 | let accountPlaylistsUrl: string | 76 | let accountPlaylistsUrl: string |
@@ -61,7 +89,7 @@ async function getOrCreateActorAndServerAndModel ( | |||
61 | 89 | ||
62 | // Create the attributed to actor | 90 | // Create the attributed to actor |
63 | // In PeerTube a video channel is owned by an account | 91 | // In PeerTube a video channel is owned by an account |
64 | let ownerActor: ActorModel = undefined | 92 | let ownerActor: MActorFullActor |
65 | if (recurseIfNeeded === true && result.actor.type === 'Group') { | 93 | if (recurseIfNeeded === true && result.actor.type === 'Group') { |
66 | const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person') | 94 | const accountAttributedTo = result.attributedTo.find(a => a.type === 'Person') |
67 | if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url) | 95 | if (!accountAttributedTo) throw new Error('Cannot find account attributed to video channel ' + actor.url) |
@@ -85,8 +113,8 @@ async function getOrCreateActorAndServerAndModel ( | |||
85 | accountPlaylistsUrl = result.playlists | 113 | accountPlaylistsUrl = result.playlists |
86 | } | 114 | } |
87 | 115 | ||
88 | if (actor.Account) actor.Account.Actor = actor | 116 | if (actor.Account) (actor as MActorAccountChannelIdActor).Account.Actor = actor |
89 | if (actor.VideoChannel) actor.VideoChannel.Actor = actor | 117 | if (actor.VideoChannel) (actor as MActorAccountChannelIdActor).VideoChannel.Actor = actor |
90 | 118 | ||
91 | const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType) | 119 | const { actor: actorRefreshed, refreshed } = await retryTransactionWrapper(refreshActorIfNeeded, actor, fetchType) |
92 | if (!actorRefreshed) throw new Error('Actor ' + actorRefreshed.url + ' does not exist anymore.') | 120 | if (!actorRefreshed) throw new Error('Actor ' + actorRefreshed.url + ' does not exist anymore.') |
@@ -120,7 +148,7 @@ function buildActorInstance (type: ActivityPubActorType, url: string, preferredU | |||
120 | sharedInboxUrl: WEBSERVER.URL + '/inbox', | 148 | sharedInboxUrl: WEBSERVER.URL + '/inbox', |
121 | followersUrl: url + '/followers', | 149 | followersUrl: url + '/followers', |
122 | followingUrl: url + '/following' | 150 | followingUrl: url + '/following' |
123 | }) | 151 | }) as MActor |
124 | } | 152 | } |
125 | 153 | ||
126 | async function updateActorInstance (actorInstance: ActorModel, attributes: ActivityPubActor) { | 154 | async function updateActorInstance (actorInstance: ActorModel, attributes: ActivityPubActor) { |
@@ -140,7 +168,8 @@ async function updateActorInstance (actorInstance: ActorModel, attributes: Activ | |||
140 | actorInstance.followingUrl = attributes.following | 168 | actorInstance.followingUrl = attributes.following |
141 | } | 169 | } |
142 | 170 | ||
143 | async function updateActorAvatarInstance (actor: ActorModel, info: { name: string, onDisk: boolean, fileUrl: string }, t: Transaction) { | 171 | type AvatarInfo = { name: string, onDisk: boolean, fileUrl: string } |
172 | async function updateActorAvatarInstance (actor: MActorDefault, info: AvatarInfo, t: Transaction) { | ||
144 | if (info.name !== undefined) { | 173 | if (info.name !== undefined) { |
145 | if (actor.avatarId) { | 174 | if (actor.avatarId) { |
146 | try { | 175 | try { |
@@ -212,14 +241,16 @@ async function addFetchOutboxJob (actor: Pick<ActorModel, 'id' | 'outboxUrl'>) { | |||
212 | return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) | 241 | return JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload }) |
213 | } | 242 | } |
214 | 243 | ||
215 | async function refreshActorIfNeeded ( | 244 | async function refreshActorIfNeeded <T extends MActorFull | MActorAccountChannelId> ( |
216 | actorArg: ActorModel, | 245 | actorArg: T, |
217 | fetchedType: ActorFetchByUrlType | 246 | fetchedType: ActorFetchByUrlType |
218 | ): Promise<{ actor: ActorModel, refreshed: boolean }> { | 247 | ): Promise<{ actor: T | MActorFull, refreshed: boolean }> { |
219 | if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false } | 248 | if (!actorArg.isOutdated()) return { actor: actorArg, refreshed: false } |
220 | 249 | ||
221 | // We need more attributes | 250 | // We need more attributes |
222 | const actor = fetchedType === 'all' ? actorArg : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url) | 251 | const actor = fetchedType === 'all' |
252 | ? actorArg as MActorFull | ||
253 | : await ActorModel.loadByUrlAndPopulateAccountAndChannel(actorArg.url) | ||
223 | 254 | ||
224 | try { | 255 | try { |
225 | let actorUrl: string | 256 | let actorUrl: string |
@@ -297,9 +328,9 @@ export { | |||
297 | 328 | ||
298 | function saveActorAndServerAndModelIfNotExist ( | 329 | function saveActorAndServerAndModelIfNotExist ( |
299 | result: FetchRemoteActorResult, | 330 | result: FetchRemoteActorResult, |
300 | ownerActor?: ActorModel, | 331 | ownerActor?: MActorFullActor, |
301 | t?: Transaction | 332 | t?: Transaction |
302 | ): Bluebird<ActorModel> | Promise<ActorModel> { | 333 | ): Bluebird<MActorFullActor> | Promise<MActorFullActor> { |
303 | let actor = result.actor | 334 | let actor = result.actor |
304 | 335 | ||
305 | if (t !== undefined) return save(t) | 336 | if (t !== undefined) return save(t) |
@@ -336,7 +367,7 @@ function saveActorAndServerAndModelIfNotExist ( | |||
336 | 367 | ||
337 | // Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists | 368 | // Force the actor creation, sometimes Sequelize skips the save() when it thinks the instance already exists |
338 | // (which could be false in a retried query) | 369 | // (which could be false in a retried query) |
339 | const [ actorCreated ] = await ActorModel.findOrCreate({ | 370 | const [ actorCreated ] = await ActorModel.findOrCreate<MActorFullActor>({ |
340 | defaults: actor.toJSON(), | 371 | defaults: actor.toJSON(), |
341 | where: { | 372 | where: { |
342 | url: actor.url | 373 | url: actor.url |
@@ -345,12 +376,11 @@ function saveActorAndServerAndModelIfNotExist ( | |||
345 | }) | 376 | }) |
346 | 377 | ||
347 | if (actorCreated.type === 'Person' || actorCreated.type === 'Application') { | 378 | if (actorCreated.type === 'Person' || actorCreated.type === 'Application') { |
348 | actorCreated.Account = await saveAccount(actorCreated, result, t) | 379 | actorCreated.Account = await saveAccount(actorCreated, result, t) as MAccountDefault |
349 | actorCreated.Account.Actor = actorCreated | 380 | actorCreated.Account.Actor = actorCreated |
350 | } else if (actorCreated.type === 'Group') { // Video channel | 381 | } else if (actorCreated.type === 'Group') { // Video channel |
351 | actorCreated.VideoChannel = await saveVideoChannel(actorCreated, result, ownerActor, t) | 382 | const channel = await saveVideoChannel(actorCreated, result, ownerActor, t) |
352 | actorCreated.VideoChannel.Actor = actorCreated | 383 | actorCreated.VideoChannel = Object.assign(channel, { Actor: actorCreated, Account: ownerActor.Account }) |
353 | actorCreated.VideoChannel.Account = ownerActor.Account | ||
354 | } | 384 | } |
355 | 385 | ||
356 | actorCreated.Server = server | 386 | actorCreated.Server = server |
@@ -360,7 +390,7 @@ function saveActorAndServerAndModelIfNotExist ( | |||
360 | } | 390 | } |
361 | 391 | ||
362 | type FetchRemoteActorResult = { | 392 | type FetchRemoteActorResult = { |
363 | actor: ActorModel | 393 | actor: MActor |
364 | name: string | 394 | name: string |
365 | summary: string | 395 | summary: string |
366 | support?: string | 396 | support?: string |
@@ -429,7 +459,7 @@ async function fetchRemoteActor (actorUrl: string): Promise<{ statusCode?: numbe | |||
429 | } | 459 | } |
430 | } | 460 | } |
431 | 461 | ||
432 | async function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t: Transaction) { | 462 | async function saveAccount (actor: MActorId, result: FetchRemoteActorResult, t: Transaction) { |
433 | const [ accountCreated ] = await AccountModel.findOrCreate({ | 463 | const [ accountCreated ] = await AccountModel.findOrCreate({ |
434 | defaults: { | 464 | defaults: { |
435 | name: result.name, | 465 | name: result.name, |
@@ -442,10 +472,10 @@ async function saveAccount (actor: ActorModel, result: FetchRemoteActorResult, t | |||
442 | transaction: t | 472 | transaction: t |
443 | }) | 473 | }) |
444 | 474 | ||
445 | return accountCreated | 475 | return accountCreated as MAccount |
446 | } | 476 | } |
447 | 477 | ||
448 | async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResult, ownerActor: ActorModel, t: Transaction) { | 478 | async function saveVideoChannel (actor: MActorId, result: FetchRemoteActorResult, ownerActor: MActorAccountId, t: Transaction) { |
449 | const [ videoChannelCreated ] = await VideoChannelModel.findOrCreate({ | 479 | const [ videoChannelCreated ] = await VideoChannelModel.findOrCreate({ |
450 | defaults: { | 480 | defaults: { |
451 | name: result.name, | 481 | name: result.name, |
@@ -460,5 +490,5 @@ async function saveVideoChannel (actor: ActorModel, result: FetchRemoteActorResu | |||
460 | transaction: t | 490 | transaction: t |
461 | }) | 491 | }) |
462 | 492 | ||
463 | return videoChannelCreated | 493 | return videoChannelCreated as MChannel |
464 | } | 494 | } |
diff --git a/server/lib/activitypub/audience.ts b/server/lib/activitypub/audience.ts index 0e3d78590..f2ab54cf7 100644 --- a/server/lib/activitypub/audience.ts +++ b/server/lib/activitypub/audience.ts | |||
@@ -3,11 +3,10 @@ import { ActivityAudience } from '../../../shared/models/activitypub' | |||
3 | import { ACTIVITY_PUB } from '../../initializers/constants' | 3 | import { ACTIVITY_PUB } from '../../initializers/constants' |
4 | import { ActorModel } from '../../models/activitypub/actor' | 4 | import { ActorModel } from '../../models/activitypub/actor' |
5 | import { VideoModel } from '../../models/video/video' | 5 | import { VideoModel } from '../../models/video/video' |
6 | import { VideoCommentModel } from '../../models/video/video-comment' | ||
7 | import { VideoShareModel } from '../../models/video/video-share' | 6 | import { VideoShareModel } from '../../models/video/video-share' |
8 | import { ActorModelOnly } from '../../typings/models' | 7 | import { MActorFollowersUrl, MActorLight, MCommentOwner, MCommentOwnerVideo, MVideo, MVideoAccountLight } from '../../typings/models' |
9 | 8 | ||
10 | function getRemoteVideoAudience (video: VideoModel, actorsInvolvedInVideo: ActorModel[]): ActivityAudience { | 9 | function getRemoteVideoAudience (video: MVideoAccountLight, actorsInvolvedInVideo: MActorFollowersUrl[]): ActivityAudience { |
11 | return { | 10 | return { |
12 | to: [ video.VideoChannel.Account.Actor.url ], | 11 | to: [ video.VideoChannel.Account.Actor.url ], |
13 | cc: actorsInvolvedInVideo.map(a => a.followersUrl) | 12 | cc: actorsInvolvedInVideo.map(a => a.followersUrl) |
@@ -15,9 +14,9 @@ function getRemoteVideoAudience (video: VideoModel, actorsInvolvedInVideo: Actor | |||
15 | } | 14 | } |
16 | 15 | ||
17 | function getVideoCommentAudience ( | 16 | function getVideoCommentAudience ( |
18 | videoComment: VideoCommentModel, | 17 | videoComment: MCommentOwnerVideo, |
19 | threadParentComments: VideoCommentModel[], | 18 | threadParentComments: MCommentOwner[], |
20 | actorsInvolvedInVideo: ActorModel[], | 19 | actorsInvolvedInVideo: MActorFollowersUrl[], |
21 | isOrigin = false | 20 | isOrigin = false |
22 | ): ActivityAudience { | 21 | ): ActivityAudience { |
23 | const to = [ ACTIVITY_PUB.PUBLIC ] | 22 | const to = [ ACTIVITY_PUB.PUBLIC ] |
@@ -42,26 +41,28 @@ function getVideoCommentAudience ( | |||
42 | } | 41 | } |
43 | } | 42 | } |
44 | 43 | ||
45 | function getAudienceFromFollowersOf (actorsInvolvedInObject: ActorModel[]): ActivityAudience { | 44 | function getAudienceFromFollowersOf (actorsInvolvedInObject: MActorFollowersUrl[]): ActivityAudience { |
46 | return { | 45 | return { |
47 | to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)), | 46 | to: [ ACTIVITY_PUB.PUBLIC ].concat(actorsInvolvedInObject.map(a => a.followersUrl)), |
48 | cc: [] | 47 | cc: [] |
49 | } | 48 | } |
50 | } | 49 | } |
51 | 50 | ||
52 | async function getActorsInvolvedInVideo (video: VideoModel, t: Transaction) { | 51 | async function getActorsInvolvedInVideo (video: MVideo, t: Transaction) { |
53 | const actors = await VideoShareModel.loadActorsByShare(video.id, t) | 52 | const actors: MActorLight[] = await VideoShareModel.loadActorsByShare(video.id, t) |
54 | 53 | ||
55 | const videoActor = video.VideoChannel && video.VideoChannel.Account | 54 | const videoAll = video as VideoModel |
56 | ? video.VideoChannel.Account.Actor | 55 | |
57 | : await ActorModel.loadAccountActorByVideoId(video.id, t) | 56 | const videoActor = videoAll.VideoChannel && videoAll.VideoChannel.Account |
57 | ? videoAll.VideoChannel.Account.Actor | ||
58 | : await ActorModel.loadFromAccountByVideoId(video.id, t) | ||
58 | 59 | ||
59 | actors.push(videoActor) | 60 | actors.push(videoActor) |
60 | 61 | ||
61 | return actors | 62 | return actors |
62 | } | 63 | } |
63 | 64 | ||
64 | function getAudience (actorSender: ActorModelOnly, isPublic = true) { | 65 | function getAudience (actorSender: MActorFollowersUrl, isPublic = true) { |
65 | return buildAudience([ actorSender.followersUrl ], isPublic) | 66 | return buildAudience([ actorSender.followersUrl ], isPublic) |
66 | } | 67 | } |
67 | 68 | ||
diff --git a/server/lib/activitypub/cache-file.ts b/server/lib/activitypub/cache-file.ts index de5cc54ac..65b2dcb49 100644 --- a/server/lib/activitypub/cache-file.ts +++ b/server/lib/activitypub/cache-file.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { CacheFileObject } from '../../../shared/index' | 1 | import { CacheFileObject } from '../../../shared/index' |
2 | import { VideoModel } from '../../models/video/video' | ||
3 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' | 2 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' |
4 | import { Transaction } from 'sequelize' | 3 | import { Transaction } from 'sequelize' |
5 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | 4 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' |
5 | import { MActorId, MVideoRedundancy, MVideoWithAllFiles } from '@server/typings/models' | ||
6 | 6 | ||
7 | function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }) { | 7 | function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId) { |
8 | 8 | ||
9 | if (cacheFileObject.url.mediaType === 'application/x-mpegURL') { | 9 | if (cacheFileObject.url.mediaType === 'application/x-mpegURL') { |
10 | const url = cacheFileObject.url | 10 | const url = cacheFileObject.url |
@@ -39,7 +39,7 @@ function cacheFileActivityObjectToDBAttributes (cacheFileObject: CacheFileObject | |||
39 | } | 39 | } |
40 | } | 40 | } |
41 | 41 | ||
42 | async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }, t: Transaction) { | 42 | async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) { |
43 | const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t) | 43 | const redundancyModel = await VideoRedundancyModel.loadByUrl(cacheFileObject.id, t) |
44 | 44 | ||
45 | if (!redundancyModel) { | 45 | if (!redundancyModel) { |
@@ -49,7 +49,7 @@ async function createOrUpdateCacheFile (cacheFileObject: CacheFileObject, video: | |||
49 | } | 49 | } |
50 | } | 50 | } |
51 | 51 | ||
52 | function createCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, byActor: { id?: number }, t: Transaction) { | 52 | function createCacheFile (cacheFileObject: CacheFileObject, video: MVideoWithAllFiles, byActor: MActorId, t: Transaction) { |
53 | const attributes = cacheFileActivityObjectToDBAttributes(cacheFileObject, video, byActor) | 53 | const attributes = cacheFileActivityObjectToDBAttributes(cacheFileObject, video, byActor) |
54 | 54 | ||
55 | return VideoRedundancyModel.create(attributes, { transaction: t }) | 55 | return VideoRedundancyModel.create(attributes, { transaction: t }) |
@@ -57,9 +57,9 @@ function createCacheFile (cacheFileObject: CacheFileObject, video: VideoModel, b | |||
57 | 57 | ||
58 | function updateCacheFile ( | 58 | function updateCacheFile ( |
59 | cacheFileObject: CacheFileObject, | 59 | cacheFileObject: CacheFileObject, |
60 | redundancyModel: VideoRedundancyModel, | 60 | redundancyModel: MVideoRedundancy, |
61 | video: VideoModel, | 61 | video: MVideoWithAllFiles, |
62 | byActor: { id?: number }, | 62 | byActor: MActorId, |
63 | t: Transaction | 63 | t: Transaction |
64 | ) { | 64 | ) { |
65 | if (redundancyModel.actorId !== byActor.id) { | 65 | if (redundancyModel.actorId !== byActor.id) { |
diff --git a/server/lib/activitypub/follow.ts b/server/lib/activitypub/follow.ts new file mode 100644 index 000000000..1abf43cd4 --- /dev/null +++ b/server/lib/activitypub/follow.ts | |||
@@ -0,0 +1,36 @@ | |||
1 | import { MActorFollowActors } from '../../typings/models' | ||
2 | import { CONFIG } from '../../initializers/config' | ||
3 | import { SERVER_ACTOR_NAME } from '../../initializers/constants' | ||
4 | import { JobQueue } from '../job-queue' | ||
5 | import { logger } from '../../helpers/logger' | ||
6 | import { getServerActor } from '../../helpers/utils' | ||
7 | import { ServerModel } from '../../models/server/server' | ||
8 | |||
9 | async function autoFollowBackIfNeeded (actorFollow: MActorFollowActors) { | ||
10 | if (!CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_BACK.ENABLED) return | ||
11 | |||
12 | const follower = actorFollow.ActorFollower | ||
13 | |||
14 | if (follower.type === 'Application' && follower.preferredUsername === SERVER_ACTOR_NAME) { | ||
15 | logger.info('Auto follow back %s.', follower.url) | ||
16 | |||
17 | const me = await getServerActor() | ||
18 | |||
19 | const server = await ServerModel.load(follower.serverId) | ||
20 | const host = server.host | ||
21 | |||
22 | const payload = { | ||
23 | host, | ||
24 | name: SERVER_ACTOR_NAME, | ||
25 | followerActorId: me.id, | ||
26 | isAutoFollow: true | ||
27 | } | ||
28 | |||
29 | JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) | ||
30 | .catch(err => logger.error('Cannot create auto follow back job for %s.', host, err)) | ||
31 | } | ||
32 | } | ||
33 | |||
34 | export { | ||
35 | autoFollowBackIfNeeded | ||
36 | } | ||
diff --git a/server/lib/activitypub/playlist.ts b/server/lib/activitypub/playlist.ts index c2e2a3283..c52b715ef 100644 --- a/server/lib/activitypub/playlist.ts +++ b/server/lib/activitypub/playlist.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object' | 1 | import { PlaylistObject } from '../../../shared/models/activitypub/objects/playlist-object' |
2 | import { crawlCollectionPage } from './crawl' | 2 | import { crawlCollectionPage } from './crawl' |
3 | import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' | 3 | import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
4 | import { AccountModel } from '../../models/account/account' | ||
5 | import { isArray } from '../../helpers/custom-validators/misc' | 4 | import { isArray } from '../../helpers/custom-validators/misc' |
6 | import { getOrCreateActorAndServerAndModel } from './actor' | 5 | import { getOrCreateActorAndServerAndModel } from './actor' |
7 | import { logger } from '../../helpers/logger' | 6 | import { logger } from '../../helpers/logger' |
@@ -13,14 +12,14 @@ import { PlaylistElementObject } from '../../../shared/models/activitypub/object | |||
13 | import { getOrCreateVideoAndAccountAndChannel } from './videos' | 12 | import { getOrCreateVideoAndAccountAndChannel } from './videos' |
14 | import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '../../helpers/custom-validators/activitypub/playlist' | 13 | import { isPlaylistElementObjectValid, isPlaylistObjectValid } from '../../helpers/custom-validators/activitypub/playlist' |
15 | import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' | 14 | import { VideoPlaylistElementModel } from '../../models/video/video-playlist-element' |
16 | import { VideoModel } from '../../models/video/video' | ||
17 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' | 15 | import { VideoPlaylistPrivacy } from '../../../shared/models/videos/playlist/video-playlist-privacy.model' |
18 | import { sequelizeTypescript } from '../../initializers/database' | 16 | import { sequelizeTypescript } from '../../initializers/database' |
19 | import { createPlaylistMiniatureFromUrl } from '../thumbnail' | 17 | import { createPlaylistMiniatureFromUrl } from '../thumbnail' |
20 | import { FilteredModelAttributes } from '../../typings/sequelize' | 18 | import { FilteredModelAttributes } from '../../typings/sequelize' |
21 | import { AccountModelId } from '../../typings/models' | 19 | import { MAccountDefault, MAccountId, MVideoId } from '../../typings/models' |
20 | import { MVideoPlaylist, MVideoPlaylistId, MVideoPlaylistOwner } from '../../typings/models/video/video-playlist' | ||
22 | 21 | ||
23 | function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) { | 22 | function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) { |
24 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED | 23 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPlaylistPrivacy.PUBLIC : VideoPlaylistPrivacy.UNLISTED |
25 | 24 | ||
26 | return { | 25 | return { |
@@ -36,7 +35,7 @@ function playlistObjectToDBAttributes (playlistObject: PlaylistObject, byAccount | |||
36 | } | 35 | } |
37 | } | 36 | } |
38 | 37 | ||
39 | function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: VideoPlaylistModel, video: VideoModel) { | 38 | function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObject, videoPlaylist: MVideoPlaylistId, video: MVideoId) { |
40 | return { | 39 | return { |
41 | position: elementObject.position, | 40 | position: elementObject.position, |
42 | url: elementObject.id, | 41 | url: elementObject.id, |
@@ -47,7 +46,7 @@ function playlistElementObjectToDBAttributes (elementObject: PlaylistElementObje | |||
47 | } | 46 | } |
48 | } | 47 | } |
49 | 48 | ||
50 | async function createAccountPlaylists (playlistUrls: string[], account: AccountModel) { | 49 | async function createAccountPlaylists (playlistUrls: string[], account: MAccountDefault) { |
51 | await Bluebird.map(playlistUrls, async playlistUrl => { | 50 | await Bluebird.map(playlistUrls, async playlistUrl => { |
52 | try { | 51 | try { |
53 | const exists = await VideoPlaylistModel.doesPlaylistExist(playlistUrl) | 52 | const exists = await VideoPlaylistModel.doesPlaylistExist(playlistUrl) |
@@ -75,7 +74,7 @@ async function createAccountPlaylists (playlistUrls: string[], account: AccountM | |||
75 | }, { concurrency: CRAWL_REQUEST_CONCURRENCY }) | 74 | }, { concurrency: CRAWL_REQUEST_CONCURRENCY }) |
76 | } | 75 | } |
77 | 76 | ||
78 | async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: AccountModelId, to: string[]) { | 77 | async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAccount: MAccountId, to: string[]) { |
79 | const playlistAttributes = playlistObjectToDBAttributes(playlistObject, byAccount, to) | 78 | const playlistAttributes = playlistObjectToDBAttributes(playlistObject, byAccount, to) |
80 | 79 | ||
81 | if (isArray(playlistObject.attributedTo) && playlistObject.attributedTo.length === 1) { | 80 | if (isArray(playlistObject.attributedTo) && playlistObject.attributedTo.length === 1) { |
@@ -88,7 +87,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc | |||
88 | } | 87 | } |
89 | } | 88 | } |
90 | 89 | ||
91 | const [ playlist ] = await VideoPlaylistModel.upsert<VideoPlaylistModel>(playlistAttributes, { returning: true }) | 90 | const [ playlist ] = await VideoPlaylistModel.upsert<MVideoPlaylist>(playlistAttributes, { returning: true }) |
92 | 91 | ||
93 | let accItems: string[] = [] | 92 | let accItems: string[] = [] |
94 | await crawlCollectionPage<string>(playlistObject.id, items => { | 93 | await crawlCollectionPage<string>(playlistObject.id, items => { |
@@ -114,7 +113,7 @@ async function createOrUpdateVideoPlaylist (playlistObject: PlaylistObject, byAc | |||
114 | return resetVideoPlaylistElements(accItems, refreshedPlaylist) | 113 | return resetVideoPlaylistElements(accItems, refreshedPlaylist) |
115 | } | 114 | } |
116 | 115 | ||
117 | async function refreshVideoPlaylistIfNeeded (videoPlaylist: VideoPlaylistModel): Promise<VideoPlaylistModel> { | 116 | async function refreshVideoPlaylistIfNeeded (videoPlaylist: MVideoPlaylistOwner): Promise<MVideoPlaylistOwner> { |
118 | if (!videoPlaylist.isOutdated()) return videoPlaylist | 117 | if (!videoPlaylist.isOutdated()) return videoPlaylist |
119 | 118 | ||
120 | try { | 119 | try { |
@@ -157,7 +156,7 @@ export { | |||
157 | 156 | ||
158 | // --------------------------------------------------------------------------- | 157 | // --------------------------------------------------------------------------- |
159 | 158 | ||
160 | async function resetVideoPlaylistElements (elementUrls: string[], playlist: VideoPlaylistModel) { | 159 | async function resetVideoPlaylistElements (elementUrls: string[], playlist: MVideoPlaylist) { |
161 | const elementsToCreate: FilteredModelAttributes<VideoPlaylistElementModel>[] = [] | 160 | const elementsToCreate: FilteredModelAttributes<VideoPlaylistElementModel>[] = [] |
162 | 161 | ||
163 | await Bluebird.map(elementUrls, async elementUrl => { | 162 | await Bluebird.map(elementUrls, async elementUrl => { |
diff --git a/server/lib/activitypub/process/process-accept.ts b/server/lib/activitypub/process/process-accept.ts index cf27e6c32..dcfbb2c84 100644 --- a/server/lib/activitypub/process/process-accept.ts +++ b/server/lib/activitypub/process/process-accept.ts | |||
@@ -1,9 +1,8 @@ | |||
1 | import { ActivityAccept } from '../../../../shared/models/activitypub' | 1 | import { ActivityAccept } from '../../../../shared/models/activitypub' |
2 | import { ActorModel } from '../../../models/activitypub/actor' | ||
3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 2 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
4 | import { addFetchOutboxJob } from '../actor' | 3 | import { addFetchOutboxJob } from '../actor' |
5 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 4 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
6 | import { SignatureActorModel } from '../../../typings/models' | 5 | import { MActorDefault, MActorSignature } from '../../../typings/models' |
7 | 6 | ||
8 | async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) { | 7 | async function processAcceptActivity (options: APProcessorOptions<ActivityAccept>) { |
9 | const { byActor: targetActor, inboxActor } = options | 8 | const { byActor: targetActor, inboxActor } = options |
@@ -20,12 +19,12 @@ export { | |||
20 | 19 | ||
21 | // --------------------------------------------------------------------------- | 20 | // --------------------------------------------------------------------------- |
22 | 21 | ||
23 | async function processAccept (actor: ActorModel, targetActor: SignatureActorModel) { | 22 | async function processAccept (actor: MActorDefault, targetActor: MActorSignature) { |
24 | const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, targetActor.id) | 23 | const follow = await ActorFollowModel.loadByActorAndTarget(actor.id, targetActor.id) |
25 | if (!follow) throw new Error('Cannot find associated follow.') | 24 | if (!follow) throw new Error('Cannot find associated follow.') |
26 | 25 | ||
27 | if (follow.state !== 'accepted') { | 26 | if (follow.state !== 'accepted') { |
28 | follow.set('state', 'accepted') | 27 | follow.state = 'accepted' |
29 | await follow.save() | 28 | await follow.save() |
30 | 29 | ||
31 | await addFetchOutboxJob(targetActor) | 30 | await addFetchOutboxJob(targetActor) |
diff --git a/server/lib/activitypub/process/process-announce.ts b/server/lib/activitypub/process/process-announce.ts index b3cdc4441..7e22125d5 100644 --- a/server/lib/activitypub/process/process-announce.ts +++ b/server/lib/activitypub/process/process-announce.ts | |||
@@ -5,10 +5,9 @@ import { VideoShareModel } from '../../../models/video/video-share' | |||
5 | import { forwardVideoRelatedActivity } from '../send/utils' | 5 | import { forwardVideoRelatedActivity } from '../send/utils' |
6 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' | 6 | import { getOrCreateVideoAndAccountAndChannel } from '../videos' |
7 | import { Notifier } from '../../notifier' | 7 | import { Notifier } from '../../notifier' |
8 | import { VideoModel } from '../../../models/video/video' | ||
9 | import { logger } from '../../../helpers/logger' | 8 | import { logger } from '../../../helpers/logger' |
10 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 9 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
11 | import { SignatureActorModel } from '../../../typings/models' | 10 | import { MActorSignature, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models' |
12 | 11 | ||
13 | async function processAnnounceActivity (options: APProcessorOptions<ActivityAnnounce>) { | 12 | async function processAnnounceActivity (options: APProcessorOptions<ActivityAnnounce>) { |
14 | const { activity, byActor: actorAnnouncer } = options | 13 | const { activity, byActor: actorAnnouncer } = options |
@@ -26,10 +25,10 @@ export { | |||
26 | 25 | ||
27 | // --------------------------------------------------------------------------- | 26 | // --------------------------------------------------------------------------- |
28 | 27 | ||
29 | async function processVideoShare (actorAnnouncer: SignatureActorModel, activity: ActivityAnnounce, notify: boolean) { | 28 | async function processVideoShare (actorAnnouncer: MActorSignature, activity: ActivityAnnounce, notify: boolean) { |
30 | const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id | 29 | const objectUri = typeof activity.object === 'string' ? activity.object : activity.object.id |
31 | 30 | ||
32 | let video: VideoModel | 31 | let video: MVideoAccountLightBlacklistAllFiles |
33 | let videoCreated: boolean | 32 | let videoCreated: boolean |
34 | 33 | ||
35 | try { | 34 | try { |
diff --git a/server/lib/activitypub/process/process-create.ts b/server/lib/activitypub/process/process-create.ts index 6815c6997..bee853721 100644 --- a/server/lib/activitypub/process/process-create.ts +++ b/server/lib/activitypub/process/process-create.ts | |||
@@ -10,10 +10,8 @@ import { createOrUpdateCacheFile } from '../cache-file' | |||
10 | import { Notifier } from '../../notifier' | 10 | import { Notifier } from '../../notifier' |
11 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' | 11 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' |
12 | import { createOrUpdateVideoPlaylist } from '../playlist' | 12 | import { createOrUpdateVideoPlaylist } from '../playlist' |
13 | import { VideoModel } from '../../../models/video/video' | ||
14 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 13 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
15 | import { VideoCommentModel } from '../../../models/video/video-comment' | 14 | import { MActorSignature, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../../typings/models' |
16 | import { SignatureActorModel } from '../../../typings/models' | ||
17 | 15 | ||
18 | async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) { | 16 | async function processCreateActivity (options: APProcessorOptions<ActivityCreate>) { |
19 | const { activity, byActor } = options | 17 | const { activity, byActor } = options |
@@ -61,7 +59,7 @@ async function processCreateVideo (activity: ActivityCreate, notify: boolean) { | |||
61 | return video | 59 | return video |
62 | } | 60 | } |
63 | 61 | ||
64 | async function processCreateCacheFile (activity: ActivityCreate, byActor: SignatureActorModel) { | 62 | async function processCreateCacheFile (activity: ActivityCreate, byActor: MActorSignature) { |
65 | const cacheFile = activity.object as CacheFileObject | 63 | const cacheFile = activity.object as CacheFileObject |
66 | 64 | ||
67 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object }) | 65 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFile.object }) |
@@ -77,15 +75,15 @@ async function processCreateCacheFile (activity: ActivityCreate, byActor: Signat | |||
77 | } | 75 | } |
78 | } | 76 | } |
79 | 77 | ||
80 | async function processCreateVideoComment (activity: ActivityCreate, byActor: SignatureActorModel, notify: boolean) { | 78 | async function processCreateVideoComment (activity: ActivityCreate, byActor: MActorSignature, notify: boolean) { |
81 | const commentObject = activity.object as VideoCommentObject | 79 | const commentObject = activity.object as VideoCommentObject |
82 | const byAccount = byActor.Account | 80 | const byAccount = byActor.Account |
83 | 81 | ||
84 | if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) | 82 | if (!byAccount) throw new Error('Cannot create video comment with the non account actor ' + byActor.url) |
85 | 83 | ||
86 | let video: VideoModel | 84 | let video: MVideoAccountLightBlacklistAllFiles |
87 | let created: boolean | 85 | let created: boolean |
88 | let comment: VideoCommentModel | 86 | let comment: MCommentOwnerVideo |
89 | try { | 87 | try { |
90 | const resolveThreadResult = await resolveThread({ url: commentObject.id, isVideo: false }) | 88 | const resolveThreadResult = await resolveThread({ url: commentObject.id, isVideo: false }) |
91 | video = resolveThreadResult.video | 89 | video = resolveThreadResult.video |
@@ -110,7 +108,7 @@ async function processCreateVideoComment (activity: ActivityCreate, byActor: Sig | |||
110 | if (created && notify) Notifier.Instance.notifyOnNewComment(comment) | 108 | if (created && notify) Notifier.Instance.notifyOnNewComment(comment) |
111 | } | 109 | } |
112 | 110 | ||
113 | async function processCreatePlaylist (activity: ActivityCreate, byActor: SignatureActorModel) { | 111 | async function processCreatePlaylist (activity: ActivityCreate, byActor: MActorSignature) { |
114 | const playlistObject = activity.object as PlaylistObject | 112 | const playlistObject = activity.object as PlaylistObject |
115 | const byAccount = byActor.Account | 113 | const byAccount = byActor.Account |
116 | 114 | ||
diff --git a/server/lib/activitypub/process/process-delete.ts b/server/lib/activitypub/process/process-delete.ts index 344d14322..79d0e0d79 100644 --- a/server/lib/activitypub/process/process-delete.ts +++ b/server/lib/activitypub/process/process-delete.ts | |||
@@ -2,15 +2,13 @@ import { ActivityDelete } from '../../../../shared/models/activitypub' | |||
2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' | 2 | import { retryTransactionWrapper } from '../../../helpers/database-utils' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { sequelizeTypescript } from '../../../initializers' | 4 | import { sequelizeTypescript } from '../../../initializers' |
5 | import { AccountModel } from '../../../models/account/account' | ||
6 | import { ActorModel } from '../../../models/activitypub/actor' | 5 | import { ActorModel } from '../../../models/activitypub/actor' |
7 | import { VideoModel } from '../../../models/video/video' | 6 | import { VideoModel } from '../../../models/video/video' |
8 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
9 | import { VideoCommentModel } from '../../../models/video/video-comment' | 7 | import { VideoCommentModel } from '../../../models/video/video-comment' |
10 | import { forwardVideoRelatedActivity } from '../send/utils' | 8 | import { forwardVideoRelatedActivity } from '../send/utils' |
11 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | 9 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' |
12 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 10 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
13 | import { SignatureActorModel } from '../../../typings/models' | 11 | import { MAccountActor, MActor, MActorSignature, MChannelActor, MChannelActorAccountActor } from '../../../typings/models' |
14 | 12 | ||
15 | async function processDeleteActivity (options: APProcessorOptions<ActivityDelete>) { | 13 | async function processDeleteActivity (options: APProcessorOptions<ActivityDelete>) { |
16 | const { activity, byActor } = options | 14 | const { activity, byActor } = options |
@@ -24,13 +22,17 @@ async function processDeleteActivity (options: APProcessorOptions<ActivityDelete | |||
24 | if (byActorFull.type === 'Person') { | 22 | if (byActorFull.type === 'Person') { |
25 | if (!byActorFull.Account) throw new Error('Actor ' + byActorFull.url + ' is a person but we cannot find it in database.') | 23 | if (!byActorFull.Account) throw new Error('Actor ' + byActorFull.url + ' is a person but we cannot find it in database.') |
26 | 24 | ||
27 | byActorFull.Account.Actor = await byActorFull.Account.$get('Actor') as ActorModel | 25 | const accountToDelete = byActorFull.Account as MAccountActor |
28 | return retryTransactionWrapper(processDeleteAccount, byActorFull.Account) | 26 | accountToDelete.Actor = byActorFull |
27 | |||
28 | return retryTransactionWrapper(processDeleteAccount, accountToDelete) | ||
29 | } else if (byActorFull.type === 'Group') { | 29 | } else if (byActorFull.type === 'Group') { |
30 | if (!byActorFull.VideoChannel) throw new Error('Actor ' + byActorFull.url + ' is a group but we cannot find it in database.') | 30 | if (!byActorFull.VideoChannel) throw new Error('Actor ' + byActorFull.url + ' is a group but we cannot find it in database.') |
31 | 31 | ||
32 | byActorFull.VideoChannel.Actor = await byActorFull.VideoChannel.$get('Actor') as ActorModel | 32 | const channelToDelete = byActorFull.VideoChannel as MChannelActorAccountActor |
33 | return retryTransactionWrapper(processDeleteVideoChannel, byActorFull.VideoChannel) | 33 | channelToDelete.Actor = byActorFull |
34 | |||
35 | return retryTransactionWrapper(processDeleteVideoChannel, channelToDelete) | ||
34 | } | 36 | } |
35 | } | 37 | } |
36 | 38 | ||
@@ -70,7 +72,7 @@ export { | |||
70 | 72 | ||
71 | // --------------------------------------------------------------------------- | 73 | // --------------------------------------------------------------------------- |
72 | 74 | ||
73 | async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel) { | 75 | async function processDeleteVideo (actor: MActor, videoToDelete: VideoModel) { |
74 | logger.debug('Removing remote video "%s".', videoToDelete.uuid) | 76 | logger.debug('Removing remote video "%s".', videoToDelete.uuid) |
75 | 77 | ||
76 | await sequelizeTypescript.transaction(async t => { | 78 | await sequelizeTypescript.transaction(async t => { |
@@ -84,7 +86,7 @@ async function processDeleteVideo (actor: ActorModel, videoToDelete: VideoModel) | |||
84 | logger.info('Remote video with uuid %s removed.', videoToDelete.uuid) | 86 | logger.info('Remote video with uuid %s removed.', videoToDelete.uuid) |
85 | } | 87 | } |
86 | 88 | ||
87 | async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete: VideoPlaylistModel) { | 89 | async function processDeleteVideoPlaylist (actor: MActor, playlistToDelete: VideoPlaylistModel) { |
88 | logger.debug('Removing remote video playlist "%s".', playlistToDelete.uuid) | 90 | logger.debug('Removing remote video playlist "%s".', playlistToDelete.uuid) |
89 | 91 | ||
90 | await sequelizeTypescript.transaction(async t => { | 92 | await sequelizeTypescript.transaction(async t => { |
@@ -98,7 +100,7 @@ async function processDeleteVideoPlaylist (actor: ActorModel, playlistToDelete: | |||
98 | logger.info('Remote video playlist with uuid %s removed.', playlistToDelete.uuid) | 100 | logger.info('Remote video playlist with uuid %s removed.', playlistToDelete.uuid) |
99 | } | 101 | } |
100 | 102 | ||
101 | async function processDeleteAccount (accountToRemove: AccountModel) { | 103 | async function processDeleteAccount (accountToRemove: MAccountActor) { |
102 | logger.debug('Removing remote account "%s".', accountToRemove.Actor.url) | 104 | logger.debug('Removing remote account "%s".', accountToRemove.Actor.url) |
103 | 105 | ||
104 | await sequelizeTypescript.transaction(async t => { | 106 | await sequelizeTypescript.transaction(async t => { |
@@ -108,7 +110,7 @@ async function processDeleteAccount (accountToRemove: AccountModel) { | |||
108 | logger.info('Remote account %s removed.', accountToRemove.Actor.url) | 110 | logger.info('Remote account %s removed.', accountToRemove.Actor.url) |
109 | } | 111 | } |
110 | 112 | ||
111 | async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelModel) { | 113 | async function processDeleteVideoChannel (videoChannelToRemove: MChannelActor) { |
112 | logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.url) | 114 | logger.debug('Removing remote video channel "%s".', videoChannelToRemove.Actor.url) |
113 | 115 | ||
114 | await sequelizeTypescript.transaction(async t => { | 116 | await sequelizeTypescript.transaction(async t => { |
@@ -118,7 +120,7 @@ async function processDeleteVideoChannel (videoChannelToRemove: VideoChannelMode | |||
118 | logger.info('Remote video channel %s removed.', videoChannelToRemove.Actor.url) | 120 | logger.info('Remote video channel %s removed.', videoChannelToRemove.Actor.url) |
119 | } | 121 | } |
120 | 122 | ||
121 | function processDeleteVideoComment (byActor: SignatureActorModel, videoComment: VideoCommentModel, activity: ActivityDelete) { | 123 | function processDeleteVideoComment (byActor: MActorSignature, videoComment: VideoCommentModel, activity: ActivityDelete) { |
122 | logger.debug('Removing remote video comment "%s".', videoComment.url) | 124 | logger.debug('Removing remote video comment "%s".', videoComment.url) |
123 | 125 | ||
124 | return sequelizeTypescript.transaction(async t => { | 126 | return sequelizeTypescript.transaction(async t => { |
diff --git a/server/lib/activitypub/process/process-dislike.ts b/server/lib/activitypub/process/process-dislike.ts index 727fcfee0..debd8a67c 100644 --- a/server/lib/activitypub/process/process-dislike.ts +++ b/server/lib/activitypub/process/process-dislike.ts | |||
@@ -7,7 +7,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos' | |||
7 | import { forwardVideoRelatedActivity } from '../send/utils' | 7 | import { forwardVideoRelatedActivity } from '../send/utils' |
8 | import { getVideoDislikeActivityPubUrl } from '../url' | 8 | import { getVideoDislikeActivityPubUrl } from '../url' |
9 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 9 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
10 | import { SignatureActorModel } from '../../../typings/models' | 10 | import { MActorSignature } from '../../../typings/models' |
11 | 11 | ||
12 | async function processDislikeActivity (options: APProcessorOptions<ActivityCreate | ActivityDislike>) { | 12 | async function processDislikeActivity (options: APProcessorOptions<ActivityCreate | ActivityDislike>) { |
13 | const { activity, byActor } = options | 13 | const { activity, byActor } = options |
@@ -22,7 +22,7 @@ export { | |||
22 | 22 | ||
23 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
24 | 24 | ||
25 | async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: SignatureActorModel) { | 25 | async function processDislike (activity: ActivityCreate | ActivityDislike, byActor: MActorSignature) { |
26 | const dislikeObject = activity.type === 'Dislike' ? activity.object : (activity.object as DislikeObject).object | 26 | const dislikeObject = activity.type === 'Dislike' ? activity.object : (activity.object as DislikeObject).object |
27 | const byAccount = byActor.Account | 27 | const byAccount = byActor.Account |
28 | 28 | ||
diff --git a/server/lib/activitypub/process/process-flag.ts b/server/lib/activitypub/process/process-flag.ts index 1f8a80c14..e6e9084de 100644 --- a/server/lib/activitypub/process/process-flag.ts +++ b/server/lib/activitypub/process/process-flag.ts | |||
@@ -8,7 +8,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos' | |||
8 | import { Notifier } from '../../notifier' | 8 | import { Notifier } from '../../notifier' |
9 | import { getAPId } from '../../../helpers/activitypub' | 9 | import { getAPId } from '../../../helpers/activitypub' |
10 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 10 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
11 | import { SignatureActorModel } from '../../../typings/models' | 11 | import { MActorSignature, MVideoAbuseVideo } from '../../../typings/models' |
12 | 12 | ||
13 | async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) { | 13 | async function processFlagActivity (options: APProcessorOptions<ActivityCreate | ActivityFlag>) { |
14 | const { activity, byActor } = options | 14 | const { activity, byActor } = options |
@@ -23,31 +23,39 @@ export { | |||
23 | 23 | ||
24 | // --------------------------------------------------------------------------- | 24 | // --------------------------------------------------------------------------- |
25 | 25 | ||
26 | async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: SignatureActorModel) { | 26 | async function processCreateVideoAbuse (activity: ActivityCreate | ActivityFlag, byActor: MActorSignature) { |
27 | const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject) | 27 | const flag = activity.type === 'Flag' ? activity : (activity.object as VideoAbuseObject) |
28 | 28 | ||
29 | logger.debug('Reporting remote abuse for video %s.', getAPId(flag.object)) | ||
30 | |||
31 | const account = byActor.Account | 29 | const account = byActor.Account |
32 | if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url) | 30 | if (!account) throw new Error('Cannot create video abuse with the non account actor ' + byActor.url) |
33 | 31 | ||
34 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: flag.object }) | 32 | const objects = Array.isArray(flag.object) ? flag.object : [ flag.object ] |
35 | 33 | ||
36 | const videoAbuse = await sequelizeTypescript.transaction(async t => { | 34 | for (const object of objects) { |
37 | const videoAbuseData = { | 35 | try { |
38 | reporterAccountId: account.id, | 36 | logger.debug('Reporting remote abuse for video %s.', getAPId(object)) |
39 | reason: flag.content, | 37 | |
40 | videoId: video.id, | 38 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: object }) |
41 | state: VideoAbuseState.PENDING | ||
42 | } | ||
43 | 39 | ||
44 | const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) | 40 | const videoAbuse = await sequelizeTypescript.transaction(async t => { |
45 | videoAbuseInstance.Video = video | 41 | const videoAbuseData = { |
42 | reporterAccountId: account.id, | ||
43 | reason: flag.content, | ||
44 | videoId: video.id, | ||
45 | state: VideoAbuseState.PENDING | ||
46 | } | ||
46 | 47 | ||
47 | logger.info('Remote abuse for video uuid %s created', flag.object) | 48 | const videoAbuseInstance = await VideoAbuseModel.create(videoAbuseData, { transaction: t }) as MVideoAbuseVideo |
49 | videoAbuseInstance.Video = video | ||
48 | 50 | ||
49 | return videoAbuseInstance | 51 | logger.info('Remote abuse for video uuid %s created', flag.object) |
50 | }) | ||
51 | 52 | ||
52 | Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse) | 53 | return videoAbuseInstance |
54 | }) | ||
55 | |||
56 | Notifier.Instance.notifyOnNewVideoAbuse(videoAbuse) | ||
57 | } catch (err) { | ||
58 | logger.debug('Cannot process report of %s. (Maybe not a video abuse).', getAPId(object), { err }) | ||
59 | } | ||
60 | } | ||
53 | } | 61 | } |
diff --git a/server/lib/activitypub/process/process-follow.ts b/server/lib/activitypub/process/process-follow.ts index 240aa5799..85f22d654 100644 --- a/server/lib/activitypub/process/process-follow.ts +++ b/server/lib/activitypub/process/process-follow.ts | |||
@@ -10,8 +10,8 @@ import { getAPId } from '../../../helpers/activitypub' | |||
10 | import { getServerActor } from '../../../helpers/utils' | 10 | import { getServerActor } from '../../../helpers/utils' |
11 | import { CONFIG } from '../../../initializers/config' | 11 | import { CONFIG } from '../../../initializers/config' |
12 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 12 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
13 | import { SignatureActorModel } from '../../../typings/models' | 13 | import { MActorFollowActors, MActorSignature } from '../../../typings/models' |
14 | import { ActorFollowModelLight } from '../../../typings/models/actor-follow' | 14 | import { autoFollowBackIfNeeded } from '../follow' |
15 | 15 | ||
16 | async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) { | 16 | async function processFollowActivity (options: APProcessorOptions<ActivityFollow>) { |
17 | const { activity, byActor } = options | 17 | const { activity, byActor } = options |
@@ -28,8 +28,8 @@ export { | |||
28 | 28 | ||
29 | // --------------------------------------------------------------------------- | 29 | // --------------------------------------------------------------------------- |
30 | 30 | ||
31 | async function processFollow (byActor: SignatureActorModel, targetActorURL: string) { | 31 | async function processFollow (byActor: MActorSignature, targetActorURL: string) { |
32 | const { actorFollow, created, isFollowingInstance } = await sequelizeTypescript.transaction(async t => { | 32 | const { actorFollow, created, isFollowingInstance, targetActor } = await sequelizeTypescript.transaction(async t => { |
33 | const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t) | 33 | const targetActor = await ActorModel.loadByUrlAndPopulateAccountAndChannel(targetActorURL, t) |
34 | 34 | ||
35 | if (!targetActor) throw new Error('Unknown actor') | 35 | if (!targetActor) throw new Error('Unknown actor') |
@@ -43,10 +43,10 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri | |||
43 | 43 | ||
44 | await sendReject(byActor, targetActor) | 44 | await sendReject(byActor, targetActor) |
45 | 45 | ||
46 | return { actorFollow: undefined } | 46 | return { actorFollow: undefined as MActorFollowActors } |
47 | } | 47 | } |
48 | 48 | ||
49 | const [ actorFollow, created ] = await ActorFollowModel.findOrCreate({ | 49 | const [ actorFollow, created ] = await ActorFollowModel.findOrCreate<MActorFollowActors>({ |
50 | where: { | 50 | where: { |
51 | actorId: byActor.id, | 51 | actorId: byActor.id, |
52 | targetActorId: targetActor.id | 52 | targetActorId: targetActor.id |
@@ -57,7 +57,7 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri | |||
57 | state: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL ? 'pending' : 'accepted' | 57 | state: CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL ? 'pending' : 'accepted' |
58 | }, | 58 | }, |
59 | transaction: t | 59 | transaction: t |
60 | }) as [ ActorFollowModelLight, boolean ] | 60 | }) |
61 | 61 | ||
62 | if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) { | 62 | if (actorFollow.state !== 'accepted' && CONFIG.FOLLOWERS.INSTANCE.MANUAL_APPROVAL === false) { |
63 | actorFollow.state = 'accepted' | 63 | actorFollow.state = 'accepted' |
@@ -68,17 +68,26 @@ async function processFollow (byActor: SignatureActorModel, targetActorURL: stri | |||
68 | actorFollow.ActorFollowing = targetActor | 68 | actorFollow.ActorFollowing = targetActor |
69 | 69 | ||
70 | // Target sends to actor he accepted the follow request | 70 | // Target sends to actor he accepted the follow request |
71 | if (actorFollow.state === 'accepted') await sendAccept(actorFollow) | 71 | if (actorFollow.state === 'accepted') { |
72 | await sendAccept(actorFollow) | ||
73 | await autoFollowBackIfNeeded(actorFollow) | ||
74 | } | ||
72 | 75 | ||
73 | return { actorFollow, created, isFollowingInstance } | 76 | return { actorFollow, created, isFollowingInstance, targetActor } |
74 | }) | 77 | }) |
75 | 78 | ||
76 | // Rejected | 79 | // Rejected |
77 | if (!actorFollow) return | 80 | if (!actorFollow) return |
78 | 81 | ||
79 | if (created) { | 82 | if (created) { |
80 | if (isFollowingInstance) Notifier.Instance.notifyOfNewInstanceFollow(actorFollow) | 83 | const follower = await ActorModel.loadFull(byActor.id) |
81 | else Notifier.Instance.notifyOfNewUserFollow(actorFollow) | 84 | const actorFollowFull = Object.assign(actorFollow, { ActorFollowing: targetActor, ActorFollower: follower }) |
85 | |||
86 | if (isFollowingInstance) { | ||
87 | Notifier.Instance.notifyOfNewInstanceFollow(actorFollowFull) | ||
88 | } else { | ||
89 | Notifier.Instance.notifyOfNewUserFollow(actorFollowFull) | ||
90 | } | ||
82 | } | 91 | } |
83 | 92 | ||
84 | logger.info('Actor %s is followed by actor %s.', targetActorURL, byActor.url) | 93 | logger.info('Actor %s is followed by actor %s.', targetActorURL, byActor.url) |
diff --git a/server/lib/activitypub/process/process-like.ts b/server/lib/activitypub/process/process-like.ts index cf559af72..62be0de42 100644 --- a/server/lib/activitypub/process/process-like.ts +++ b/server/lib/activitypub/process/process-like.ts | |||
@@ -7,7 +7,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos' | |||
7 | import { getVideoLikeActivityPubUrl } from '../url' | 7 | import { getVideoLikeActivityPubUrl } from '../url' |
8 | import { getAPId } from '../../../helpers/activitypub' | 8 | import { getAPId } from '../../../helpers/activitypub' |
9 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 9 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
10 | import { SignatureActorModel } from '../../../typings/models' | 10 | import { MActorSignature } from '../../../typings/models' |
11 | 11 | ||
12 | async function processLikeActivity (options: APProcessorOptions<ActivityLike>) { | 12 | async function processLikeActivity (options: APProcessorOptions<ActivityLike>) { |
13 | const { activity, byActor } = options | 13 | const { activity, byActor } = options |
@@ -22,7 +22,7 @@ export { | |||
22 | 22 | ||
23 | // --------------------------------------------------------------------------- | 23 | // --------------------------------------------------------------------------- |
24 | 24 | ||
25 | async function processLikeVideo (byActor: SignatureActorModel, activity: ActivityLike) { | 25 | async function processLikeVideo (byActor: MActorSignature, activity: ActivityLike) { |
26 | const videoUrl = getAPId(activity.object) | 26 | const videoUrl = getAPId(activity.object) |
27 | 27 | ||
28 | const byAccount = byActor.Account | 28 | const byAccount = byActor.Account |
diff --git a/server/lib/activitypub/process/process-reject.ts b/server/lib/activitypub/process/process-reject.ts index 22e311ceb..00e9afa10 100644 --- a/server/lib/activitypub/process/process-reject.ts +++ b/server/lib/activitypub/process/process-reject.ts | |||
@@ -2,7 +2,7 @@ import { ActivityReject } from '../../../../shared/models/activitypub/activity' | |||
2 | import { sequelizeTypescript } from '../../../initializers' | 2 | import { sequelizeTypescript } from '../../../initializers' |
3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 3 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
4 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 4 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
5 | import { ActorModelOnly } from '../../../typings/models' | 5 | import { MActor } from '../../../typings/models' |
6 | 6 | ||
7 | async function processRejectActivity (options: APProcessorOptions<ActivityReject>) { | 7 | async function processRejectActivity (options: APProcessorOptions<ActivityReject>) { |
8 | const { byActor: targetActor, inboxActor } = options | 8 | const { byActor: targetActor, inboxActor } = options |
@@ -19,7 +19,7 @@ export { | |||
19 | 19 | ||
20 | // --------------------------------------------------------------------------- | 20 | // --------------------------------------------------------------------------- |
21 | 21 | ||
22 | async function processReject (follower: ActorModelOnly, targetActor: ActorModelOnly) { | 22 | async function processReject (follower: MActor, targetActor: MActor) { |
23 | return sequelizeTypescript.transaction(async t => { | 23 | return sequelizeTypescript.transaction(async t => { |
24 | const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, targetActor.id, t) | 24 | const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, targetActor.id, t) |
25 | 25 | ||
diff --git a/server/lib/activitypub/process/process-undo.ts b/server/lib/activitypub/process/process-undo.ts index c37ee38bb..10643b2e9 100644 --- a/server/lib/activitypub/process/process-undo.ts +++ b/server/lib/activitypub/process/process-undo.ts | |||
@@ -11,7 +11,7 @@ import { getOrCreateVideoAndAccountAndChannel } from '../videos' | |||
11 | import { VideoShareModel } from '../../../models/video/video-share' | 11 | import { VideoShareModel } from '../../../models/video/video-share' |
12 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | 12 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' |
13 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 13 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
14 | import { SignatureActorModel } from '../../../typings/models' | 14 | import { MActorSignature } from '../../../typings/models' |
15 | 15 | ||
16 | async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) { | 16 | async function processUndoActivity (options: APProcessorOptions<ActivityUndo>) { |
17 | const { activity, byActor } = options | 17 | const { activity, byActor } = options |
@@ -54,7 +54,7 @@ export { | |||
54 | 54 | ||
55 | // --------------------------------------------------------------------------- | 55 | // --------------------------------------------------------------------------- |
56 | 56 | ||
57 | async function processUndoLike (byActor: SignatureActorModel, activity: ActivityUndo) { | 57 | async function processUndoLike (byActor: MActorSignature, activity: ActivityUndo) { |
58 | const likeActivity = activity.object as ActivityLike | 58 | const likeActivity = activity.object as ActivityLike |
59 | 59 | ||
60 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: likeActivity.object }) | 60 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: likeActivity.object }) |
@@ -77,7 +77,7 @@ async function processUndoLike (byActor: SignatureActorModel, activity: Activity | |||
77 | }) | 77 | }) |
78 | } | 78 | } |
79 | 79 | ||
80 | async function processUndoDislike (byActor: SignatureActorModel, activity: ActivityUndo) { | 80 | async function processUndoDislike (byActor: MActorSignature, activity: ActivityUndo) { |
81 | const dislike = activity.object.type === 'Dislike' | 81 | const dislike = activity.object.type === 'Dislike' |
82 | ? activity.object | 82 | ? activity.object |
83 | : activity.object.object as DislikeObject | 83 | : activity.object.object as DislikeObject |
@@ -102,7 +102,7 @@ async function processUndoDislike (byActor: SignatureActorModel, activity: Activ | |||
102 | }) | 102 | }) |
103 | } | 103 | } |
104 | 104 | ||
105 | async function processUndoCacheFile (byActor: SignatureActorModel, activity: ActivityUndo) { | 105 | async function processUndoCacheFile (byActor: MActorSignature, activity: ActivityUndo) { |
106 | const cacheFileObject = activity.object.object as CacheFileObject | 106 | const cacheFileObject = activity.object.object as CacheFileObject |
107 | 107 | ||
108 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFileObject.object }) | 108 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: cacheFileObject.object }) |
@@ -127,7 +127,7 @@ async function processUndoCacheFile (byActor: SignatureActorModel, activity: Act | |||
127 | }) | 127 | }) |
128 | } | 128 | } |
129 | 129 | ||
130 | function processUndoFollow (follower: SignatureActorModel, followActivity: ActivityFollow) { | 130 | function processUndoFollow (follower: MActorSignature, followActivity: ActivityFollow) { |
131 | return sequelizeTypescript.transaction(async t => { | 131 | return sequelizeTypescript.transaction(async t => { |
132 | const following = await ActorModel.loadByUrlAndPopulateAccountAndChannel(followActivity.object, t) | 132 | const following = await ActorModel.loadByUrlAndPopulateAccountAndChannel(followActivity.object, t) |
133 | const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t) | 133 | const actorFollow = await ActorFollowModel.loadByActorAndTarget(follower.id, following.id, t) |
@@ -140,7 +140,7 @@ function processUndoFollow (follower: SignatureActorModel, followActivity: Activ | |||
140 | }) | 140 | }) |
141 | } | 141 | } |
142 | 142 | ||
143 | function processUndoAnnounce (byActor: SignatureActorModel, announceActivity: ActivityAnnounce) { | 143 | function processUndoAnnounce (byActor: MActorSignature, announceActivity: ActivityAnnounce) { |
144 | return sequelizeTypescript.transaction(async t => { | 144 | return sequelizeTypescript.transaction(async t => { |
145 | const share = await VideoShareModel.loadByUrl(announceActivity.id, t) | 145 | const share = await VideoShareModel.loadByUrl(announceActivity.id, t) |
146 | if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`) | 146 | if (!share) throw new Error(`Unknown video share ${announceActivity.id}.`) |
diff --git a/server/lib/activitypub/process/process-update.ts b/server/lib/activitypub/process/process-update.ts index 414f9e375..a47d605d8 100644 --- a/server/lib/activitypub/process/process-update.ts +++ b/server/lib/activitypub/process/process-update.ts | |||
@@ -15,7 +15,7 @@ import { forwardVideoRelatedActivity } from '../send/utils' | |||
15 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' | 15 | import { PlaylistObject } from '../../../../shared/models/activitypub/objects/playlist-object' |
16 | import { createOrUpdateVideoPlaylist } from '../playlist' | 16 | import { createOrUpdateVideoPlaylist } from '../playlist' |
17 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 17 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
18 | import { SignatureActorModel } from '../../../typings/models' | 18 | import { MActorSignature, MAccountIdActor } from '../../../typings/models' |
19 | 19 | ||
20 | async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { | 20 | async function processUpdateActivity (options: APProcessorOptions<ActivityUpdate>) { |
21 | const { activity, byActor } = options | 21 | const { activity, byActor } = options |
@@ -53,7 +53,7 @@ export { | |||
53 | 53 | ||
54 | // --------------------------------------------------------------------------- | 54 | // --------------------------------------------------------------------------- |
55 | 55 | ||
56 | async function processUpdateVideo (actor: SignatureActorModel, activity: ActivityUpdate) { | 56 | async function processUpdateVideo (actor: MActorSignature, activity: ActivityUpdate) { |
57 | const videoObject = activity.object as VideoTorrentObject | 57 | const videoObject = activity.object as VideoTorrentObject |
58 | 58 | ||
59 | if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) { | 59 | if (sanitizeAndCheckVideoTorrentObject(videoObject) === false) { |
@@ -61,20 +61,23 @@ async function processUpdateVideo (actor: SignatureActorModel, activity: Activit | |||
61 | return undefined | 61 | return undefined |
62 | } | 62 | } |
63 | 63 | ||
64 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id, allowRefresh: false }) | 64 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: videoObject.id, allowRefresh: false, fetchType: 'all' }) |
65 | const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) | 65 | const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) |
66 | 66 | ||
67 | const account = actor.Account as MAccountIdActor | ||
68 | account.Actor = actor | ||
69 | |||
67 | const updateOptions = { | 70 | const updateOptions = { |
68 | video, | 71 | video, |
69 | videoObject, | 72 | videoObject, |
70 | account: actor.Account, | 73 | account, |
71 | channel: channelActor.VideoChannel, | 74 | channel: channelActor.VideoChannel, |
72 | overrideTo: activity.to | 75 | overrideTo: activity.to |
73 | } | 76 | } |
74 | return updateVideoFromAP(updateOptions) | 77 | return updateVideoFromAP(updateOptions) |
75 | } | 78 | } |
76 | 79 | ||
77 | async function processUpdateCacheFile (byActor: SignatureActorModel, activity: ActivityUpdate) { | 80 | async function processUpdateCacheFile (byActor: MActorSignature, activity: ActivityUpdate) { |
78 | const cacheFileObject = activity.object as CacheFileObject | 81 | const cacheFileObject = activity.object as CacheFileObject |
79 | 82 | ||
80 | if (!isCacheFileObjectValid(cacheFileObject)) { | 83 | if (!isCacheFileObjectValid(cacheFileObject)) { |
@@ -150,7 +153,7 @@ async function processUpdateActor (actor: ActorModel, activity: ActivityUpdate) | |||
150 | } | 153 | } |
151 | } | 154 | } |
152 | 155 | ||
153 | async function processUpdatePlaylist (byActor: SignatureActorModel, activity: ActivityUpdate) { | 156 | async function processUpdatePlaylist (byActor: MActorSignature, activity: ActivityUpdate) { |
154 | const playlistObject = activity.object as PlaylistObject | 157 | const playlistObject = activity.object as PlaylistObject |
155 | const byAccount = byActor.Account | 158 | const byAccount = byActor.Account |
156 | 159 | ||
diff --git a/server/lib/activitypub/process/process-view.ts b/server/lib/activitypub/process/process-view.ts index e4997b828..df29ee968 100644 --- a/server/lib/activitypub/process/process-view.ts +++ b/server/lib/activitypub/process/process-view.ts | |||
@@ -3,7 +3,7 @@ import { forwardVideoRelatedActivity } from '../send/utils' | |||
3 | import { Redis } from '../../redis' | 3 | import { Redis } from '../../redis' |
4 | import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub' | 4 | import { ActivityCreate, ActivityView, ViewObject } from '../../../../shared/models/activitypub' |
5 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 5 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
6 | import { SignatureActorModel } from '../../../typings/models' | 6 | import { MActorSignature } from '../../../typings/models' |
7 | 7 | ||
8 | async function processViewActivity (options: APProcessorOptions<ActivityCreate | ActivityView>) { | 8 | async function processViewActivity (options: APProcessorOptions<ActivityCreate | ActivityView>) { |
9 | const { activity, byActor } = options | 9 | const { activity, byActor } = options |
@@ -18,11 +18,11 @@ export { | |||
18 | 18 | ||
19 | // --------------------------------------------------------------------------- | 19 | // --------------------------------------------------------------------------- |
20 | 20 | ||
21 | async function processCreateView (activity: ActivityView | ActivityCreate, byActor: SignatureActorModel) { | 21 | async function processCreateView (activity: ActivityView | ActivityCreate, byActor: MActorSignature) { |
22 | const videoObject = activity.type === 'View' ? activity.object : (activity.object as ViewObject).object | 22 | const videoObject = activity.type === 'View' ? activity.object : (activity.object as ViewObject).object |
23 | 23 | ||
24 | const options = { | 24 | const options = { |
25 | videoObject: videoObject, | 25 | videoObject, |
26 | fetchType: 'only-video' as 'only-video' | 26 | fetchType: 'only-video' as 'only-video' |
27 | } | 27 | } |
28 | const { video } = await getOrCreateVideoAndAccountAndChannel(options) | 28 | const { video } = await getOrCreateVideoAndAccountAndChannel(options) |
diff --git a/server/lib/activitypub/process/process.ts b/server/lib/activitypub/process/process.ts index d108fe321..c602bf218 100644 --- a/server/lib/activitypub/process/process.ts +++ b/server/lib/activitypub/process/process.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import { Activity, ActivityType } from '../../../../shared/models/activitypub' | 1 | import { Activity, ActivityType } from '../../../../shared/models/activitypub' |
2 | import { checkUrlsSameHost, getAPId } from '../../../helpers/activitypub' | 2 | import { checkUrlsSameHost, getAPId } from '../../../helpers/activitypub' |
3 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
4 | import { ActorModel } from '../../../models/activitypub/actor' | ||
5 | import { processAcceptActivity } from './process-accept' | 4 | import { processAcceptActivity } from './process-accept' |
6 | import { processAnnounceActivity } from './process-announce' | 5 | import { processAnnounceActivity } from './process-announce' |
7 | import { processCreateActivity } from './process-create' | 6 | import { processCreateActivity } from './process-create' |
@@ -16,7 +15,7 @@ import { processDislikeActivity } from './process-dislike' | |||
16 | import { processFlagActivity } from './process-flag' | 15 | import { processFlagActivity } from './process-flag' |
17 | import { processViewActivity } from './process-view' | 16 | import { processViewActivity } from './process-view' |
18 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' | 17 | import { APProcessorOptions } from '../../../typings/activitypub-processor.model' |
19 | import { SignatureActorModel } from '../../../typings/models' | 18 | import { MActorDefault, MActorSignature } from '../../../typings/models' |
20 | 19 | ||
21 | const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Activity>) => Promise<any> } = { | 20 | const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Activity>) => Promise<any> } = { |
22 | Create: processCreateActivity, | 21 | Create: processCreateActivity, |
@@ -36,15 +35,15 @@ const processActivity: { [ P in ActivityType ]: (options: APProcessorOptions<Act | |||
36 | async function processActivities ( | 35 | async function processActivities ( |
37 | activities: Activity[], | 36 | activities: Activity[], |
38 | options: { | 37 | options: { |
39 | signatureActor?: SignatureActorModel | 38 | signatureActor?: MActorSignature |
40 | inboxActor?: ActorModel | 39 | inboxActor?: MActorDefault |
41 | outboxUrl?: string | 40 | outboxUrl?: string |
42 | fromFetch?: boolean | 41 | fromFetch?: boolean |
43 | } = {} | 42 | } = {} |
44 | ) { | 43 | ) { |
45 | const { outboxUrl, signatureActor, inboxActor, fromFetch = false } = options | 44 | const { outboxUrl, signatureActor, inboxActor, fromFetch = false } = options |
46 | 45 | ||
47 | const actorsCache: { [ url: string ]: SignatureActorModel } = {} | 46 | const actorsCache: { [ url: string ]: MActorSignature } = {} |
48 | 47 | ||
49 | for (const activity of activities) { | 48 | for (const activity of activities) { |
50 | if (!signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) { | 49 | if (!signatureActor && [ 'Create', 'Announce', 'Like' ].includes(activity.type) === false) { |
@@ -75,7 +74,7 @@ async function processActivities ( | |||
75 | } | 74 | } |
76 | 75 | ||
77 | try { | 76 | try { |
78 | await activityProcessor({ activity, byActor, inboxActor: inboxActor, fromFetch }) | 77 | await activityProcessor({ activity, byActor, inboxActor, fromFetch }) |
79 | } catch (err) { | 78 | } catch (err) { |
80 | logger.warn('Cannot process activity %s.', activity.type, { err }) | 79 | logger.warn('Cannot process activity %s.', activity.type, { err }) |
81 | } | 80 | } |
diff --git a/server/lib/activitypub/send/send-accept.ts b/server/lib/activitypub/send/send-accept.ts index 813c42e15..9f0225b64 100644 --- a/server/lib/activitypub/send/send-accept.ts +++ b/server/lib/activitypub/send/send-accept.ts | |||
@@ -3,10 +3,9 @@ import { getActorFollowAcceptActivityPubUrl, getActorFollowActivityPubUrl } from | |||
3 | import { unicastTo } from './utils' | 3 | import { unicastTo } from './utils' |
4 | import { buildFollowActivity } from './send-follow' | 4 | import { buildFollowActivity } from './send-follow' |
5 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
6 | import { ActorFollowModelLight } from '../../../typings/models/actor-follow' | 6 | import { MActor, MActorFollowActors } from '../../../typings/models' |
7 | import { ActorModelOnly } from '../../../typings/models' | ||
8 | 7 | ||
9 | async function sendAccept (actorFollow: ActorFollowModelLight) { | 8 | async function sendAccept (actorFollow: MActorFollowActors) { |
10 | const follower = actorFollow.ActorFollower | 9 | const follower = actorFollow.ActorFollower |
11 | const me = actorFollow.ActorFollowing | 10 | const me = actorFollow.ActorFollowing |
12 | 11 | ||
@@ -34,7 +33,7 @@ export { | |||
34 | 33 | ||
35 | // --------------------------------------------------------------------------- | 34 | // --------------------------------------------------------------------------- |
36 | 35 | ||
37 | function buildAcceptActivity (url: string, byActor: ActorModelOnly, followActivityData: ActivityFollow): ActivityAccept { | 36 | function buildAcceptActivity (url: string, byActor: MActor, followActivityData: ActivityFollow): ActivityAccept { |
38 | return { | 37 | return { |
39 | type: 'Accept', | 38 | type: 'Accept', |
40 | id: url, | 39 | id: url, |
diff --git a/server/lib/activitypub/send/send-announce.ts b/server/lib/activitypub/send/send-announce.ts index 7fe4ca180..a0f33852c 100644 --- a/server/lib/activitypub/send/send-announce.ts +++ b/server/lib/activitypub/send/send-announce.ts | |||
@@ -1,16 +1,15 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAnnounce, ActivityAudience } from '../../../../shared/models/activitypub' | 2 | import { ActivityAnnounce, ActivityAudience } from '../../../../shared/models/activitypub' |
3 | import { VideoModel } from '../../../models/video/video' | ||
4 | import { broadcastToFollowers } from './utils' | 3 | import { broadcastToFollowers } from './utils' |
5 | import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf } from '../audience' | 4 | import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf } from '../audience' |
6 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
7 | import { ActorModelOnly } from '../../../typings/models' | 6 | import { MActorLight, MVideo } from '../../../typings/models' |
8 | import { VideoShareModelOnly } from '../../../typings/models/video-share' | 7 | import { MVideoShare } from '../../../typings/models/video' |
9 | 8 | ||
10 | async function buildAnnounceWithVideoAudience ( | 9 | async function buildAnnounceWithVideoAudience ( |
11 | byActor: ActorModelOnly, | 10 | byActor: MActorLight, |
12 | videoShare: VideoShareModelOnly, | 11 | videoShare: MVideoShare, |
13 | video: VideoModel, | 12 | video: MVideo, |
14 | t: Transaction | 13 | t: Transaction |
15 | ) { | 14 | ) { |
16 | const announcedObject = video.url | 15 | const announcedObject = video.url |
@@ -23,7 +22,7 @@ async function buildAnnounceWithVideoAudience ( | |||
23 | return { activity, actorsInvolvedInVideo } | 22 | return { activity, actorsInvolvedInVideo } |
24 | } | 23 | } |
25 | 24 | ||
26 | async function sendVideoAnnounce (byActor: ActorModelOnly, videoShare: VideoShareModelOnly, video: VideoModel, t: Transaction) { | 25 | async function sendVideoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) { |
27 | const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t) | 26 | const { activity, actorsInvolvedInVideo } = await buildAnnounceWithVideoAudience(byActor, videoShare, video, t) |
28 | 27 | ||
29 | logger.info('Creating job to send announce %s.', videoShare.url) | 28 | logger.info('Creating job to send announce %s.', videoShare.url) |
@@ -32,7 +31,7 @@ async function sendVideoAnnounce (byActor: ActorModelOnly, videoShare: VideoShar | |||
32 | return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException) | 31 | return broadcastToFollowers(activity, byActor, actorsInvolvedInVideo, t, followersException) |
33 | } | 32 | } |
34 | 33 | ||
35 | function buildAnnounceActivity (url: string, byActor: ActorModelOnly, object: string, audience?: ActivityAudience): ActivityAnnounce { | 34 | function buildAnnounceActivity (url: string, byActor: MActorLight, object: string, audience?: ActivityAudience): ActivityAnnounce { |
36 | if (!audience) audience = getAudience(byActor) | 35 | if (!audience) audience = getAudience(byActor) |
37 | 36 | ||
38 | return audiencify({ | 37 | return audiencify({ |
diff --git a/server/lib/activitypub/send/send-create.ts b/server/lib/activitypub/send/send-create.ts index 9c21149f2..26ec3e948 100644 --- a/server/lib/activitypub/send/send-create.ts +++ b/server/lib/activitypub/send/send-create.ts | |||
@@ -1,19 +1,23 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' | 2 | import { ActivityAudience, ActivityCreate } from '../../../../shared/models/activitypub' |
3 | import { VideoPrivacy } from '../../../../shared/models/videos' | 3 | import { VideoPrivacy } from '../../../../shared/models/videos' |
4 | import { ActorModel } from '../../../models/activitypub/actor' | ||
5 | import { VideoModel } from '../../../models/video/video' | ||
6 | import { VideoCommentModel } from '../../../models/video/video-comment' | 4 | import { VideoCommentModel } from '../../../models/video/video-comment' |
7 | import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' | 5 | import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' |
8 | import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience' | 6 | import { audiencify, getActorsInvolvedInVideo, getAudience, getAudienceFromFollowersOf, getVideoCommentAudience } from '../audience' |
9 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
10 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | ||
11 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
12 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | 8 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' |
13 | import { getServerActor } from '../../../helpers/utils' | 9 | import { getServerActor } from '../../../helpers/utils' |
14 | import * as Bluebird from 'bluebird' | 10 | import { |
15 | 11 | MActorLight, | |
16 | async function sendCreateVideo (video: VideoModel, t: Transaction) { | 12 | MCommentOwnerVideo, |
13 | MVideoAccountLight, | ||
14 | MVideoAP, | ||
15 | MVideoPlaylistFull, | ||
16 | MVideoRedundancyFileVideo, | ||
17 | MVideoRedundancyStreamingPlaylistVideo | ||
18 | } from '../../../typings/models' | ||
19 | |||
20 | async function sendCreateVideo (video: MVideoAP, t: Transaction) { | ||
17 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined | 21 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined |
18 | 22 | ||
19 | logger.info('Creating job to send video creation of %s.', video.url) | 23 | logger.info('Creating job to send video creation of %s.', video.url) |
@@ -27,7 +31,11 @@ async function sendCreateVideo (video: VideoModel, t: Transaction) { | |||
27 | return broadcastToFollowers(createActivity, byActor, [ byActor ], t) | 31 | return broadcastToFollowers(createActivity, byActor, [ byActor ], t) |
28 | } | 32 | } |
29 | 33 | ||
30 | async function sendCreateCacheFile (byActor: ActorModel, video: VideoModel, fileRedundancy: VideoRedundancyModel) { | 34 | async function sendCreateCacheFile ( |
35 | byActor: MActorLight, | ||
36 | video: MVideoAccountLight, | ||
37 | fileRedundancy: MVideoRedundancyStreamingPlaylistVideo | MVideoRedundancyFileVideo | ||
38 | ) { | ||
31 | logger.info('Creating job to send file cache of %s.', fileRedundancy.url) | 39 | logger.info('Creating job to send file cache of %s.', fileRedundancy.url) |
32 | 40 | ||
33 | return sendVideoRelatedCreateActivity({ | 41 | return sendVideoRelatedCreateActivity({ |
@@ -38,7 +46,7 @@ async function sendCreateCacheFile (byActor: ActorModel, video: VideoModel, file | |||
38 | }) | 46 | }) |
39 | } | 47 | } |
40 | 48 | ||
41 | async function sendCreateVideoPlaylist (playlist: VideoPlaylistModel, t: Transaction) { | 49 | async function sendCreateVideoPlaylist (playlist: MVideoPlaylistFull, t: Transaction) { |
42 | if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined | 50 | if (playlist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined |
43 | 51 | ||
44 | logger.info('Creating job to send create video playlist of %s.', playlist.url) | 52 | logger.info('Creating job to send create video playlist of %s.', playlist.url) |
@@ -57,7 +65,7 @@ async function sendCreateVideoPlaylist (playlist: VideoPlaylistModel, t: Transac | |||
57 | return broadcastToFollowers(createActivity, byActor, toFollowersOf, t) | 65 | return broadcastToFollowers(createActivity, byActor, toFollowersOf, t) |
58 | } | 66 | } |
59 | 67 | ||
60 | async function sendCreateVideoComment (comment: VideoCommentModel, t: Transaction) { | 68 | async function sendCreateVideoComment (comment: MCommentOwnerVideo, t: Transaction) { |
61 | logger.info('Creating job to send comment %s.', comment.url) | 69 | logger.info('Creating job to send comment %s.', comment.url) |
62 | 70 | ||
63 | const isOrigin = comment.Video.isOwned() | 71 | const isOrigin = comment.Video.isOwned() |
@@ -95,7 +103,7 @@ async function sendCreateVideoComment (comment: VideoCommentModel, t: Transactio | |||
95 | t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl)) | 103 | t.afterCommit(() => unicastTo(createActivity, byActor, comment.Video.VideoChannel.Account.Actor.sharedInboxUrl)) |
96 | } | 104 | } |
97 | 105 | ||
98 | function buildCreateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityCreate { | 106 | function buildCreateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityCreate { |
99 | if (!audience) audience = getAudience(byActor) | 107 | if (!audience) audience = getAudience(byActor) |
100 | 108 | ||
101 | return audiencify( | 109 | return audiencify( |
@@ -122,8 +130,8 @@ export { | |||
122 | // --------------------------------------------------------------------------- | 130 | // --------------------------------------------------------------------------- |
123 | 131 | ||
124 | async function sendVideoRelatedCreateActivity (options: { | 132 | async function sendVideoRelatedCreateActivity (options: { |
125 | byActor: ActorModel, | 133 | byActor: MActorLight, |
126 | video: VideoModel, | 134 | video: MVideoAccountLight, |
127 | url: string, | 135 | url: string, |
128 | object: any, | 136 | object: any, |
129 | transaction?: Transaction | 137 | transaction?: Transaction |
diff --git a/server/lib/activitypub/send/send-delete.ts b/server/lib/activitypub/send/send-delete.ts index 6c7fb8449..4b1ff8dc5 100644 --- a/server/lib/activitypub/send/send-delete.ts +++ b/server/lib/activitypub/send/send-delete.ts | |||
@@ -1,17 +1,17 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub' | 2 | import { ActivityAudience, ActivityDelete } from '../../../../shared/models/activitypub' |
3 | import { ActorModel } from '../../../models/activitypub/actor' | 3 | import { ActorModel } from '../../../models/activitypub/actor' |
4 | import { VideoModel } from '../../../models/video/video' | ||
5 | import { VideoCommentModel } from '../../../models/video/video-comment' | 4 | import { VideoCommentModel } from '../../../models/video/video-comment' |
6 | import { VideoShareModel } from '../../../models/video/video-share' | 5 | import { VideoShareModel } from '../../../models/video/video-share' |
7 | import { getDeleteActivityPubUrl } from '../url' | 6 | import { getDeleteActivityPubUrl } from '../url' |
8 | import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' | 7 | import { broadcastToActors, broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' |
9 | import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience' | 8 | import { audiencify, getActorsInvolvedInVideo, getVideoCommentAudience } from '../audience' |
10 | import { logger } from '../../../helpers/logger' | 9 | import { logger } from '../../../helpers/logger' |
11 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
12 | import { getServerActor } from '../../../helpers/utils' | 10 | import { getServerActor } from '../../../helpers/utils' |
11 | import { MCommentOwnerVideoReply, MVideoAccountLight, MVideoPlaylistFullSummary } from '../../../typings/models/video' | ||
12 | import { MActorUrl } from '../../../typings/models' | ||
13 | 13 | ||
14 | async function sendDeleteVideo (video: VideoModel, transaction: Transaction) { | 14 | async function sendDeleteVideo (video: MVideoAccountLight, transaction: Transaction) { |
15 | logger.info('Creating job to broadcast delete of video %s.', video.url) | 15 | logger.info('Creating job to broadcast delete of video %s.', video.url) |
16 | 16 | ||
17 | const byActor = video.VideoChannel.Account.Actor | 17 | const byActor = video.VideoChannel.Account.Actor |
@@ -42,7 +42,7 @@ async function sendDeleteActor (byActor: ActorModel, t: Transaction) { | |||
42 | return broadcastToFollowers(activity, byActor, actorsInvolved, t) | 42 | return broadcastToFollowers(activity, byActor, actorsInvolved, t) |
43 | } | 43 | } |
44 | 44 | ||
45 | async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Transaction) { | 45 | async function sendDeleteVideoComment (videoComment: MCommentOwnerVideoReply, t: Transaction) { |
46 | logger.info('Creating job to send delete of comment %s.', videoComment.url) | 46 | logger.info('Creating job to send delete of comment %s.', videoComment.url) |
47 | 47 | ||
48 | const isVideoOrigin = videoComment.Video.isOwned() | 48 | const isVideoOrigin = videoComment.Video.isOwned() |
@@ -74,7 +74,7 @@ async function sendDeleteVideoComment (videoComment: VideoCommentModel, t: Trans | |||
74 | t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl)) | 74 | t.afterCommit(() => unicastTo(activity, byActor, videoComment.Video.VideoChannel.Account.Actor.sharedInboxUrl)) |
75 | } | 75 | } |
76 | 76 | ||
77 | async function sendDeleteVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) { | 77 | async function sendDeleteVideoPlaylist (videoPlaylist: MVideoPlaylistFullSummary, t: Transaction) { |
78 | logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url) | 78 | logger.info('Creating job to send delete of playlist %s.', videoPlaylist.url) |
79 | 79 | ||
80 | const byActor = videoPlaylist.OwnerAccount.Actor | 80 | const byActor = videoPlaylist.OwnerAccount.Actor |
@@ -101,7 +101,7 @@ export { | |||
101 | 101 | ||
102 | // --------------------------------------------------------------------------- | 102 | // --------------------------------------------------------------------------- |
103 | 103 | ||
104 | function buildDeleteActivity (url: string, object: string, byActor: ActorModel, audience?: ActivityAudience): ActivityDelete { | 104 | function buildDeleteActivity (url: string, object: string, byActor: MActorUrl, audience?: ActivityAudience): ActivityDelete { |
105 | const activity = { | 105 | const activity = { |
106 | type: 'Delete' as 'Delete', | 106 | type: 'Delete' as 'Delete', |
107 | id: url, | 107 | id: url, |
diff --git a/server/lib/activitypub/send/send-dislike.ts b/server/lib/activitypub/send/send-dislike.ts index a88436f2c..6e41f241f 100644 --- a/server/lib/activitypub/send/send-dislike.ts +++ b/server/lib/activitypub/send/send-dislike.ts | |||
@@ -1,13 +1,12 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActorModel } from '../../../models/activitypub/actor' | ||
3 | import { VideoModel } from '../../../models/video/video' | ||
4 | import { getVideoDislikeActivityPubUrl } from '../url' | 2 | import { getVideoDislikeActivityPubUrl } from '../url' |
5 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
6 | import { ActivityAudience, ActivityDislike } from '../../../../shared/models/activitypub' | 4 | import { ActivityAudience, ActivityDislike } from '../../../../shared/models/activitypub' |
7 | import { sendVideoRelatedActivity } from './utils' | 5 | import { sendVideoRelatedActivity } from './utils' |
8 | import { audiencify, getAudience } from '../audience' | 6 | import { audiencify, getAudience } from '../audience' |
7 | import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' | ||
9 | 8 | ||
10 | async function sendDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { | 9 | async function sendDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { |
11 | logger.info('Creating job to dislike %s.', video.url) | 10 | logger.info('Creating job to dislike %s.', video.url) |
12 | 11 | ||
13 | const activityBuilder = (audience: ActivityAudience) => { | 12 | const activityBuilder = (audience: ActivityAudience) => { |
@@ -19,7 +18,7 @@ async function sendDislike (byActor: ActorModel, video: VideoModel, t: Transacti | |||
19 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) | 18 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) |
20 | } | 19 | } |
21 | 20 | ||
22 | function buildDislikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityDislike { | 21 | function buildDislikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityDislike { |
23 | if (!audience) audience = getAudience(byActor) | 22 | if (!audience) audience = getAudience(byActor) |
24 | 23 | ||
25 | return audiencify( | 24 | return audiencify( |
diff --git a/server/lib/activitypub/send/send-flag.ts b/server/lib/activitypub/send/send-flag.ts index 61ee389a6..5ae1614ab 100644 --- a/server/lib/activitypub/send/send-flag.ts +++ b/server/lib/activitypub/send/send-flag.ts | |||
@@ -1,14 +1,13 @@ | |||
1 | import { ActorModel } from '../../../models/activitypub/actor' | ||
2 | import { VideoModel } from '../../../models/video/video' | ||
3 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
4 | import { getVideoAbuseActivityPubUrl } from '../url' | 1 | import { getVideoAbuseActivityPubUrl } from '../url' |
5 | import { unicastTo } from './utils' | 2 | import { unicastTo } from './utils' |
6 | import { logger } from '../../../helpers/logger' | 3 | import { logger } from '../../../helpers/logger' |
7 | import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub' | 4 | import { ActivityAudience, ActivityFlag } from '../../../../shared/models/activitypub' |
8 | import { audiencify, getAudience } from '../audience' | 5 | import { audiencify, getAudience } from '../audience' |
9 | import { Transaction } from 'sequelize' | 6 | import { Transaction } from 'sequelize' |
7 | import { MActor, MVideoFullLight } from '../../../typings/models' | ||
8 | import { MVideoAbuseVideo } from '../../../typings/models/video' | ||
10 | 9 | ||
11 | async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, video: VideoModel, t: Transaction) { | 10 | async function sendVideoAbuse (byActor: MActor, videoAbuse: MVideoAbuseVideo, video: MVideoFullLight, t: Transaction) { |
12 | if (!video.VideoChannel.Account.Actor.serverId) return // Local user | 11 | if (!video.VideoChannel.Account.Actor.serverId) return // Local user |
13 | 12 | ||
14 | const url = getVideoAbuseActivityPubUrl(videoAbuse) | 13 | const url = getVideoAbuseActivityPubUrl(videoAbuse) |
@@ -22,7 +21,7 @@ async function sendVideoAbuse (byActor: ActorModel, videoAbuse: VideoAbuseModel, | |||
22 | t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)) | 21 | t.afterCommit(() => unicastTo(flagActivity, byActor, video.VideoChannel.Account.Actor.sharedInboxUrl)) |
23 | } | 22 | } |
24 | 23 | ||
25 | function buildFlagActivity (url: string, byActor: ActorModel, videoAbuse: VideoAbuseModel, audience: ActivityAudience): ActivityFlag { | 24 | function buildFlagActivity (url: string, byActor: MActor, videoAbuse: MVideoAbuseVideo, audience: ActivityAudience): ActivityFlag { |
26 | if (!audience) audience = getAudience(byActor) | 25 | if (!audience) audience = getAudience(byActor) |
27 | 26 | ||
28 | const activity = Object.assign( | 27 | const activity = Object.assign( |
diff --git a/server/lib/activitypub/send/send-follow.ts b/server/lib/activitypub/send/send-follow.ts index a59ed50cf..ce400d8ff 100644 --- a/server/lib/activitypub/send/send-follow.ts +++ b/server/lib/activitypub/send/send-follow.ts | |||
@@ -1,12 +1,11 @@ | |||
1 | import { ActivityFollow } from '../../../../shared/models/activitypub' | 1 | import { ActivityFollow } from '../../../../shared/models/activitypub' |
2 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
3 | import { getActorFollowActivityPubUrl } from '../url' | 2 | import { getActorFollowActivityPubUrl } from '../url' |
4 | import { unicastTo } from './utils' | 3 | import { unicastTo } from './utils' |
5 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
6 | import { Transaction } from 'sequelize' | 5 | import { Transaction } from 'sequelize' |
7 | import { ActorModelOnly } from '../../../typings/models' | 6 | import { MActor, MActorFollowActors } from '../../../typings/models' |
8 | 7 | ||
9 | function sendFollow (actorFollow: ActorFollowModel, t: Transaction) { | 8 | function sendFollow (actorFollow: MActorFollowActors, t: Transaction) { |
10 | const me = actorFollow.ActorFollower | 9 | const me = actorFollow.ActorFollower |
11 | const following = actorFollow.ActorFollowing | 10 | const following = actorFollow.ActorFollowing |
12 | 11 | ||
@@ -21,7 +20,7 @@ function sendFollow (actorFollow: ActorFollowModel, t: Transaction) { | |||
21 | t.afterCommit(() => unicastTo(data, me, following.inboxUrl)) | 20 | t.afterCommit(() => unicastTo(data, me, following.inboxUrl)) |
22 | } | 21 | } |
23 | 22 | ||
24 | function buildFollowActivity (url: string, byActor: ActorModelOnly, targetActor: ActorModelOnly): ActivityFollow { | 23 | function buildFollowActivity (url: string, byActor: MActor, targetActor: MActor): ActivityFollow { |
25 | return { | 24 | return { |
26 | type: 'Follow', | 25 | type: 'Follow', |
27 | id: url, | 26 | id: url, |
diff --git a/server/lib/activitypub/send/send-like.ts b/server/lib/activitypub/send/send-like.ts index 35227887a..e84a6f98b 100644 --- a/server/lib/activitypub/send/send-like.ts +++ b/server/lib/activitypub/send/send-like.ts | |||
@@ -1,13 +1,12 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub' | 2 | import { ActivityAudience, ActivityLike } from '../../../../shared/models/activitypub' |
3 | import { ActorModel } from '../../../models/activitypub/actor' | ||
4 | import { VideoModel } from '../../../models/video/video' | ||
5 | import { getVideoLikeActivityPubUrl } from '../url' | 3 | import { getVideoLikeActivityPubUrl } from '../url' |
6 | import { sendVideoRelatedActivity } from './utils' | 4 | import { sendVideoRelatedActivity } from './utils' |
7 | import { audiencify, getAudience } from '../audience' | 5 | import { audiencify, getAudience } from '../audience' |
8 | import { logger } from '../../../helpers/logger' | 6 | import { logger } from '../../../helpers/logger' |
7 | import { MActor, MActorAudience, MVideoAccountLight, MVideoUrl } from '../../../typings/models' | ||
9 | 8 | ||
10 | async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction) { | 9 | async function sendLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { |
11 | logger.info('Creating job to like %s.', video.url) | 10 | logger.info('Creating job to like %s.', video.url) |
12 | 11 | ||
13 | const activityBuilder = (audience: ActivityAudience) => { | 12 | const activityBuilder = (audience: ActivityAudience) => { |
@@ -19,7 +18,7 @@ async function sendLike (byActor: ActorModel, video: VideoModel, t: Transaction) | |||
19 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) | 18 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) |
20 | } | 19 | } |
21 | 20 | ||
22 | function buildLikeActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityLike { | 21 | function buildLikeActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityLike { |
23 | if (!audience) audience = getAudience(byActor) | 22 | if (!audience) audience = getAudience(byActor) |
24 | 23 | ||
25 | return audiencify( | 24 | return audiencify( |
diff --git a/server/lib/activitypub/send/send-reject.ts b/server/lib/activitypub/send/send-reject.ts index 63110b433..4258a3c36 100644 --- a/server/lib/activitypub/send/send-reject.ts +++ b/server/lib/activitypub/send/send-reject.ts | |||
@@ -1,12 +1,11 @@ | |||
1 | import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub' | 1 | import { ActivityFollow, ActivityReject } from '../../../../shared/models/activitypub' |
2 | import { ActorModel } from '../../../models/activitypub/actor' | ||
3 | import { getActorFollowActivityPubUrl, getActorFollowRejectActivityPubUrl } from '../url' | 2 | import { getActorFollowActivityPubUrl, getActorFollowRejectActivityPubUrl } from '../url' |
4 | import { unicastTo } from './utils' | 3 | import { unicastTo } from './utils' |
5 | import { buildFollowActivity } from './send-follow' | 4 | import { buildFollowActivity } from './send-follow' |
6 | import { logger } from '../../../helpers/logger' | 5 | import { logger } from '../../../helpers/logger' |
7 | import { SignatureActorModel } from '../../../typings/models' | 6 | import { MActor } from '../../../typings/models' |
8 | 7 | ||
9 | async function sendReject (follower: SignatureActorModel, following: ActorModel) { | 8 | async function sendReject (follower: MActor, following: MActor) { |
10 | if (!follower.serverId) { // This should never happen | 9 | if (!follower.serverId) { // This should never happen |
11 | logger.warn('Do not sending reject to local follower.') | 10 | logger.warn('Do not sending reject to local follower.') |
12 | return | 11 | return |
@@ -31,7 +30,7 @@ export { | |||
31 | 30 | ||
32 | // --------------------------------------------------------------------------- | 31 | // --------------------------------------------------------------------------- |
33 | 32 | ||
34 | function buildRejectActivity (url: string, byActor: ActorModel, followActivityData: ActivityFollow): ActivityReject { | 33 | function buildRejectActivity (url: string, byActor: MActor, followActivityData: ActivityFollow): ActivityReject { |
35 | return { | 34 | return { |
36 | type: 'Reject', | 35 | type: 'Reject', |
37 | id: url, | 36 | id: url, |
diff --git a/server/lib/activitypub/send/send-undo.ts b/server/lib/activitypub/send/send-undo.ts index 8fcbbac5c..e9ab5b3c5 100644 --- a/server/lib/activitypub/send/send-undo.ts +++ b/server/lib/activitypub/send/send-undo.ts | |||
@@ -2,13 +2,12 @@ import { Transaction } from 'sequelize' | |||
2 | import { | 2 | import { |
3 | ActivityAnnounce, | 3 | ActivityAnnounce, |
4 | ActivityAudience, | 4 | ActivityAudience, |
5 | ActivityCreate, ActivityDislike, | 5 | ActivityCreate, |
6 | ActivityDislike, | ||
6 | ActivityFollow, | 7 | ActivityFollow, |
7 | ActivityLike, | 8 | ActivityLike, |
8 | ActivityUndo | 9 | ActivityUndo |
9 | } from '../../../../shared/models/activitypub' | 10 | } from '../../../../shared/models/activitypub' |
10 | import { ActorModel } from '../../../models/activitypub/actor' | ||
11 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
12 | import { VideoModel } from '../../../models/video/video' | 11 | import { VideoModel } from '../../../models/video/video' |
13 | import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' | 12 | import { getActorFollowActivityPubUrl, getUndoActivityPubUrl, getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from '../url' |
14 | import { broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' | 13 | import { broadcastToFollowers, sendVideoRelatedActivity, unicastTo } from './utils' |
@@ -16,13 +15,20 @@ import { audiencify, getAudience } from '../audience' | |||
16 | import { buildCreateActivity } from './send-create' | 15 | import { buildCreateActivity } from './send-create' |
17 | import { buildFollowActivity } from './send-follow' | 16 | import { buildFollowActivity } from './send-follow' |
18 | import { buildLikeActivity } from './send-like' | 17 | import { buildLikeActivity } from './send-like' |
19 | import { VideoShareModel } from '../../../models/video/video-share' | ||
20 | import { buildAnnounceWithVideoAudience } from './send-announce' | 18 | import { buildAnnounceWithVideoAudience } from './send-announce' |
21 | import { logger } from '../../../helpers/logger' | 19 | import { logger } from '../../../helpers/logger' |
22 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | ||
23 | import { buildDislikeActivity } from './send-dislike' | 20 | import { buildDislikeActivity } from './send-dislike' |
24 | 21 | import { | |
25 | async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) { | 22 | MActor, MActorAudience, |
23 | MActorFollowActors, | ||
24 | MActorLight, | ||
25 | MVideo, | ||
26 | MVideoAccountLight, | ||
27 | MVideoRedundancyVideo, | ||
28 | MVideoShare | ||
29 | } from '../../../typings/models' | ||
30 | |||
31 | async function sendUndoFollow (actorFollow: MActorFollowActors, t: Transaction) { | ||
26 | const me = actorFollow.ActorFollower | 32 | const me = actorFollow.ActorFollower |
27 | const following = actorFollow.ActorFollowing | 33 | const following = actorFollow.ActorFollowing |
28 | 34 | ||
@@ -40,7 +46,7 @@ async function sendUndoFollow (actorFollow: ActorFollowModel, t: Transaction) { | |||
40 | t.afterCommit(() => unicastTo(undoActivity, me, following.inboxUrl)) | 46 | t.afterCommit(() => unicastTo(undoActivity, me, following.inboxUrl)) |
41 | } | 47 | } |
42 | 48 | ||
43 | async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareModel, video: VideoModel, t: Transaction) { | 49 | async function sendUndoAnnounce (byActor: MActorLight, videoShare: MVideoShare, video: MVideo, t: Transaction) { |
44 | logger.info('Creating job to undo announce %s.', videoShare.url) | 50 | logger.info('Creating job to undo announce %s.', videoShare.url) |
45 | 51 | ||
46 | const undoUrl = getUndoActivityPubUrl(videoShare.url) | 52 | const undoUrl = getUndoActivityPubUrl(videoShare.url) |
@@ -52,7 +58,7 @@ async function sendUndoAnnounce (byActor: ActorModel, videoShare: VideoShareMode | |||
52 | return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException) | 58 | return broadcastToFollowers(undoActivity, byActor, actorsInvolvedInVideo, t, followersException) |
53 | } | 59 | } |
54 | 60 | ||
55 | async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transaction) { | 61 | async function sendUndoLike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { |
56 | logger.info('Creating job to undo a like of video %s.', video.url) | 62 | logger.info('Creating job to undo a like of video %s.', video.url) |
57 | 63 | ||
58 | const likeUrl = getVideoLikeActivityPubUrl(byActor, video) | 64 | const likeUrl = getVideoLikeActivityPubUrl(byActor, video) |
@@ -61,7 +67,7 @@ async function sendUndoLike (byActor: ActorModel, video: VideoModel, t: Transact | |||
61 | return sendUndoVideoRelatedActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t }) | 67 | return sendUndoVideoRelatedActivity({ byActor, video, url: likeUrl, activity: likeActivity, transaction: t }) |
62 | } | 68 | } |
63 | 69 | ||
64 | async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Transaction) { | 70 | async function sendUndoDislike (byActor: MActor, video: MVideoAccountLight, t: Transaction) { |
65 | logger.info('Creating job to undo a dislike of video %s.', video.url) | 71 | logger.info('Creating job to undo a dislike of video %s.', video.url) |
66 | 72 | ||
67 | const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video) | 73 | const dislikeUrl = getVideoDislikeActivityPubUrl(byActor, video) |
@@ -70,7 +76,7 @@ async function sendUndoDislike (byActor: ActorModel, video: VideoModel, t: Trans | |||
70 | return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t }) | 76 | return sendUndoVideoRelatedActivity({ byActor, video, url: dislikeUrl, activity: dislikeActivity, transaction: t }) |
71 | } | 77 | } |
72 | 78 | ||
73 | async function sendUndoCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel, t: Transaction) { | 79 | async function sendUndoCacheFile (byActor: MActor, redundancyModel: MVideoRedundancyVideo, t: Transaction) { |
74 | logger.info('Creating job to undo cache file %s.', redundancyModel.url) | 80 | logger.info('Creating job to undo cache file %s.', redundancyModel.url) |
75 | 81 | ||
76 | const videoId = redundancyModel.getVideo().id | 82 | const videoId = redundancyModel.getVideo().id |
@@ -94,7 +100,7 @@ export { | |||
94 | 100 | ||
95 | function undoActivityData ( | 101 | function undoActivityData ( |
96 | url: string, | 102 | url: string, |
97 | byActor: ActorModel, | 103 | byActor: MActorAudience, |
98 | object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, | 104 | object: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, |
99 | audience?: ActivityAudience | 105 | audience?: ActivityAudience |
100 | ): ActivityUndo { | 106 | ): ActivityUndo { |
@@ -112,8 +118,8 @@ function undoActivityData ( | |||
112 | } | 118 | } |
113 | 119 | ||
114 | async function sendUndoVideoRelatedActivity (options: { | 120 | async function sendUndoVideoRelatedActivity (options: { |
115 | byActor: ActorModel, | 121 | byActor: MActor, |
116 | video: VideoModel, | 122 | video: MVideoAccountLight, |
117 | url: string, | 123 | url: string, |
118 | activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, | 124 | activity: ActivityFollow | ActivityLike | ActivityDislike | ActivityCreate | ActivityAnnounce, |
119 | transaction: Transaction | 125 | transaction: Transaction |
diff --git a/server/lib/activitypub/send/send-update.ts b/server/lib/activitypub/send/send-update.ts index 5bf092894..37517c2be 100644 --- a/server/lib/activitypub/send/send-update.ts +++ b/server/lib/activitypub/send/send-update.ts | |||
@@ -2,21 +2,29 @@ import { Transaction } from 'sequelize' | |||
2 | import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub' | 2 | import { ActivityAudience, ActivityUpdate } from '../../../../shared/models/activitypub' |
3 | import { VideoPrivacy } from '../../../../shared/models/videos' | 3 | import { VideoPrivacy } from '../../../../shared/models/videos' |
4 | import { AccountModel } from '../../../models/account/account' | 4 | import { AccountModel } from '../../../models/account/account' |
5 | import { ActorModel } from '../../../models/activitypub/actor' | ||
6 | import { VideoModel } from '../../../models/video/video' | 5 | import { VideoModel } from '../../../models/video/video' |
7 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
8 | import { VideoShareModel } from '../../../models/video/video-share' | 6 | import { VideoShareModel } from '../../../models/video/video-share' |
9 | import { getUpdateActivityPubUrl } from '../url' | 7 | import { getUpdateActivityPubUrl } from '../url' |
10 | import { broadcastToFollowers, sendVideoRelatedActivity } from './utils' | 8 | import { broadcastToFollowers, sendVideoRelatedActivity } from './utils' |
11 | import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience' | 9 | import { audiencify, getActorsInvolvedInVideo, getAudience } from '../audience' |
12 | import { logger } from '../../../helpers/logger' | 10 | import { logger } from '../../../helpers/logger' |
13 | import { VideoCaptionModel } from '../../../models/video/video-caption' | 11 | import { VideoCaptionModel } from '../../../models/video/video-caption' |
14 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | ||
15 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
16 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | 12 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' |
17 | import { getServerActor } from '../../../helpers/utils' | 13 | import { getServerActor } from '../../../helpers/utils' |
14 | import { | ||
15 | MAccountDefault, | ||
16 | MActor, | ||
17 | MActorLight, | ||
18 | MChannelDefault, | ||
19 | MVideoAP, | ||
20 | MVideoAPWithoutCaption, | ||
21 | MVideoPlaylistFull, | ||
22 | MVideoRedundancyVideo | ||
23 | } from '../../../typings/models' | ||
24 | |||
25 | async function sendUpdateVideo (videoArg: MVideoAPWithoutCaption, t: Transaction, overrodeByActor?: MActor) { | ||
26 | const video = videoArg as MVideoAP | ||
18 | 27 | ||
19 | async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByActor?: ActorModel) { | ||
20 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined | 28 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined |
21 | 29 | ||
22 | logger.info('Creating job to update video %s.', video.url) | 30 | logger.info('Creating job to update video %s.', video.url) |
@@ -41,7 +49,7 @@ async function sendUpdateVideo (video: VideoModel, t: Transaction, overrodeByAct | |||
41 | return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) | 49 | return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) |
42 | } | 50 | } |
43 | 51 | ||
44 | async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelModel, t: Transaction) { | 52 | async function sendUpdateActor (accountOrChannel: MChannelDefault | MAccountDefault, t: Transaction) { |
45 | const byActor = accountOrChannel.Actor | 53 | const byActor = accountOrChannel.Actor |
46 | 54 | ||
47 | logger.info('Creating job to update actor %s.', byActor.url) | 55 | logger.info('Creating job to update actor %s.', byActor.url) |
@@ -51,7 +59,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod | |||
51 | const audience = getAudience(byActor) | 59 | const audience = getAudience(byActor) |
52 | const updateActivity = buildUpdateActivity(url, byActor, accountOrChannelObject, audience) | 60 | const updateActivity = buildUpdateActivity(url, byActor, accountOrChannelObject, audience) |
53 | 61 | ||
54 | let actorsInvolved: ActorModel[] | 62 | let actorsInvolved: MActor[] |
55 | if (accountOrChannel instanceof AccountModel) { | 63 | if (accountOrChannel instanceof AccountModel) { |
56 | // Actors that shared my videos are involved too | 64 | // Actors that shared my videos are involved too |
57 | actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t) | 65 | actorsInvolved = await VideoShareModel.loadActorsWhoSharedVideosOf(byActor.id, t) |
@@ -65,7 +73,7 @@ async function sendUpdateActor (accountOrChannel: AccountModel | VideoChannelMod | |||
65 | return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) | 73 | return broadcastToFollowers(updateActivity, byActor, actorsInvolved, t) |
66 | } | 74 | } |
67 | 75 | ||
68 | async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoRedundancyModel) { | 76 | async function sendUpdateCacheFile (byActor: MActorLight, redundancyModel: MVideoRedundancyVideo) { |
69 | logger.info('Creating job to update cache file %s.', redundancyModel.url) | 77 | logger.info('Creating job to update cache file %s.', redundancyModel.url) |
70 | 78 | ||
71 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.getVideo().id) | 79 | const video = await VideoModel.loadAndPopulateAccountAndServerAndTags(redundancyModel.getVideo().id) |
@@ -80,7 +88,7 @@ async function sendUpdateCacheFile (byActor: ActorModel, redundancyModel: VideoR | |||
80 | return sendVideoRelatedActivity(activityBuilder, { byActor, video }) | 88 | return sendVideoRelatedActivity(activityBuilder, { byActor, video }) |
81 | } | 89 | } |
82 | 90 | ||
83 | async function sendUpdateVideoPlaylist (videoPlaylist: VideoPlaylistModel, t: Transaction) { | 91 | async function sendUpdateVideoPlaylist (videoPlaylist: MVideoPlaylistFull, t: Transaction) { |
84 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined | 92 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) return undefined |
85 | 93 | ||
86 | const byActor = videoPlaylist.OwnerAccount.Actor | 94 | const byActor = videoPlaylist.OwnerAccount.Actor |
@@ -113,7 +121,7 @@ export { | |||
113 | 121 | ||
114 | // --------------------------------------------------------------------------- | 122 | // --------------------------------------------------------------------------- |
115 | 123 | ||
116 | function buildUpdateActivity (url: string, byActor: ActorModel, object: any, audience?: ActivityAudience): ActivityUpdate { | 124 | function buildUpdateActivity (url: string, byActor: MActorLight, object: any, audience?: ActivityAudience): ActivityUpdate { |
117 | if (!audience) audience = getAudience(byActor) | 125 | if (!audience) audience = getAudience(byActor) |
118 | 126 | ||
119 | return audiencify( | 127 | return audiencify( |
@@ -121,8 +129,7 @@ function buildUpdateActivity (url: string, byActor: ActorModel, object: any, aud | |||
121 | type: 'Update' as 'Update', | 129 | type: 'Update' as 'Update', |
122 | id: url, | 130 | id: url, |
123 | actor: byActor.url, | 131 | actor: byActor.url, |
124 | object: audiencify(object, audience | 132 | object: audiencify(object, audience) |
125 | ) | ||
126 | }, | 133 | }, |
127 | audience | 134 | audience |
128 | ) | 135 | ) |
diff --git a/server/lib/activitypub/send/send-view.ts b/server/lib/activitypub/send/send-view.ts index 8ad126be0..8809417f9 100644 --- a/server/lib/activitypub/send/send-view.ts +++ b/server/lib/activitypub/send/send-view.ts | |||
@@ -1,13 +1,13 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub' | 2 | import { ActivityAudience, ActivityView } from '../../../../shared/models/activitypub' |
3 | import { ActorModel } from '../../../models/activitypub/actor' | 3 | import { ActorModel } from '../../../models/activitypub/actor' |
4 | import { VideoModel } from '../../../models/video/video' | ||
5 | import { getVideoLikeActivityPubUrl } from '../url' | 4 | import { getVideoLikeActivityPubUrl } from '../url' |
6 | import { sendVideoRelatedActivity } from './utils' | 5 | import { sendVideoRelatedActivity } from './utils' |
7 | import { audiencify, getAudience } from '../audience' | 6 | import { audiencify, getAudience } from '../audience' |
8 | import { logger } from '../../../helpers/logger' | 7 | import { logger } from '../../../helpers/logger' |
8 | import { MActorAudience, MVideoAccountLight, MVideoUrl } from '@server/typings/models' | ||
9 | 9 | ||
10 | async function sendView (byActor: ActorModel, video: VideoModel, t: Transaction) { | 10 | async function sendView (byActor: ActorModel, video: MVideoAccountLight, t: Transaction) { |
11 | logger.info('Creating job to send view of %s.', video.url) | 11 | logger.info('Creating job to send view of %s.', video.url) |
12 | 12 | ||
13 | const activityBuilder = (audience: ActivityAudience) => { | 13 | const activityBuilder = (audience: ActivityAudience) => { |
@@ -19,7 +19,7 @@ async function sendView (byActor: ActorModel, video: VideoModel, t: Transaction) | |||
19 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) | 19 | return sendVideoRelatedActivity(activityBuilder, { byActor, video, transaction: t }) |
20 | } | 20 | } |
21 | 21 | ||
22 | function buildViewActivity (url: string, byActor: ActorModel, video: VideoModel, audience?: ActivityAudience): ActivityView { | 22 | function buildViewActivity (url: string, byActor: MActorAudience, video: MVideoUrl, audience?: ActivityAudience): ActivityView { |
23 | if (!audience) audience = getAudience(byActor) | 23 | if (!audience) audience = getAudience(byActor) |
24 | 24 | ||
25 | return audiencify( | 25 | return audiencify( |
diff --git a/server/lib/activitypub/send/utils.ts b/server/lib/activitypub/send/utils.ts index 4f69afb00..8129ab32a 100644 --- a/server/lib/activitypub/send/utils.ts +++ b/server/lib/activitypub/send/utils.ts | |||
@@ -4,15 +4,14 @@ import { logger } from '../../../helpers/logger' | |||
4 | import { ActorModel } from '../../../models/activitypub/actor' | 4 | import { ActorModel } from '../../../models/activitypub/actor' |
5 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | 5 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' |
6 | import { JobQueue } from '../../job-queue' | 6 | import { JobQueue } from '../../job-queue' |
7 | import { VideoModel } from '../../../models/video/video' | ||
8 | import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' | 7 | import { getActorsInvolvedInVideo, getAudienceFromFollowersOf, getRemoteVideoAudience } from '../audience' |
9 | import { getServerActor } from '../../../helpers/utils' | 8 | import { getServerActor } from '../../../helpers/utils' |
10 | import { afterCommitIfTransaction } from '../../../helpers/database-utils' | 9 | import { afterCommitIfTransaction } from '../../../helpers/database-utils' |
11 | import { ActorFollowerException, ActorModelId, ActorModelOnly } from '../../../typings/models' | 10 | import { MActorFollowerException, MActor, MActorId, MActorLight, MVideo, MVideoAccountLight } from '../../../typings/models' |
12 | 11 | ||
13 | async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { | 12 | async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAudience) => Activity, options: { |
14 | byActor: ActorModelOnly, | 13 | byActor: MActorLight, |
15 | video: VideoModel, | 14 | video: MVideoAccountLight, |
16 | transaction?: Transaction | 15 | transaction?: Transaction |
17 | }) { | 16 | }) { |
18 | const { byActor, video, transaction } = options | 17 | const { byActor, video, transaction } = options |
@@ -41,8 +40,8 @@ async function sendVideoRelatedActivity (activityBuilder: (audience: ActivityAud | |||
41 | async function forwardVideoRelatedActivity ( | 40 | async function forwardVideoRelatedActivity ( |
42 | activity: Activity, | 41 | activity: Activity, |
43 | t: Transaction, | 42 | t: Transaction, |
44 | followersException: ActorFollowerException[] = [], | 43 | followersException: MActorFollowerException[] = [], |
45 | video: VideoModel | 44 | video: MVideo |
46 | ) { | 45 | ) { |
47 | // Mastodon does not add our announces in audience, so we forward to them manually | 46 | // Mastodon does not add our announces in audience, so we forward to them manually |
48 | const additionalActors = await getActorsInvolvedInVideo(video, t) | 47 | const additionalActors = await getActorsInvolvedInVideo(video, t) |
@@ -54,7 +53,7 @@ async function forwardVideoRelatedActivity ( | |||
54 | async function forwardActivity ( | 53 | async function forwardActivity ( |
55 | activity: Activity, | 54 | activity: Activity, |
56 | t: Transaction, | 55 | t: Transaction, |
57 | followersException: ActorFollowerException[] = [], | 56 | followersException: MActorFollowerException[] = [], |
58 | additionalFollowerUrls: string[] = [] | 57 | additionalFollowerUrls: string[] = [] |
59 | ) { | 58 | ) { |
60 | logger.info('Forwarding activity %s.', activity.id) | 59 | logger.info('Forwarding activity %s.', activity.id) |
@@ -88,10 +87,10 @@ async function forwardActivity ( | |||
88 | 87 | ||
89 | async function broadcastToFollowers ( | 88 | async function broadcastToFollowers ( |
90 | data: any, | 89 | data: any, |
91 | byActor: ActorModelId, | 90 | byActor: MActorId, |
92 | toFollowersOf: ActorModelId[], | 91 | toFollowersOf: MActorId[], |
93 | t: Transaction, | 92 | t: Transaction, |
94 | actorsException: ActorFollowerException[] = [] | 93 | actorsException: MActorFollowerException[] = [] |
95 | ) { | 94 | ) { |
96 | const uris = await computeFollowerUris(toFollowersOf, actorsException, t) | 95 | const uris = await computeFollowerUris(toFollowersOf, actorsException, t) |
97 | 96 | ||
@@ -100,16 +99,16 @@ async function broadcastToFollowers ( | |||
100 | 99 | ||
101 | async function broadcastToActors ( | 100 | async function broadcastToActors ( |
102 | data: any, | 101 | data: any, |
103 | byActor: ActorModelId, | 102 | byActor: MActorId, |
104 | toActors: ActorModelOnly[], | 103 | toActors: MActor[], |
105 | t?: Transaction, | 104 | t?: Transaction, |
106 | actorsException: ActorFollowerException[] = [] | 105 | actorsException: MActorFollowerException[] = [] |
107 | ) { | 106 | ) { |
108 | const uris = await computeUris(toActors, actorsException) | 107 | const uris = await computeUris(toActors, actorsException) |
109 | return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor)) | 108 | return afterCommitIfTransaction(t, () => broadcastTo(uris, data, byActor)) |
110 | } | 109 | } |
111 | 110 | ||
112 | function broadcastTo (uris: string[], data: any, byActor: ActorModelId) { | 111 | function broadcastTo (uris: string[], data: any, byActor: MActorId) { |
113 | if (uris.length === 0) return undefined | 112 | if (uris.length === 0) return undefined |
114 | 113 | ||
115 | logger.debug('Creating broadcast job.', { uris }) | 114 | logger.debug('Creating broadcast job.', { uris }) |
@@ -123,7 +122,7 @@ function broadcastTo (uris: string[], data: any, byActor: ActorModelId) { | |||
123 | return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }) | 122 | return JobQueue.Instance.createJob({ type: 'activitypub-http-broadcast', payload }) |
124 | } | 123 | } |
125 | 124 | ||
126 | function unicastTo (data: any, byActor: ActorModelId, toActorUrl: string) { | 125 | function unicastTo (data: any, byActor: MActorId, toActorUrl: string) { |
127 | logger.debug('Creating unicast job.', { uri: toActorUrl }) | 126 | logger.debug('Creating unicast job.', { uri: toActorUrl }) |
128 | 127 | ||
129 | const payload = { | 128 | const payload = { |
@@ -148,7 +147,7 @@ export { | |||
148 | 147 | ||
149 | // --------------------------------------------------------------------------- | 148 | // --------------------------------------------------------------------------- |
150 | 149 | ||
151 | async function computeFollowerUris (toFollowersOf: ActorModelId[], actorsException: ActorFollowerException[], t: Transaction) { | 150 | async function computeFollowerUris (toFollowersOf: MActorId[], actorsException: MActorFollowerException[], t: Transaction) { |
152 | const toActorFollowerIds = toFollowersOf.map(a => a.id) | 151 | const toActorFollowerIds = toFollowersOf.map(a => a.id) |
153 | 152 | ||
154 | const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t) | 153 | const result = await ActorFollowModel.listAcceptedFollowerSharedInboxUrls(toActorFollowerIds, t) |
@@ -157,7 +156,7 @@ async function computeFollowerUris (toFollowersOf: ActorModelId[], actorsExcepti | |||
157 | return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) | 156 | return result.data.filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) |
158 | } | 157 | } |
159 | 158 | ||
160 | async function computeUris (toActors: ActorModelOnly[], actorsException: ActorFollowerException[] = []) { | 159 | async function computeUris (toActors: MActor[], actorsException: MActorFollowerException[] = []) { |
161 | const serverActor = await getServerActor() | 160 | const serverActor = await getServerActor() |
162 | const targetUrls = toActors | 161 | const targetUrls = toActors |
163 | .filter(a => a.id !== serverActor.id) // Don't send to ourselves | 162 | .filter(a => a.id !== serverActor.id) // Don't send to ourselves |
@@ -170,7 +169,7 @@ async function computeUris (toActors: ActorModelOnly[], actorsException: ActorFo | |||
170 | .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) | 169 | .filter(sharedInbox => sharedInboxesException.indexOf(sharedInbox) === -1) |
171 | } | 170 | } |
172 | 171 | ||
173 | async function buildSharedInboxesException (actorsException: ActorFollowerException[]) { | 172 | async function buildSharedInboxesException (actorsException: MActorFollowerException[]) { |
174 | const serverActor = await getServerActor() | 173 | const serverActor = await getServerActor() |
175 | 174 | ||
176 | return actorsException | 175 | return actorsException |
diff --git a/server/lib/activitypub/share.ts b/server/lib/activitypub/share.ts index 7f38402b6..fdca9bed7 100644 --- a/server/lib/activitypub/share.ts +++ b/server/lib/activitypub/share.ts | |||
@@ -1,19 +1,18 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { VideoPrivacy } from '../../../shared/models/videos' | 2 | import { VideoPrivacy } from '../../../shared/models/videos' |
3 | import { getServerActor } from '../../helpers/utils' | 3 | import { getServerActor } from '../../helpers/utils' |
4 | import { VideoModel } from '../../models/video/video' | ||
5 | import { VideoShareModel } from '../../models/video/video-share' | 4 | import { VideoShareModel } from '../../models/video/video-share' |
6 | import { sendUndoAnnounce, sendVideoAnnounce } from './send' | 5 | import { sendUndoAnnounce, sendVideoAnnounce } from './send' |
7 | import { getVideoAnnounceActivityPubUrl } from './url' | 6 | import { getVideoAnnounceActivityPubUrl } from './url' |
8 | import { VideoChannelModel } from '../../models/video/video-channel' | ||
9 | import * as Bluebird from 'bluebird' | 7 | import * as Bluebird from 'bluebird' |
10 | import { doRequest } from '../../helpers/requests' | 8 | import { doRequest } from '../../helpers/requests' |
11 | import { getOrCreateActorAndServerAndModel } from './actor' | 9 | import { getOrCreateActorAndServerAndModel } from './actor' |
12 | import { logger } from '../../helpers/logger' | 10 | import { logger } from '../../helpers/logger' |
13 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' | 11 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
14 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' | 12 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' |
13 | import { MChannelActor, MChannelActorLight, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models/video' | ||
15 | 14 | ||
16 | async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) { | 15 | async function shareVideoByServerAndChannel (video: MVideoAccountLight, t: Transaction) { |
17 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined | 16 | if (video.privacy === VideoPrivacy.PRIVATE) return undefined |
18 | 17 | ||
19 | return Promise.all([ | 18 | return Promise.all([ |
@@ -22,7 +21,11 @@ async function shareVideoByServerAndChannel (video: VideoModel, t: Transaction) | |||
22 | ]) | 21 | ]) |
23 | } | 22 | } |
24 | 23 | ||
25 | async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) { | 24 | async function changeVideoChannelShare ( |
25 | video: MVideoAccountLight, | ||
26 | oldVideoChannel: MChannelActorLight, | ||
27 | t: Transaction | ||
28 | ) { | ||
26 | logger.info('Updating video channel of video %s: %s -> %s.', video.uuid, oldVideoChannel.name, video.VideoChannel.name) | 29 | logger.info('Updating video channel of video %s: %s -> %s.', video.uuid, oldVideoChannel.name, video.VideoChannel.name) |
27 | 30 | ||
28 | await undoShareByVideoChannel(video, oldVideoChannel, t) | 31 | await undoShareByVideoChannel(video, oldVideoChannel, t) |
@@ -30,7 +33,7 @@ async function changeVideoChannelShare (video: VideoModel, oldVideoChannel: Vide | |||
30 | await shareByVideoChannel(video, t) | 33 | await shareByVideoChannel(video, t) |
31 | } | 34 | } |
32 | 35 | ||
33 | async function addVideoShares (shareUrls: string[], instance: VideoModel) { | 36 | async function addVideoShares (shareUrls: string[], video: MVideoId) { |
34 | await Bluebird.map(shareUrls, async shareUrl => { | 37 | await Bluebird.map(shareUrls, async shareUrl => { |
35 | try { | 38 | try { |
36 | // Fetch url | 39 | // Fetch url |
@@ -50,7 +53,7 @@ async function addVideoShares (shareUrls: string[], instance: VideoModel) { | |||
50 | 53 | ||
51 | const entry = { | 54 | const entry = { |
52 | actorId: actor.id, | 55 | actorId: actor.id, |
53 | videoId: instance.id, | 56 | videoId: video.id, |
54 | url: shareUrl | 57 | url: shareUrl |
55 | } | 58 | } |
56 | 59 | ||
@@ -69,7 +72,7 @@ export { | |||
69 | 72 | ||
70 | // --------------------------------------------------------------------------- | 73 | // --------------------------------------------------------------------------- |
71 | 74 | ||
72 | async function shareByServer (video: VideoModel, t: Transaction) { | 75 | async function shareByServer (video: MVideo, t: Transaction) { |
73 | const serverActor = await getServerActor() | 76 | const serverActor = await getServerActor() |
74 | 77 | ||
75 | const serverShareUrl = getVideoAnnounceActivityPubUrl(serverActor, video) | 78 | const serverShareUrl = getVideoAnnounceActivityPubUrl(serverActor, video) |
@@ -88,7 +91,7 @@ async function shareByServer (video: VideoModel, t: Transaction) { | |||
88 | return sendVideoAnnounce(serverActor, serverShare, video, t) | 91 | return sendVideoAnnounce(serverActor, serverShare, video, t) |
89 | } | 92 | } |
90 | 93 | ||
91 | async function shareByVideoChannel (video: VideoModel, t: Transaction) { | 94 | async function shareByVideoChannel (video: MVideoAccountLight, t: Transaction) { |
92 | const videoChannelShareUrl = getVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video) | 95 | const videoChannelShareUrl = getVideoAnnounceActivityPubUrl(video.VideoChannel.Actor, video) |
93 | const [ videoChannelShare ] = await VideoShareModel.findOrCreate({ | 96 | const [ videoChannelShare ] = await VideoShareModel.findOrCreate({ |
94 | defaults: { | 97 | defaults: { |
@@ -105,7 +108,7 @@ async function shareByVideoChannel (video: VideoModel, t: Transaction) { | |||
105 | return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t) | 108 | return sendVideoAnnounce(video.VideoChannel.Actor, videoChannelShare, video, t) |
106 | } | 109 | } |
107 | 110 | ||
108 | async function undoShareByVideoChannel (video: VideoModel, oldVideoChannel: VideoChannelModel, t: Transaction) { | 111 | async function undoShareByVideoChannel (video: MVideo, oldVideoChannel: MChannelActorLight, t: Transaction) { |
109 | // Load old share | 112 | // Load old share |
110 | const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t) | 113 | const oldShare = await VideoShareModel.load(oldVideoChannel.actorId, video.id, t) |
111 | if (!oldShare) return new Error('Cannot find old video channel share ' + oldVideoChannel.actorId + ' for video ' + video.id) | 114 | if (!oldShare) return new Error('Cannot find old video channel share ' + oldVideoChannel.actorId + ' for video ' + video.id) |
diff --git a/server/lib/activitypub/url.ts b/server/lib/activitypub/url.ts index dfcb3c668..6290af34b 100644 --- a/server/lib/activitypub/url.ts +++ b/server/lib/activitypub/url.ts | |||
@@ -1,36 +1,42 @@ | |||
1 | import { WEBSERVER } from '../../initializers/constants' | 1 | import { WEBSERVER } from '../../initializers/constants' |
2 | import { VideoModel } from '../../models/video/video' | 2 | import { |
3 | import { VideoAbuseModel } from '../../models/video/video-abuse' | 3 | MActor, |
4 | import { VideoCommentModel } from '../../models/video/video-comment' | 4 | MActorFollowActors, |
5 | import { VideoFileModel } from '../../models/video/video-file' | 5 | MActorId, |
6 | import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' | 6 | MActorUrl, |
7 | import { VideoPlaylistModel } from '../../models/video/video-playlist' | 7 | MCommentId, |
8 | import { ActorModelOnly, ActorModelUrl } from '../../typings/models' | 8 | MVideoAbuseId, |
9 | import { ActorFollowModelLight } from '../../typings/models/actor-follow' | 9 | MVideoId, |
10 | 10 | MVideoUrl, | |
11 | function getVideoActivityPubUrl (video: VideoModel) { | 11 | MVideoUUID |
12 | } from '../../typings/models' | ||
13 | import { MVideoPlaylist, MVideoPlaylistUUID } from '../../typings/models/video/video-playlist' | ||
14 | import { MVideoFileVideoUUID } from '../../typings/models/video/video-file' | ||
15 | import { MStreamingPlaylist } from '../../typings/models/video/video-streaming-playlist' | ||
16 | |||
17 | function getVideoActivityPubUrl (video: MVideoUUID) { | ||
12 | return WEBSERVER.URL + '/videos/watch/' + video.uuid | 18 | return WEBSERVER.URL + '/videos/watch/' + video.uuid |
13 | } | 19 | } |
14 | 20 | ||
15 | function getVideoPlaylistActivityPubUrl (videoPlaylist: VideoPlaylistModel) { | 21 | function getVideoPlaylistActivityPubUrl (videoPlaylist: MVideoPlaylist) { |
16 | return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid | 22 | return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid |
17 | } | 23 | } |
18 | 24 | ||
19 | function getVideoPlaylistElementActivityPubUrl (videoPlaylist: VideoPlaylistModel, video: VideoModel) { | 25 | function getVideoPlaylistElementActivityPubUrl (videoPlaylist: MVideoPlaylistUUID, video: MVideoUUID) { |
20 | return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/' + video.uuid | 26 | return WEBSERVER.URL + '/video-playlists/' + videoPlaylist.uuid + '/' + video.uuid |
21 | } | 27 | } |
22 | 28 | ||
23 | function getVideoCacheFileActivityPubUrl (videoFile: VideoFileModel) { | 29 | function getVideoCacheFileActivityPubUrl (videoFile: MVideoFileVideoUUID) { |
24 | const suffixFPS = videoFile.fps && videoFile.fps !== -1 ? '-' + videoFile.fps : '' | 30 | const suffixFPS = videoFile.fps && videoFile.fps !== -1 ? '-' + videoFile.fps : '' |
25 | 31 | ||
26 | return `${WEBSERVER.URL}/redundancy/videos/${videoFile.Video.uuid}/${videoFile.resolution}${suffixFPS}` | 32 | return `${WEBSERVER.URL}/redundancy/videos/${videoFile.Video.uuid}/${videoFile.resolution}${suffixFPS}` |
27 | } | 33 | } |
28 | 34 | ||
29 | function getVideoCacheStreamingPlaylistActivityPubUrl (video: VideoModel, playlist: VideoStreamingPlaylistModel) { | 35 | function getVideoCacheStreamingPlaylistActivityPubUrl (video: MVideoUUID, playlist: MStreamingPlaylist) { |
30 | return `${WEBSERVER.URL}/redundancy/streaming-playlists/${playlist.getStringType()}/${video.uuid}` | 36 | return `${WEBSERVER.URL}/redundancy/streaming-playlists/${playlist.getStringType()}/${video.uuid}` |
31 | } | 37 | } |
32 | 38 | ||
33 | function getVideoCommentActivityPubUrl (video: VideoModel, videoComment: VideoCommentModel) { | 39 | function getVideoCommentActivityPubUrl (video: MVideoUUID, videoComment: MCommentId) { |
34 | return WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id | 40 | return WEBSERVER.URL + '/videos/watch/' + video.uuid + '/comments/' + videoComment.id |
35 | } | 41 | } |
36 | 42 | ||
@@ -42,54 +48,54 @@ function getAccountActivityPubUrl (accountName: string) { | |||
42 | return WEBSERVER.URL + '/accounts/' + accountName | 48 | return WEBSERVER.URL + '/accounts/' + accountName |
43 | } | 49 | } |
44 | 50 | ||
45 | function getVideoAbuseActivityPubUrl (videoAbuse: VideoAbuseModel) { | 51 | function getVideoAbuseActivityPubUrl (videoAbuse: MVideoAbuseId) { |
46 | return WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id | 52 | return WEBSERVER.URL + '/admin/video-abuses/' + videoAbuse.id |
47 | } | 53 | } |
48 | 54 | ||
49 | function getVideoViewActivityPubUrl (byActor: ActorModelUrl, video: VideoModel) { | 55 | function getVideoViewActivityPubUrl (byActor: MActorUrl, video: MVideoId) { |
50 | return byActor.url + '/views/videos/' + video.id + '/' + new Date().toISOString() | 56 | return byActor.url + '/views/videos/' + video.id + '/' + new Date().toISOString() |
51 | } | 57 | } |
52 | 58 | ||
53 | function getVideoLikeActivityPubUrl (byActor: ActorModelUrl, video: VideoModel | { id: number }) { | 59 | function getVideoLikeActivityPubUrl (byActor: MActorUrl, video: MVideoId) { |
54 | return byActor.url + '/likes/' + video.id | 60 | return byActor.url + '/likes/' + video.id |
55 | } | 61 | } |
56 | 62 | ||
57 | function getVideoDislikeActivityPubUrl (byActor: ActorModelUrl, video: VideoModel | { id: number }) { | 63 | function getVideoDislikeActivityPubUrl (byActor: MActorUrl, video: MVideoId) { |
58 | return byActor.url + '/dislikes/' + video.id | 64 | return byActor.url + '/dislikes/' + video.id |
59 | } | 65 | } |
60 | 66 | ||
61 | function getVideoSharesActivityPubUrl (video: VideoModel) { | 67 | function getVideoSharesActivityPubUrl (video: MVideoUrl) { |
62 | return video.url + '/announces' | 68 | return video.url + '/announces' |
63 | } | 69 | } |
64 | 70 | ||
65 | function getVideoCommentsActivityPubUrl (video: VideoModel) { | 71 | function getVideoCommentsActivityPubUrl (video: MVideoUrl) { |
66 | return video.url + '/comments' | 72 | return video.url + '/comments' |
67 | } | 73 | } |
68 | 74 | ||
69 | function getVideoLikesActivityPubUrl (video: VideoModel) { | 75 | function getVideoLikesActivityPubUrl (video: MVideoUrl) { |
70 | return video.url + '/likes' | 76 | return video.url + '/likes' |
71 | } | 77 | } |
72 | 78 | ||
73 | function getVideoDislikesActivityPubUrl (video: VideoModel) { | 79 | function getVideoDislikesActivityPubUrl (video: MVideoUrl) { |
74 | return video.url + '/dislikes' | 80 | return video.url + '/dislikes' |
75 | } | 81 | } |
76 | 82 | ||
77 | function getActorFollowActivityPubUrl (follower: ActorModelOnly, following: ActorModelOnly) { | 83 | function getActorFollowActivityPubUrl (follower: MActor, following: MActorId) { |
78 | return follower.url + '/follows/' + following.id | 84 | return follower.url + '/follows/' + following.id |
79 | } | 85 | } |
80 | 86 | ||
81 | function getActorFollowAcceptActivityPubUrl (actorFollow: ActorFollowModelLight) { | 87 | function getActorFollowAcceptActivityPubUrl (actorFollow: MActorFollowActors) { |
82 | const follower = actorFollow.ActorFollower | 88 | const follower = actorFollow.ActorFollower |
83 | const me = actorFollow.ActorFollowing | 89 | const me = actorFollow.ActorFollowing |
84 | 90 | ||
85 | return follower.url + '/accepts/follows/' + me.id | 91 | return follower.url + '/accepts/follows/' + me.id |
86 | } | 92 | } |
87 | 93 | ||
88 | function getActorFollowRejectActivityPubUrl (follower: ActorModelOnly, following: ActorModelOnly) { | 94 | function getActorFollowRejectActivityPubUrl (follower: MActorUrl, following: MActorId) { |
89 | return follower.url + '/rejects/follows/' + following.id | 95 | return follower.url + '/rejects/follows/' + following.id |
90 | } | 96 | } |
91 | 97 | ||
92 | function getVideoAnnounceActivityPubUrl (byActor: ActorModelOnly, video: VideoModel) { | 98 | function getVideoAnnounceActivityPubUrl (byActor: MActorId, video: MVideoUrl) { |
93 | return video.url + '/announces/' + byActor.id | 99 | return video.url + '/announces/' + byActor.id |
94 | } | 100 | } |
95 | 101 | ||
diff --git a/server/lib/activitypub/video-comments.ts b/server/lib/activitypub/video-comments.ts index 8d2c1ade3..3e8306fa4 100644 --- a/server/lib/activitypub/video-comments.ts +++ b/server/lib/activitypub/video-comments.ts | |||
@@ -2,20 +2,20 @@ import { sanitizeAndCheckVideoCommentObject } from '../../helpers/custom-validat | |||
2 | import { logger } from '../../helpers/logger' | 2 | import { logger } from '../../helpers/logger' |
3 | import { doRequest } from '../../helpers/requests' | 3 | import { doRequest } from '../../helpers/requests' |
4 | import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' | 4 | import { ACTIVITY_PUB, CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
5 | import { VideoModel } from '../../models/video/video' | ||
6 | import { VideoCommentModel } from '../../models/video/video-comment' | 5 | import { VideoCommentModel } from '../../models/video/video-comment' |
7 | import { getOrCreateActorAndServerAndModel } from './actor' | 6 | import { getOrCreateActorAndServerAndModel } from './actor' |
8 | import { getOrCreateVideoAndAccountAndChannel } from './videos' | 7 | import { getOrCreateVideoAndAccountAndChannel } from './videos' |
9 | import * as Bluebird from 'bluebird' | 8 | import * as Bluebird from 'bluebird' |
10 | import { checkUrlsSameHost } from '../../helpers/activitypub' | 9 | import { checkUrlsSameHost } from '../../helpers/activitypub' |
10 | import { MCommentOwner, MCommentOwnerVideo, MVideoAccountLightBlacklistAllFiles } from '../../typings/models/video' | ||
11 | 11 | ||
12 | type ResolveThreadParams = { | 12 | type ResolveThreadParams = { |
13 | url: string, | 13 | url: string, |
14 | comments?: VideoCommentModel[], | 14 | comments?: MCommentOwner[], |
15 | isVideo?: boolean, | 15 | isVideo?: boolean, |
16 | commentCreated?: boolean | 16 | commentCreated?: boolean |
17 | } | 17 | } |
18 | type ResolveThreadResult = Promise<{ video: VideoModel, comment: VideoCommentModel, commentCreated: boolean }> | 18 | type ResolveThreadResult = Promise<{ video: MVideoAccountLightBlacklistAllFiles, comment: MCommentOwnerVideo, commentCreated: boolean }> |
19 | 19 | ||
20 | async function addVideoComments (commentUrls: string[]) { | 20 | async function addVideoComments (commentUrls: string[]) { |
21 | return Bluebird.map(commentUrls, commentUrl => { | 21 | return Bluebird.map(commentUrls, commentUrl => { |
@@ -85,9 +85,9 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) { | |||
85 | const syncParam = { likes: true, dislikes: true, shares: true, comments: false, thumbnail: true, refreshVideo: false } | 85 | const syncParam = { likes: true, dislikes: true, shares: true, comments: false, thumbnail: true, refreshVideo: false } |
86 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: url, syncParam }) | 86 | const { video } = await getOrCreateVideoAndAccountAndChannel({ videoObject: url, syncParam }) |
87 | 87 | ||
88 | let resultComment: VideoCommentModel | 88 | let resultComment: MCommentOwnerVideo |
89 | if (comments.length !== 0) { | 89 | if (comments.length !== 0) { |
90 | const firstReply = comments[ comments.length - 1 ] | 90 | const firstReply = comments[ comments.length - 1 ] as MCommentOwnerVideo |
91 | firstReply.inReplyToCommentId = null | 91 | firstReply.inReplyToCommentId = null |
92 | firstReply.originCommentId = null | 92 | firstReply.originCommentId = null |
93 | firstReply.videoId = video.id | 93 | firstReply.videoId = video.id |
@@ -97,7 +97,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) { | |||
97 | comments[comments.length - 1] = await firstReply.save() | 97 | comments[comments.length - 1] = await firstReply.save() |
98 | 98 | ||
99 | for (let i = comments.length - 2; i >= 0; i--) { | 99 | for (let i = comments.length - 2; i >= 0; i--) { |
100 | const comment = comments[ i ] | 100 | const comment = comments[ i ] as MCommentOwnerVideo |
101 | comment.originCommentId = firstReply.id | 101 | comment.originCommentId = firstReply.id |
102 | comment.inReplyToCommentId = comments[ i + 1 ].id | 102 | comment.inReplyToCommentId = comments[ i + 1 ].id |
103 | comment.videoId = video.id | 103 | comment.videoId = video.id |
@@ -107,7 +107,7 @@ async function tryResolveThreadFromVideo (params: ResolveThreadParams) { | |||
107 | comments[i] = await comment.save() | 107 | comments[i] = await comment.save() |
108 | } | 108 | } |
109 | 109 | ||
110 | resultComment = comments[0] | 110 | resultComment = comments[0] as MCommentOwnerVideo |
111 | } | 111 | } |
112 | 112 | ||
113 | return { video, comment: resultComment, commentCreated } | 113 | return { video, comment: resultComment, commentCreated } |
@@ -151,7 +151,7 @@ async function resolveParentComment (params: ResolveThreadParams) { | |||
151 | originCommentId: null, | 151 | originCommentId: null, |
152 | createdAt: new Date(body.published), | 152 | createdAt: new Date(body.published), |
153 | updatedAt: new Date(body.updated) | 153 | updatedAt: new Date(body.updated) |
154 | }) | 154 | }) as MCommentOwner |
155 | comment.Account = actor.Account | 155 | comment.Account = actor.Account |
156 | 156 | ||
157 | return resolveThread({ | 157 | return resolveThread({ |
diff --git a/server/lib/activitypub/video-rates.ts b/server/lib/activitypub/video-rates.ts index cda5b2981..6bd46bb58 100644 --- a/server/lib/activitypub/video-rates.ts +++ b/server/lib/activitypub/video-rates.ts | |||
@@ -1,6 +1,4 @@ | |||
1 | import { Transaction } from 'sequelize' | 1 | import { Transaction } from 'sequelize' |
2 | import { AccountModel } from '../../models/account/account' | ||
3 | import { VideoModel } from '../../models/video/video' | ||
4 | import { sendLike, sendUndoDislike, sendUndoLike } from './send' | 2 | import { sendLike, sendUndoDislike, sendUndoLike } from './send' |
5 | import { VideoRateType } from '../../../shared/models/videos' | 3 | import { VideoRateType } from '../../../shared/models/videos' |
6 | import * as Bluebird from 'bluebird' | 4 | import * as Bluebird from 'bluebird' |
@@ -10,11 +8,11 @@ import { logger } from '../../helpers/logger' | |||
10 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' | 8 | import { CRAWL_REQUEST_CONCURRENCY } from '../../initializers/constants' |
11 | import { doRequest } from '../../helpers/requests' | 9 | import { doRequest } from '../../helpers/requests' |
12 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' | 10 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' |
13 | import { ActorModel } from '../../models/activitypub/actor' | ||
14 | import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from './url' | 11 | import { getVideoDislikeActivityPubUrl, getVideoLikeActivityPubUrl } from './url' |
15 | import { sendDislike } from './send/send-dislike' | 12 | import { sendDislike } from './send/send-dislike' |
13 | import { MAccountActor, MActorUrl, MVideo, MVideoAccountLight, MVideoId } from '../../typings/models' | ||
16 | 14 | ||
17 | async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRateType) { | 15 | async function createRates (ratesUrl: string[], video: MVideo, rate: VideoRateType) { |
18 | let rateCounts = 0 | 16 | let rateCounts = 0 |
19 | 17 | ||
20 | await Bluebird.map(ratesUrl, async rateUrl => { | 18 | await Bluebird.map(ratesUrl, async rateUrl => { |
@@ -64,11 +62,13 @@ async function createRates (ratesUrl: string[], video: VideoModel, rate: VideoRa | |||
64 | return | 62 | return |
65 | } | 63 | } |
66 | 64 | ||
67 | async function sendVideoRateChange (account: AccountModel, | 65 | async function sendVideoRateChange ( |
68 | video: VideoModel, | 66 | account: MAccountActor, |
69 | likes: number, | 67 | video: MVideoAccountLight, |
70 | dislikes: number, | 68 | likes: number, |
71 | t: Transaction) { | 69 | dislikes: number, |
70 | t: Transaction | ||
71 | ) { | ||
72 | const actor = account.Actor | 72 | const actor = account.Actor |
73 | 73 | ||
74 | // Keep the order: first we undo and then we create | 74 | // Keep the order: first we undo and then we create |
@@ -84,8 +84,10 @@ async function sendVideoRateChange (account: AccountModel, | |||
84 | if (dislikes > 0) await sendDislike(actor, video, t) | 84 | if (dislikes > 0) await sendDislike(actor, video, t) |
85 | } | 85 | } |
86 | 86 | ||
87 | function getRateUrl (rateType: VideoRateType, actor: ActorModel, video: VideoModel) { | 87 | function getRateUrl (rateType: VideoRateType, actor: MActorUrl, video: MVideoId) { |
88 | return rateType === 'like' ? getVideoLikeActivityPubUrl(actor, video) : getVideoDislikeActivityPubUrl(actor, video) | 88 | return rateType === 'like' |
89 | ? getVideoLikeActivityPubUrl(actor, video) | ||
90 | : getVideoDislikeActivityPubUrl(actor, video) | ||
89 | } | 91 | } |
90 | 92 | ||
91 | export { | 93 | export { |
diff --git a/server/lib/activitypub/videos.ts b/server/lib/activitypub/videos.ts index 3a8451a32..c318978fd 100644 --- a/server/lib/activitypub/videos.ts +++ b/server/lib/activitypub/videos.ts | |||
@@ -24,7 +24,6 @@ import { | |||
24 | REMOTE_SCHEME, | 24 | REMOTE_SCHEME, |
25 | STATIC_PATHS | 25 | STATIC_PATHS |
26 | } from '../../initializers/constants' | 26 | } from '../../initializers/constants' |
27 | import { ActorModel } from '../../models/activitypub/actor' | ||
28 | import { TagModel } from '../../models/video/tag' | 27 | import { TagModel } from '../../models/video/tag' |
29 | import { VideoModel } from '../../models/video/video' | 28 | import { VideoModel } from '../../models/video/video' |
30 | import { VideoFileModel } from '../../models/video/video-file' | 29 | import { VideoFileModel } from '../../models/video/video-file' |
@@ -38,7 +37,6 @@ import { JobQueue } from '../job-queue' | |||
38 | import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher' | 37 | import { ActivitypubHttpFetcherPayload } from '../job-queue/handlers/activitypub-http-fetcher' |
39 | import { createRates } from './video-rates' | 38 | import { createRates } from './video-rates' |
40 | import { addVideoShares, shareVideoByServerAndChannel } from './share' | 39 | import { addVideoShares, shareVideoByServerAndChannel } from './share' |
41 | import { AccountModel } from '../../models/account/account' | ||
42 | import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' | 40 | import { fetchVideoByUrl, VideoFetchByUrlType } from '../../helpers/video' |
43 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' | 41 | import { checkUrlsSameHost, getAPId } from '../../helpers/activitypub' |
44 | import { Notifier } from '../notifier' | 42 | import { Notifier } from '../notifier' |
@@ -49,15 +47,31 @@ import { VideoShareModel } from '../../models/video/video-share' | |||
49 | import { VideoCommentModel } from '../../models/video/video-comment' | 47 | import { VideoCommentModel } from '../../models/video/video-comment' |
50 | import { sequelizeTypescript } from '../../initializers/database' | 48 | import { sequelizeTypescript } from '../../initializers/database' |
51 | import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail' | 49 | import { createPlaceholderThumbnail, createVideoMiniatureFromUrl } from '../thumbnail' |
52 | import { ThumbnailModel } from '../../models/video/thumbnail' | ||
53 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | 50 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' |
54 | import { join } from 'path' | 51 | import { join } from 'path' |
55 | import { FilteredModelAttributes } from '../../typings/sequelize' | 52 | import { FilteredModelAttributes } from '../../typings/sequelize' |
56 | import { autoBlacklistVideoIfNeeded } from '../video-blacklist' | 53 | import { autoBlacklistVideoIfNeeded } from '../video-blacklist' |
57 | import { ActorFollowScoreCache } from '../files-cache' | 54 | import { ActorFollowScoreCache } from '../files-cache' |
58 | import { AccountModelIdActor, VideoChannelModelId, VideoChannelModelIdActor } from '../../typings/models' | 55 | import { |
56 | MAccountIdActor, | ||
57 | MChannelAccountLight, | ||
58 | MChannelDefault, | ||
59 | MChannelId, | ||
60 | MVideo, | ||
61 | MVideoAccountLight, | ||
62 | MVideoAccountLightBlacklistAllFiles, | ||
63 | MVideoAP, | ||
64 | MVideoAPWithoutCaption, | ||
65 | MVideoFile, | ||
66 | MVideoFullLight, | ||
67 | MVideoId, | ||
68 | MVideoThumbnail | ||
69 | } from '../../typings/models' | ||
70 | import { MThumbnail } from '../../typings/models/video/thumbnail' | ||
71 | |||
72 | async function federateVideoIfNeeded (videoArg: MVideoAPWithoutCaption, isNewVideo: boolean, transaction?: sequelize.Transaction) { | ||
73 | const video = videoArg as MVideoAP | ||
59 | 74 | ||
60 | async function federateVideoIfNeeded (video: VideoModel, isNewVideo: boolean, transaction?: sequelize.Transaction) { | ||
61 | if ( | 75 | if ( |
62 | // Check this is not a blacklisted video, or unfederated blacklisted video | 76 | // Check this is not a blacklisted video, or unfederated blacklisted video |
63 | (video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) && | 77 | (video.isBlacklisted() === false || (isNewVideo === false && video.VideoBlacklist.unfederated === false)) && |
@@ -102,7 +116,7 @@ async function fetchRemoteVideo (videoUrl: string): Promise<{ response: request. | |||
102 | return { response, videoObject: body } | 116 | return { response, videoObject: body } |
103 | } | 117 | } |
104 | 118 | ||
105 | async function fetchRemoteVideoDescription (video: VideoModel) { | 119 | async function fetchRemoteVideoDescription (video: MVideoAccountLight) { |
106 | const host = video.VideoChannel.Account.Actor.Server.host | 120 | const host = video.VideoChannel.Account.Actor.Server.host |
107 | const path = video.getDescriptionAPIPath() | 121 | const path = video.getDescriptionAPIPath() |
108 | const options = { | 122 | const options = { |
@@ -114,14 +128,14 @@ async function fetchRemoteVideoDescription (video: VideoModel) { | |||
114 | return body.description ? body.description : '' | 128 | return body.description ? body.description : '' |
115 | } | 129 | } |
116 | 130 | ||
117 | function fetchRemoteVideoStaticFile (video: VideoModel, path: string, destPath: string) { | 131 | function fetchRemoteVideoStaticFile (video: MVideoAccountLight, path: string, destPath: string) { |
118 | const url = buildRemoteBaseUrl(video, path) | 132 | const url = buildRemoteBaseUrl(video, path) |
119 | 133 | ||
120 | // We need to provide a callback, if no we could have an uncaught exception | 134 | // We need to provide a callback, if no we could have an uncaught exception |
121 | return doRequestAndSaveToFile({ uri: url }, destPath) | 135 | return doRequestAndSaveToFile({ uri: url }, destPath) |
122 | } | 136 | } |
123 | 137 | ||
124 | function buildRemoteBaseUrl (video: VideoModel, path: string) { | 138 | function buildRemoteBaseUrl (video: MVideoAccountLight, path: string) { |
125 | const host = video.VideoChannel.Account.Actor.Server.host | 139 | const host = video.VideoChannel.Account.Actor.Server.host |
126 | 140 | ||
127 | return REMOTE_SCHEME.HTTP + '://' + host + path | 141 | return REMOTE_SCHEME.HTTP + '://' + host + path |
@@ -146,7 +160,7 @@ type SyncParam = { | |||
146 | thumbnail: boolean | 160 | thumbnail: boolean |
147 | refreshVideo?: boolean | 161 | refreshVideo?: boolean |
148 | } | 162 | } |
149 | async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) { | 163 | async function syncVideoExternalAttributes (video: MVideo, fetchedVideo: VideoTorrentObject, syncParam: SyncParam) { |
150 | logger.info('Adding likes/dislikes/shares/comments of video %s.', video.uuid) | 164 | logger.info('Adding likes/dislikes/shares/comments of video %s.', video.uuid) |
151 | 165 | ||
152 | const jobPayloads: ActivitypubHttpFetcherPayload[] = [] | 166 | const jobPayloads: ActivitypubHttpFetcherPayload[] = [] |
@@ -194,12 +208,24 @@ async function syncVideoExternalAttributes (video: VideoModel, fetchedVideo: Vid | |||
194 | await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })) | 208 | await Bluebird.map(jobPayloads, payload => JobQueue.Instance.createJob({ type: 'activitypub-http-fetcher', payload })) |
195 | } | 209 | } |
196 | 210 | ||
211 | function getOrCreateVideoAndAccountAndChannel (options: { | ||
212 | videoObject: { id: string } | string, | ||
213 | syncParam?: SyncParam, | ||
214 | fetchType?: 'all', | ||
215 | allowRefresh?: boolean | ||
216 | }): Promise<{ video: MVideoAccountLightBlacklistAllFiles, created: boolean, autoBlacklisted?: boolean }> | ||
217 | function getOrCreateVideoAndAccountAndChannel (options: { | ||
218 | videoObject: { id: string } | string, | ||
219 | syncParam?: SyncParam, | ||
220 | fetchType?: VideoFetchByUrlType, | ||
221 | allowRefresh?: boolean | ||
222 | }): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> | ||
197 | async function getOrCreateVideoAndAccountAndChannel (options: { | 223 | async function getOrCreateVideoAndAccountAndChannel (options: { |
198 | videoObject: { id: string } | string, | 224 | videoObject: { id: string } | string, |
199 | syncParam?: SyncParam, | 225 | syncParam?: SyncParam, |
200 | fetchType?: VideoFetchByUrlType, | 226 | fetchType?: VideoFetchByUrlType, |
201 | allowRefresh?: boolean // true by default | 227 | allowRefresh?: boolean // true by default |
202 | }) { | 228 | }): Promise<{ video: MVideoAccountLightBlacklistAllFiles | MVideoThumbnail, created: boolean, autoBlacklisted?: boolean }> { |
203 | // Default params | 229 | // Default params |
204 | const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false } | 230 | const syncParam = options.syncParam || { likes: true, dislikes: true, shares: true, comments: true, thumbnail: true, refreshVideo: false } |
205 | const fetchType = options.fetchType || 'all' | 231 | const fetchType = options.fetchType || 'all' |
@@ -227,8 +253,9 @@ async function getOrCreateVideoAndAccountAndChannel (options: { | |||
227 | const { videoObject: fetchedVideo } = await fetchRemoteVideo(videoUrl) | 253 | const { videoObject: fetchedVideo } = await fetchRemoteVideo(videoUrl) |
228 | if (!fetchedVideo) throw new Error('Cannot fetch remote video with url: ' + videoUrl) | 254 | if (!fetchedVideo) throw new Error('Cannot fetch remote video with url: ' + videoUrl) |
229 | 255 | ||
230 | const channelActor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo) | 256 | const actor = await getOrCreateVideoChannelFromVideoObject(fetchedVideo) |
231 | const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, channelActor, syncParam.thumbnail) | 257 | const videoChannel = actor.VideoChannel |
258 | const { autoBlacklisted, videoCreated } = await retryTransactionWrapper(createVideo, fetchedVideo, videoChannel, syncParam.thumbnail) | ||
232 | 259 | ||
233 | await syncVideoExternalAttributes(videoCreated, fetchedVideo, syncParam) | 260 | await syncVideoExternalAttributes(videoCreated, fetchedVideo, syncParam) |
234 | 261 | ||
@@ -236,22 +263,22 @@ async function getOrCreateVideoAndAccountAndChannel (options: { | |||
236 | } | 263 | } |
237 | 264 | ||
238 | async function updateVideoFromAP (options: { | 265 | async function updateVideoFromAP (options: { |
239 | video: VideoModel, | 266 | video: MVideoAccountLightBlacklistAllFiles, |
240 | videoObject: VideoTorrentObject, | 267 | videoObject: VideoTorrentObject, |
241 | account: AccountModelIdActor, | 268 | account: MAccountIdActor, |
242 | channel: VideoChannelModelIdActor, | 269 | channel: MChannelDefault, |
243 | overrideTo?: string[] | 270 | overrideTo?: string[] |
244 | }) { | 271 | }) { |
245 | const { video, videoObject, account, channel, overrideTo } = options | 272 | const { video, videoObject, account, channel, overrideTo } = options |
246 | 273 | ||
247 | logger.debug('Updating remote video "%s".', options.videoObject.uuid) | 274 | logger.debug('Updating remote video "%s".', options.videoObject.uuid, { account, channel }) |
248 | 275 | ||
249 | let videoFieldsSave: any | 276 | let videoFieldsSave: any |
250 | const wasPrivateVideo = video.privacy === VideoPrivacy.PRIVATE | 277 | const wasPrivateVideo = video.privacy === VideoPrivacy.PRIVATE |
251 | const wasUnlistedVideo = video.privacy === VideoPrivacy.UNLISTED | 278 | const wasUnlistedVideo = video.privacy === VideoPrivacy.UNLISTED |
252 | 279 | ||
253 | try { | 280 | try { |
254 | let thumbnailModel: ThumbnailModel | 281 | let thumbnailModel: MThumbnail |
255 | 282 | ||
256 | try { | 283 | try { |
257 | thumbnailModel = await createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) | 284 | thumbnailModel = await createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) |
@@ -259,7 +286,7 @@ async function updateVideoFromAP (options: { | |||
259 | logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }) | 286 | logger.warn('Cannot generate thumbnail of %s.', videoObject.id, { err }) |
260 | } | 287 | } |
261 | 288 | ||
262 | await sequelizeTypescript.transaction(async t => { | 289 | const videoUpdated = await sequelizeTypescript.transaction(async t => { |
263 | const sequelizeOptions = { transaction: t } | 290 | const sequelizeOptions = { transaction: t } |
264 | 291 | ||
265 | videoFieldsSave = video.toJSON() | 292 | videoFieldsSave = video.toJSON() |
@@ -293,21 +320,21 @@ async function updateVideoFromAP (options: { | |||
293 | video.channelId = videoData.channelId | 320 | video.channelId = videoData.channelId |
294 | video.views = videoData.views | 321 | video.views = videoData.views |
295 | 322 | ||
296 | await video.save(sequelizeOptions) | 323 | const videoUpdated = await video.save(sequelizeOptions) as MVideoFullLight |
297 | 324 | ||
298 | if (thumbnailModel) await video.addAndSaveThumbnail(thumbnailModel, t) | 325 | if (thumbnailModel) await videoUpdated.addAndSaveThumbnail(thumbnailModel, t) |
299 | 326 | ||
300 | // FIXME: use icon URL instead | 327 | // FIXME: use icon URL instead |
301 | const previewUrl = buildRemoteBaseUrl(video, join(STATIC_PATHS.PREVIEWS, video.getPreview().filename)) | 328 | const previewUrl = buildRemoteBaseUrl(videoUpdated, join(STATIC_PATHS.PREVIEWS, videoUpdated.getPreview().filename)) |
302 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) | 329 | const previewModel = createPlaceholderThumbnail(previewUrl, video, ThumbnailType.PREVIEW, PREVIEWS_SIZE) |
303 | await video.addAndSaveThumbnail(previewModel, t) | 330 | await videoUpdated.addAndSaveThumbnail(previewModel, t) |
304 | 331 | ||
305 | { | 332 | { |
306 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(video, videoObject) | 333 | const videoFileAttributes = videoFileActivityUrlToDBAttributes(videoUpdated, videoObject) |
307 | const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a)) | 334 | const newVideoFiles = videoFileAttributes.map(a => new VideoFileModel(a)) |
308 | 335 | ||
309 | // Remove video files that do not exist anymore | 336 | // Remove video files that do not exist anymore |
310 | const destroyTasks = video.VideoFiles | 337 | const destroyTasks = videoUpdated.VideoFiles |
311 | .filter(f => !newVideoFiles.find(newFile => newFile.hasSameUniqueKeysThan(f))) | 338 | .filter(f => !newVideoFiles.find(newFile => newFile.hasSameUniqueKeysThan(f))) |
312 | .map(f => f.destroy(sequelizeOptions)) | 339 | .map(f => f.destroy(sequelizeOptions)) |
313 | await Promise.all(destroyTasks) | 340 | await Promise.all(destroyTasks) |
@@ -318,15 +345,15 @@ async function updateVideoFromAP (options: { | |||
318 | .then(([ file ]) => file) | 345 | .then(([ file ]) => file) |
319 | }) | 346 | }) |
320 | 347 | ||
321 | video.VideoFiles = await Promise.all(upsertTasks) | 348 | videoUpdated.VideoFiles = await Promise.all(upsertTasks) |
322 | } | 349 | } |
323 | 350 | ||
324 | { | 351 | { |
325 | const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(video, videoObject, video.VideoFiles) | 352 | const streamingPlaylistAttributes = streamingPlaylistActivityUrlToDBAttributes(videoUpdated, videoObject, videoUpdated.VideoFiles) |
326 | const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a)) | 353 | const newStreamingPlaylists = streamingPlaylistAttributes.map(a => new VideoStreamingPlaylistModel(a)) |
327 | 354 | ||
328 | // Remove video files that do not exist anymore | 355 | // Remove video files that do not exist anymore |
329 | const destroyTasks = video.VideoStreamingPlaylists | 356 | const destroyTasks = videoUpdated.VideoStreamingPlaylists |
330 | .filter(f => !newStreamingPlaylists.find(newPlaylist => newPlaylist.hasSameUniqueKeysThan(f))) | 357 | .filter(f => !newStreamingPlaylists.find(newPlaylist => newPlaylist.hasSameUniqueKeysThan(f))) |
331 | .map(f => f.destroy(sequelizeOptions)) | 358 | .map(f => f.destroy(sequelizeOptions)) |
332 | await Promise.all(destroyTasks) | 359 | await Promise.all(destroyTasks) |
@@ -337,38 +364,42 @@ async function updateVideoFromAP (options: { | |||
337 | .then(([ streamingPlaylist ]) => streamingPlaylist) | 364 | .then(([ streamingPlaylist ]) => streamingPlaylist) |
338 | }) | 365 | }) |
339 | 366 | ||
340 | video.VideoStreamingPlaylists = await Promise.all(upsertTasks) | 367 | videoUpdated.VideoStreamingPlaylists = await Promise.all(upsertTasks) |
341 | } | 368 | } |
342 | 369 | ||
343 | { | 370 | { |
344 | // Update Tags | 371 | // Update Tags |
345 | const tags = videoObject.tag.map(tag => tag.name) | 372 | const tags = videoObject.tag.map(tag => tag.name) |
346 | const tagInstances = await TagModel.findOrCreateTags(tags, t) | 373 | const tagInstances = await TagModel.findOrCreateTags(tags, t) |
347 | await video.$set('Tags', tagInstances, sequelizeOptions) | 374 | await videoUpdated.$set('Tags', tagInstances, sequelizeOptions) |
348 | } | 375 | } |
349 | 376 | ||
350 | { | 377 | { |
351 | // Update captions | 378 | // Update captions |
352 | await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(video.id, t) | 379 | await VideoCaptionModel.deleteAllCaptionsOfRemoteVideo(videoUpdated.id, t) |
353 | 380 | ||
354 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { | 381 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { |
355 | return VideoCaptionModel.insertOrReplaceLanguage(video.id, c.identifier, t) | 382 | return VideoCaptionModel.insertOrReplaceLanguage(videoUpdated.id, c.identifier, t) |
356 | }) | 383 | }) |
357 | video.VideoCaptions = await Promise.all(videoCaptionsPromises) | 384 | await Promise.all(videoCaptionsPromises) |
358 | } | 385 | } |
386 | |||
387 | return videoUpdated | ||
359 | }) | 388 | }) |
360 | 389 | ||
361 | await autoBlacklistVideoIfNeeded({ | 390 | await autoBlacklistVideoIfNeeded({ |
362 | video, | 391 | video: videoUpdated, |
363 | user: undefined, | 392 | user: undefined, |
364 | isRemote: true, | 393 | isRemote: true, |
365 | isNew: false, | 394 | isNew: false, |
366 | transaction: undefined | 395 | transaction: undefined |
367 | }) | 396 | }) |
368 | 397 | ||
369 | if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(video) // Notify our users? | 398 | if (wasPrivateVideo || wasUnlistedVideo) Notifier.Instance.notifyOnNewVideoIfNeeded(videoUpdated) // Notify our users? |
370 | 399 | ||
371 | logger.info('Remote video with uuid %s updated', videoObject.uuid) | 400 | logger.info('Remote video with uuid %s updated', videoObject.uuid) |
401 | |||
402 | return videoUpdated | ||
372 | } catch (err) { | 403 | } catch (err) { |
373 | if (video !== undefined && videoFieldsSave !== undefined) { | 404 | if (video !== undefined && videoFieldsSave !== undefined) { |
374 | resetSequelizeInstance(video, videoFieldsSave) | 405 | resetSequelizeInstance(video, videoFieldsSave) |
@@ -381,15 +412,15 @@ async function updateVideoFromAP (options: { | |||
381 | } | 412 | } |
382 | 413 | ||
383 | async function refreshVideoIfNeeded (options: { | 414 | async function refreshVideoIfNeeded (options: { |
384 | video: VideoModel, | 415 | video: MVideoThumbnail, |
385 | fetchedType: VideoFetchByUrlType, | 416 | fetchedType: VideoFetchByUrlType, |
386 | syncParam: SyncParam | 417 | syncParam: SyncParam |
387 | }): Promise<VideoModel> { | 418 | }): Promise<MVideoThumbnail> { |
388 | if (!options.video.isOutdated()) return options.video | 419 | if (!options.video.isOutdated()) return options.video |
389 | 420 | ||
390 | // We need more attributes if the argument video was fetched with not enough joints | 421 | // We need more attributes if the argument video was fetched with not enough joints |
391 | const video = options.fetchedType === 'all' | 422 | const video = options.fetchedType === 'all' |
392 | ? options.video | 423 | ? options.video as MVideoAccountLightBlacklistAllFiles |
393 | : await VideoModel.loadByUrlAndPopulateAccount(options.video.url) | 424 | : await VideoModel.loadByUrlAndPopulateAccount(options.video.url) |
394 | 425 | ||
395 | try { | 426 | try { |
@@ -410,12 +441,11 @@ async function refreshVideoIfNeeded (options: { | |||
410 | } | 441 | } |
411 | 442 | ||
412 | const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) | 443 | const channelActor = await getOrCreateVideoChannelFromVideoObject(videoObject) |
413 | const account = await AccountModel.load(channelActor.VideoChannel.accountId) | ||
414 | 444 | ||
415 | const updateOptions = { | 445 | const updateOptions = { |
416 | video, | 446 | video, |
417 | videoObject, | 447 | videoObject, |
418 | account, | 448 | account: channelActor.VideoChannel.Account, |
419 | channel: channelActor.VideoChannel | 449 | channel: channelActor.VideoChannel |
420 | } | 450 | } |
421 | await retryTransactionWrapper(updateVideoFromAP, updateOptions) | 451 | await retryTransactionWrapper(updateVideoFromAP, updateOptions) |
@@ -467,15 +497,15 @@ function isAPPlaylistSegmentHashesUrlObject (tag: any): tag is ActivityPlaylistS | |||
467 | return tag.name === 'sha256' && tag.type === 'Link' && urlMediaType === 'application/json' | 497 | return tag.name === 'sha256' && tag.type === 'Link' && urlMediaType === 'application/json' |
468 | } | 498 | } |
469 | 499 | ||
470 | async function createVideo (videoObject: VideoTorrentObject, channelActor: ActorModel, waitThumbnail = false) { | 500 | async function createVideo (videoObject: VideoTorrentObject, channel: MChannelAccountLight, waitThumbnail = false) { |
471 | logger.debug('Adding remote video %s.', videoObject.id) | 501 | logger.debug('Adding remote video %s.', videoObject.id) |
472 | 502 | ||
473 | const videoData = await videoActivityObjectToDBAttributes(channelActor.VideoChannel, videoObject, videoObject.to) | 503 | const videoData = await videoActivityObjectToDBAttributes(channel, videoObject, videoObject.to) |
474 | const video = VideoModel.build(videoData) | 504 | const video = VideoModel.build(videoData) as MVideoThumbnail |
475 | 505 | ||
476 | const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) | 506 | const promiseThumbnail = createVideoMiniatureFromUrl(videoObject.icon.url, video, ThumbnailType.MINIATURE) |
477 | 507 | ||
478 | let thumbnailModel: ThumbnailModel | 508 | let thumbnailModel: MThumbnail |
479 | if (waitThumbnail === true) { | 509 | if (waitThumbnail === true) { |
480 | thumbnailModel = await promiseThumbnail | 510 | thumbnailModel = await promiseThumbnail |
481 | } | 511 | } |
@@ -483,8 +513,8 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor | |||
483 | const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => { | 513 | const { autoBlacklisted, videoCreated } = await sequelizeTypescript.transaction(async t => { |
484 | const sequelizeOptions = { transaction: t } | 514 | const sequelizeOptions = { transaction: t } |
485 | 515 | ||
486 | const videoCreated = await video.save(sequelizeOptions) | 516 | const videoCreated = await video.save(sequelizeOptions) as MVideoFullLight |
487 | videoCreated.VideoChannel = channelActor.VideoChannel | 517 | videoCreated.VideoChannel = channel |
488 | 518 | ||
489 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) | 519 | if (thumbnailModel) await videoCreated.addAndSaveThumbnail(thumbnailModel, t) |
490 | 520 | ||
@@ -517,15 +547,14 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor | |||
517 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { | 547 | const videoCaptionsPromises = videoObject.subtitleLanguage.map(c => { |
518 | return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, t) | 548 | return VideoCaptionModel.insertOrReplaceLanguage(videoCreated.id, c.identifier, t) |
519 | }) | 549 | }) |
520 | const captions = await Promise.all(videoCaptionsPromises) | 550 | await Promise.all(videoCaptionsPromises) |
521 | 551 | ||
522 | video.VideoFiles = videoFiles | 552 | videoCreated.VideoFiles = videoFiles |
523 | video.VideoStreamingPlaylists = streamingPlaylists | 553 | videoCreated.VideoStreamingPlaylists = streamingPlaylists |
524 | video.Tags = tagInstances | 554 | videoCreated.Tags = tagInstances |
525 | video.VideoCaptions = captions | ||
526 | 555 | ||
527 | const autoBlacklisted = await autoBlacklistVideoIfNeeded({ | 556 | const autoBlacklisted = await autoBlacklistVideoIfNeeded({ |
528 | video, | 557 | video: videoCreated, |
529 | user: undefined, | 558 | user: undefined, |
530 | isRemote: true, | 559 | isRemote: true, |
531 | isNew: true, | 560 | isNew: true, |
@@ -548,11 +577,7 @@ async function createVideo (videoObject: VideoTorrentObject, channelActor: Actor | |||
548 | return { autoBlacklisted, videoCreated } | 577 | return { autoBlacklisted, videoCreated } |
549 | } | 578 | } |
550 | 579 | ||
551 | async function videoActivityObjectToDBAttributes ( | 580 | async function videoActivityObjectToDBAttributes (videoChannel: MChannelId, videoObject: VideoTorrentObject, to: string[] = []) { |
552 | videoChannel: VideoChannelModelId, | ||
553 | videoObject: VideoTorrentObject, | ||
554 | to: string[] = [] | ||
555 | ) { | ||
556 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED | 581 | const privacy = to.indexOf(ACTIVITY_PUB.PUBLIC) !== -1 ? VideoPrivacy.PUBLIC : VideoPrivacy.UNLISTED |
557 | const duration = videoObject.duration.replace(/[^\d]+/, '') | 582 | const duration = videoObject.duration.replace(/[^\d]+/, '') |
558 | 583 | ||
@@ -603,7 +628,7 @@ async function videoActivityObjectToDBAttributes ( | |||
603 | } | 628 | } |
604 | } | 629 | } |
605 | 630 | ||
606 | function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject) { | 631 | function videoFileActivityUrlToDBAttributes (video: MVideo, videoObject: VideoTorrentObject) { |
607 | const fileUrls = videoObject.url.filter(u => isAPVideoUrlObject(u)) as ActivityVideoUrlObject[] | 632 | const fileUrls = videoObject.url.filter(u => isAPVideoUrlObject(u)) as ActivityVideoUrlObject[] |
608 | 633 | ||
609 | if (fileUrls.length === 0) { | 634 | if (fileUrls.length === 0) { |
@@ -641,7 +666,7 @@ function videoFileActivityUrlToDBAttributes (video: VideoModel, videoObject: Vid | |||
641 | return attributes | 666 | return attributes |
642 | } | 667 | } |
643 | 668 | ||
644 | function streamingPlaylistActivityUrlToDBAttributes (video: VideoModel, videoObject: VideoTorrentObject, videoFiles: VideoFileModel[]) { | 669 | function streamingPlaylistActivityUrlToDBAttributes (video: MVideoId, videoObject: VideoTorrentObject, videoFiles: MVideoFile[]) { |
645 | const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[] | 670 | const playlistUrls = videoObject.url.filter(u => isAPStreamingPlaylistUrlObject(u)) as ActivityPlaylistUrlObject[] |
646 | if (playlistUrls.length === 0) return [] | 671 | if (playlistUrls.length === 0) return [] |
647 | 672 | ||
diff --git a/server/lib/avatar.ts b/server/lib/avatar.ts index 1b38e6cb5..ad4cdd3ab 100644 --- a/server/lib/avatar.ts +++ b/server/lib/avatar.ts | |||
@@ -3,8 +3,6 @@ import { sendUpdateActor } from './activitypub/send' | |||
3 | import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants' | 3 | import { AVATARS_SIZE, LRU_CACHE, QUEUE_CONCURRENCY } from '../initializers/constants' |
4 | import { updateActorAvatarInstance } from './activitypub' | 4 | import { updateActorAvatarInstance } from './activitypub' |
5 | import { processImage } from '../helpers/image-utils' | 5 | import { processImage } from '../helpers/image-utils' |
6 | import { AccountModel } from '../models/account/account' | ||
7 | import { VideoChannelModel } from '../models/video/video-channel' | ||
8 | import { extname, join } from 'path' | 6 | import { extname, join } from 'path' |
9 | import { retryTransactionWrapper } from '../helpers/database-utils' | 7 | import { retryTransactionWrapper } from '../helpers/database-utils' |
10 | import * as uuidv4 from 'uuid/v4' | 8 | import * as uuidv4 from 'uuid/v4' |
@@ -13,8 +11,12 @@ import { sequelizeTypescript } from '../initializers/database' | |||
13 | import * as LRUCache from 'lru-cache' | 11 | import * as LRUCache from 'lru-cache' |
14 | import { queue } from 'async' | 12 | import { queue } from 'async' |
15 | import { downloadImage } from '../helpers/requests' | 13 | import { downloadImage } from '../helpers/requests' |
14 | import { MAccountDefault, MChannelDefault } from '../typings/models' | ||
16 | 15 | ||
17 | async function updateActorAvatarFile (avatarPhysicalFile: Express.Multer.File, accountOrChannel: AccountModel | VideoChannelModel) { | 16 | async function updateActorAvatarFile ( |
17 | avatarPhysicalFile: Express.Multer.File, | ||
18 | accountOrChannel: MAccountDefault | MChannelDefault | ||
19 | ) { | ||
18 | const extension = extname(avatarPhysicalFile.filename) | 20 | const extension = extname(avatarPhysicalFile.filename) |
19 | const avatarName = uuidv4() + extension | 21 | const avatarName = uuidv4() + extension |
20 | const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) | 22 | const destination = join(CONFIG.STORAGE.AVATARS_DIR, avatarName) |
diff --git a/server/lib/blocklist.ts b/server/lib/blocklist.ts index 1633e500c..28c69b46e 100644 --- a/server/lib/blocklist.ts +++ b/server/lib/blocklist.ts | |||
@@ -1,6 +1,7 @@ | |||
1 | import { sequelizeTypescript } from '../initializers' | 1 | import { sequelizeTypescript } from '../initializers' |
2 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | 2 | import { AccountBlocklistModel } from '../models/account/account-blocklist' |
3 | import { ServerBlocklistModel } from '../models/server/server-blocklist' | 3 | import { ServerBlocklistModel } from '../models/server/server-blocklist' |
4 | import { MAccountBlocklist, MServerBlocklist } from '@server/typings/models' | ||
4 | 5 | ||
5 | function addAccountInBlocklist (byAccountId: number, targetAccountId: number) { | 6 | function addAccountInBlocklist (byAccountId: number, targetAccountId: number) { |
6 | return sequelizeTypescript.transaction(async t => { | 7 | return sequelizeTypescript.transaction(async t => { |
@@ -20,13 +21,13 @@ function addServerInBlocklist (byAccountId: number, targetServerId: number) { | |||
20 | }) | 21 | }) |
21 | } | 22 | } |
22 | 23 | ||
23 | function removeAccountFromBlocklist (accountBlock: AccountBlocklistModel) { | 24 | function removeAccountFromBlocklist (accountBlock: MAccountBlocklist) { |
24 | return sequelizeTypescript.transaction(async t => { | 25 | return sequelizeTypescript.transaction(async t => { |
25 | return accountBlock.destroy({ transaction: t }) | 26 | return accountBlock.destroy({ transaction: t }) |
26 | }) | 27 | }) |
27 | } | 28 | } |
28 | 29 | ||
29 | function removeServerFromBlocklist (serverBlock: ServerBlocklistModel) { | 30 | function removeServerFromBlocklist (serverBlock: MServerBlocklist) { |
30 | return sequelizeTypescript.transaction(async t => { | 31 | return sequelizeTypescript.transaction(async t => { |
31 | return serverBlock.destroy({ transaction: t }) | 32 | return serverBlock.destroy({ transaction: t }) |
32 | }) | 33 | }) |
diff --git a/server/lib/client-html.ts b/server/lib/client-html.ts index 8841dd2ac..a1f4ae858 100644 --- a/server/lib/client-html.ts +++ b/server/lib/client-html.ts | |||
@@ -13,6 +13,7 @@ import { VideoChannelModel } from '../models/video/video-channel' | |||
13 | import * as Bluebird from 'bluebird' | 13 | import * as Bluebird from 'bluebird' |
14 | import { CONFIG } from '../initializers/config' | 14 | import { CONFIG } from '../initializers/config' |
15 | import { logger } from '../helpers/logger' | 15 | import { logger } from '../helpers/logger' |
16 | import { MAccountActor, MChannelActor, MVideo } from '../typings/models' | ||
16 | 17 | ||
17 | export class ClientHtml { | 18 | export class ClientHtml { |
18 | 19 | ||
@@ -41,11 +42,11 @@ export class ClientHtml { | |||
41 | 42 | ||
42 | const [ html, video ] = await Promise.all([ | 43 | const [ html, video ] = await Promise.all([ |
43 | ClientHtml.getIndexHTML(req, res), | 44 | ClientHtml.getIndexHTML(req, res), |
44 | VideoModel.load(videoId) | 45 | VideoModel.loadWithBlacklist(videoId) |
45 | ]) | 46 | ]) |
46 | 47 | ||
47 | // Let Angular application handle errors | 48 | // Let Angular application handle errors |
48 | if (!video || video.privacy === VideoPrivacy.PRIVATE) { | 49 | if (!video || video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) { |
49 | return ClientHtml.getIndexHTML(req, res) | 50 | return ClientHtml.getIndexHTML(req, res) |
50 | } | 51 | } |
51 | 52 | ||
@@ -65,7 +66,7 @@ export class ClientHtml { | |||
65 | } | 66 | } |
66 | 67 | ||
67 | private static async getAccountOrChannelHTMLPage ( | 68 | private static async getAccountOrChannelHTMLPage ( |
68 | loader: () => Bluebird<AccountModel | VideoChannelModel>, | 69 | loader: () => Bluebird<MAccountActor | MChannelActor>, |
69 | req: express.Request, | 70 | req: express.Request, |
70 | res: express.Response | 71 | res: express.Response |
71 | ) { | 72 | ) { |
@@ -157,7 +158,7 @@ export class ClientHtml { | |||
157 | return htmlStringPage.replace('</head>', linkTag + '</head>') | 158 | return htmlStringPage.replace('</head>', linkTag + '</head>') |
158 | } | 159 | } |
159 | 160 | ||
160 | private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) { | 161 | private static addVideoOpenGraphAndOEmbedTags (htmlStringPage: string, video: MVideo) { |
161 | const previewUrl = WEBSERVER.URL + video.getPreviewStaticPath() | 162 | const previewUrl = WEBSERVER.URL + video.getPreviewStaticPath() |
162 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 163 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
163 | 164 | ||
@@ -236,7 +237,7 @@ export class ClientHtml { | |||
236 | return this.addOpenGraphAndOEmbedTags(htmlStringPage, tagsString) | 237 | return this.addOpenGraphAndOEmbedTags(htmlStringPage, tagsString) |
237 | } | 238 | } |
238 | 239 | ||
239 | private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: AccountModel | VideoChannelModel) { | 240 | private static addAccountOrChannelMetaTags (htmlStringPage: string, entity: MAccountActor | MChannelActor) { |
240 | // SEO, use origin account or channel URL | 241 | // SEO, use origin account or channel URL |
241 | const metaTags = `<link rel="canonical" href="${entity.Actor.url}" />` | 242 | const metaTags = `<link rel="canonical" href="${entity.Actor.url}" />` |
242 | 243 | ||
diff --git a/server/lib/emailer.ts b/server/lib/emailer.ts index 10e7d0479..bd3d4f252 100644 --- a/server/lib/emailer.ts +++ b/server/lib/emailer.ts | |||
@@ -2,17 +2,20 @@ import { createTransport, Transporter } from 'nodemailer' | |||
2 | import { isTestInstance } from '../helpers/core-utils' | 2 | import { isTestInstance } from '../helpers/core-utils' |
3 | import { bunyanLogger, logger } from '../helpers/logger' | 3 | import { bunyanLogger, logger } from '../helpers/logger' |
4 | import { CONFIG } from '../initializers/config' | 4 | import { CONFIG } from '../initializers/config' |
5 | import { UserModel } from '../models/account/user' | ||
6 | import { VideoModel } from '../models/video/video' | ||
7 | import { JobQueue } from './job-queue' | 5 | import { JobQueue } from './job-queue' |
8 | import { EmailPayload } from './job-queue/handlers/email' | 6 | import { EmailPayload } from './job-queue/handlers/email' |
9 | import { readFileSync } from 'fs-extra' | 7 | import { readFileSync } from 'fs-extra' |
10 | import { VideoCommentModel } from '../models/video/video-comment' | ||
11 | import { VideoAbuseModel } from '../models/video/video-abuse' | ||
12 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | ||
13 | import { VideoImportModel } from '../models/video/video-import' | ||
14 | import { ActorFollowModel } from '../models/activitypub/actor-follow' | ||
15 | import { WEBSERVER } from '../initializers/constants' | 8 | import { WEBSERVER } from '../initializers/constants' |
9 | import { | ||
10 | MCommentOwnerVideo, | ||
11 | MVideo, | ||
12 | MVideoAbuseVideo, | ||
13 | MVideoAccountLight, | ||
14 | MVideoBlacklistLightVideo, | ||
15 | MVideoBlacklistVideo | ||
16 | } from '../typings/models/video' | ||
17 | import { MActorFollowActors, MActorFollowFull, MUser } from '../typings/models' | ||
18 | import { MVideoImport, MVideoImportVideo } from '@server/typings/models/video/video-import' | ||
16 | 19 | ||
17 | type SendEmailOptions = { | 20 | type SendEmailOptions = { |
18 | to: string[] | 21 | to: string[] |
@@ -90,7 +93,7 @@ class Emailer { | |||
90 | } | 93 | } |
91 | } | 94 | } |
92 | 95 | ||
93 | addNewVideoFromSubscriberNotification (to: string[], video: VideoModel) { | 96 | addNewVideoFromSubscriberNotification (to: string[], video: MVideoAccountLight) { |
94 | const channelName = video.VideoChannel.getDisplayName() | 97 | const channelName = video.VideoChannel.getDisplayName() |
95 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 98 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
96 | 99 | ||
@@ -111,7 +114,7 @@ class Emailer { | |||
111 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 114 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
112 | } | 115 | } |
113 | 116 | ||
114 | addNewFollowNotification (to: string[], actorFollow: ActorFollowModel, followType: 'account' | 'channel') { | 117 | addNewFollowNotification (to: string[], actorFollow: MActorFollowFull, followType: 'account' | 'channel') { |
115 | const followerName = actorFollow.ActorFollower.Account.getDisplayName() | 118 | const followerName = actorFollow.ActorFollower.Account.getDisplayName() |
116 | const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName() | 119 | const followingName = (actorFollow.ActorFollowing.VideoChannel || actorFollow.ActorFollowing.Account).getDisplayName() |
117 | 120 | ||
@@ -130,7 +133,7 @@ class Emailer { | |||
130 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 133 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
131 | } | 134 | } |
132 | 135 | ||
133 | addNewInstanceFollowerNotification (to: string[], actorFollow: ActorFollowModel) { | 136 | addNewInstanceFollowerNotification (to: string[], actorFollow: MActorFollowActors) { |
134 | const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' | 137 | const awaitingApproval = actorFollow.state === 'pending' ? ' awaiting manual approval.' : '' |
135 | 138 | ||
136 | const text = `Hi dear admin,\n\n` + | 139 | const text = `Hi dear admin,\n\n` + |
@@ -148,7 +151,23 @@ class Emailer { | |||
148 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 151 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
149 | } | 152 | } |
150 | 153 | ||
151 | myVideoPublishedNotification (to: string[], video: VideoModel) { | 154 | addAutoInstanceFollowingNotification (to: string[], actorFollow: MActorFollowActors) { |
155 | const text = `Hi dear admin,\n\n` + | ||
156 | `Your instance automatically followed a new instance: ${actorFollow.ActorFollowing.url}` + | ||
157 | `\n\n` + | ||
158 | `Cheers,\n` + | ||
159 | `${CONFIG.EMAIL.BODY.SIGNATURE}` | ||
160 | |||
161 | const emailPayload: EmailPayload = { | ||
162 | to, | ||
163 | subject: CONFIG.EMAIL.SUBJECT.PREFIX + 'Auto instance following', | ||
164 | text | ||
165 | } | ||
166 | |||
167 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | ||
168 | } | ||
169 | |||
170 | myVideoPublishedNotification (to: string[], video: MVideo) { | ||
152 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 171 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
153 | 172 | ||
154 | const text = `Hi dear user,\n\n` + | 173 | const text = `Hi dear user,\n\n` + |
@@ -168,7 +187,7 @@ class Emailer { | |||
168 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 187 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
169 | } | 188 | } |
170 | 189 | ||
171 | myVideoImportSuccessNotification (to: string[], videoImport: VideoImportModel) { | 190 | myVideoImportSuccessNotification (to: string[], videoImport: MVideoImportVideo) { |
172 | const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() | 191 | const videoUrl = WEBSERVER.URL + videoImport.Video.getWatchStaticPath() |
173 | 192 | ||
174 | const text = `Hi dear user,\n\n` + | 193 | const text = `Hi dear user,\n\n` + |
@@ -188,7 +207,7 @@ class Emailer { | |||
188 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 207 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
189 | } | 208 | } |
190 | 209 | ||
191 | myVideoImportErrorNotification (to: string[], videoImport: VideoImportModel) { | 210 | myVideoImportErrorNotification (to: string[], videoImport: MVideoImport) { |
192 | const importUrl = WEBSERVER.URL + '/my-account/video-imports' | 211 | const importUrl = WEBSERVER.URL + '/my-account/video-imports' |
193 | 212 | ||
194 | const text = `Hi dear user,\n\n` + | 213 | const text = `Hi dear user,\n\n` + |
@@ -208,7 +227,7 @@ class Emailer { | |||
208 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 227 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
209 | } | 228 | } |
210 | 229 | ||
211 | addNewCommentOnMyVideoNotification (to: string[], comment: VideoCommentModel) { | 230 | addNewCommentOnMyVideoNotification (to: string[], comment: MCommentOwnerVideo) { |
212 | const accountName = comment.Account.getDisplayName() | 231 | const accountName = comment.Account.getDisplayName() |
213 | const video = comment.Video | 232 | const video = comment.Video |
214 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() | 233 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() |
@@ -230,7 +249,7 @@ class Emailer { | |||
230 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 249 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
231 | } | 250 | } |
232 | 251 | ||
233 | addNewCommentMentionNotification (to: string[], comment: VideoCommentModel) { | 252 | addNewCommentMentionNotification (to: string[], comment: MCommentOwnerVideo) { |
234 | const accountName = comment.Account.getDisplayName() | 253 | const accountName = comment.Account.getDisplayName() |
235 | const video = comment.Video | 254 | const video = comment.Video |
236 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() | 255 | const commentUrl = WEBSERVER.URL + comment.getCommentStaticPath() |
@@ -252,7 +271,7 @@ class Emailer { | |||
252 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 271 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
253 | } | 272 | } |
254 | 273 | ||
255 | addVideoAbuseModeratorsNotification (to: string[], videoAbuse: VideoAbuseModel) { | 274 | addVideoAbuseModeratorsNotification (to: string[], videoAbuse: MVideoAbuseVideo) { |
256 | const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() | 275 | const videoUrl = WEBSERVER.URL + videoAbuse.Video.getWatchStaticPath() |
257 | 276 | ||
258 | const text = `Hi,\n\n` + | 277 | const text = `Hi,\n\n` + |
@@ -269,9 +288,9 @@ class Emailer { | |||
269 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 288 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
270 | } | 289 | } |
271 | 290 | ||
272 | addVideoAutoBlacklistModeratorsNotification (to: string[], video: VideoModel) { | 291 | addVideoAutoBlacklistModeratorsNotification (to: string[], videoBlacklist: MVideoBlacklistLightVideo) { |
273 | const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' | 292 | const VIDEO_AUTO_BLACKLIST_URL = WEBSERVER.URL + '/admin/moderation/video-auto-blacklist/list' |
274 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 293 | const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() |
275 | 294 | ||
276 | const text = `Hi,\n\n` + | 295 | const text = `Hi,\n\n` + |
277 | `A recently added video was auto-blacklisted and requires moderator review before publishing.` + | 296 | `A recently added video was auto-blacklisted and requires moderator review before publishing.` + |
@@ -292,7 +311,7 @@ class Emailer { | |||
292 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 311 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
293 | } | 312 | } |
294 | 313 | ||
295 | addNewUserRegistrationNotification (to: string[], user: UserModel) { | 314 | addNewUserRegistrationNotification (to: string[], user: MUser) { |
296 | const text = `Hi,\n\n` + | 315 | const text = `Hi,\n\n` + |
297 | `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + | 316 | `User ${user.username} just registered on ${WEBSERVER.HOST} PeerTube instance.\n\n` + |
298 | `Cheers,\n` + | 317 | `Cheers,\n` + |
@@ -307,7 +326,7 @@ class Emailer { | |||
307 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 326 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
308 | } | 327 | } |
309 | 328 | ||
310 | addVideoBlacklistNotification (to: string[], videoBlacklist: VideoBlacklistModel) { | 329 | addVideoBlacklistNotification (to: string[], videoBlacklist: MVideoBlacklistVideo) { |
311 | const videoName = videoBlacklist.Video.name | 330 | const videoName = videoBlacklist.Video.name |
312 | const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() | 331 | const videoUrl = WEBSERVER.URL + videoBlacklist.Video.getWatchStaticPath() |
313 | 332 | ||
@@ -329,7 +348,7 @@ class Emailer { | |||
329 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 348 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
330 | } | 349 | } |
331 | 350 | ||
332 | addVideoUnblacklistNotification (to: string[], video: VideoModel) { | 351 | addVideoUnblacklistNotification (to: string[], video: MVideo) { |
333 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() | 352 | const videoUrl = WEBSERVER.URL + video.getWatchStaticPath() |
334 | 353 | ||
335 | const text = 'Hi,\n\n' + | 354 | const text = 'Hi,\n\n' + |
@@ -381,7 +400,7 @@ class Emailer { | |||
381 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) | 400 | return JobQueue.Instance.createJob({ type: 'email', payload: emailPayload }) |
382 | } | 401 | } |
383 | 402 | ||
384 | addUserBlockJob (user: UserModel, blocked: boolean, reason?: string) { | 403 | addUserBlockJob (user: MUser, blocked: boolean, reason?: string) { |
385 | const reasonString = reason ? ` for the following reason: ${reason}` : '' | 404 | const reasonString = reason ? ` for the following reason: ${reason}` : '' |
386 | const blockedWord = blocked ? 'blocked' : 'unblocked' | 405 | const blockedWord = blocked ? 'blocked' : 'unblocked' |
387 | const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.` | 406 | const blockedString = `Your account ${user.username} on ${WEBSERVER.HOST} has been ${blockedWord}${reasonString}.` |
diff --git a/server/lib/hls.ts b/server/lib/hls.ts index 98da4dcd8..05136c21c 100644 --- a/server/lib/hls.ts +++ b/server/lib/hls.ts | |||
@@ -1,4 +1,3 @@ | |||
1 | import { VideoModel } from '../models/video/video' | ||
2 | import { basename, dirname, join } from 'path' | 1 | import { basename, dirname, join } from 'path' |
3 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../initializers/constants' | 2 | import { HLS_STREAMING_PLAYLIST_DIRECTORY, P2P_MEDIA_LOADER_PEER_VERSION } from '../initializers/constants' |
4 | import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra' | 3 | import { close, ensureDir, move, open, outputJSON, pathExists, read, readFile, remove, writeFile } from 'fs-extra' |
@@ -12,6 +11,7 @@ import { flatten, uniq } from 'lodash' | |||
12 | import { VideoFileModel } from '../models/video/video-file' | 11 | import { VideoFileModel } from '../models/video/video-file' |
13 | import { CONFIG } from '../initializers/config' | 12 | import { CONFIG } from '../initializers/config' |
14 | import { sequelizeTypescript } from '../initializers/database' | 13 | import { sequelizeTypescript } from '../initializers/database' |
14 | import { MVideoWithFile } from '@server/typings/models' | ||
15 | 15 | ||
16 | async function updateStreamingPlaylistsInfohashesIfNeeded () { | 16 | async function updateStreamingPlaylistsInfohashesIfNeeded () { |
17 | const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion() | 17 | const playlistsToUpdate = await VideoStreamingPlaylistModel.listByIncorrectPeerVersion() |
@@ -28,7 +28,7 @@ async function updateStreamingPlaylistsInfohashesIfNeeded () { | |||
28 | } | 28 | } |
29 | } | 29 | } |
30 | 30 | ||
31 | async function updateMasterHLSPlaylist (video: VideoModel) { | 31 | async function updateMasterHLSPlaylist (video: MVideoWithFile) { |
32 | const directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) | 32 | const directory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) |
33 | const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ] | 33 | const masterPlaylists: string[] = [ '#EXTM3U', '#EXT-X-VERSION:3' ] |
34 | const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename()) | 34 | const masterPlaylistPath = join(directory, VideoStreamingPlaylistModel.getMasterHlsPlaylistFilename()) |
@@ -55,7 +55,7 @@ async function updateMasterHLSPlaylist (video: VideoModel) { | |||
55 | await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n') | 55 | await writeFile(masterPlaylistPath, masterPlaylists.join('\n') + '\n') |
56 | } | 56 | } |
57 | 57 | ||
58 | async function updateSha256Segments (video: VideoModel) { | 58 | async function updateSha256Segments (video: MVideoWithFile) { |
59 | const json: { [filename: string]: { [range: string]: string } } = {} | 59 | const json: { [filename: string]: { [range: string]: string } } = {} |
60 | 60 | ||
61 | const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) | 61 | const playlistDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) |
diff --git a/server/lib/job-queue/handlers/activitypub-follow.ts b/server/lib/job-queue/handlers/activitypub-follow.ts index 4ae66cd01..af7c8a838 100644 --- a/server/lib/job-queue/handlers/activitypub-follow.ts +++ b/server/lib/job-queue/handlers/activitypub-follow.ts | |||
@@ -10,11 +10,13 @@ import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | |||
10 | import { ActorModel } from '../../../models/activitypub/actor' | 10 | import { ActorModel } from '../../../models/activitypub/actor' |
11 | import { Notifier } from '../../notifier' | 11 | import { Notifier } from '../../notifier' |
12 | import { sequelizeTypescript } from '../../../initializers/database' | 12 | import { sequelizeTypescript } from '../../../initializers/database' |
13 | import { MActor, MActorFollowActors, MActorFull } from '../../../typings/models' | ||
13 | 14 | ||
14 | export type ActivitypubFollowPayload = { | 15 | export type ActivitypubFollowPayload = { |
15 | followerActorId: number | 16 | followerActorId: number |
16 | name: string | 17 | name: string |
17 | host: string | 18 | host: string |
19 | isAutoFollow?: boolean | ||
18 | } | 20 | } |
19 | 21 | ||
20 | async function processActivityPubFollow (job: Bull.Job) { | 22 | async function processActivityPubFollow (job: Bull.Job) { |
@@ -23,18 +25,18 @@ async function processActivityPubFollow (job: Bull.Job) { | |||
23 | 25 | ||
24 | logger.info('Processing ActivityPub follow in job %d.', job.id) | 26 | logger.info('Processing ActivityPub follow in job %d.', job.id) |
25 | 27 | ||
26 | let targetActor: ActorModel | 28 | let targetActor: MActorFull |
27 | if (!host || host === WEBSERVER.HOST) { | 29 | if (!host || host === WEBSERVER.HOST) { |
28 | targetActor = await ActorModel.loadLocalByName(payload.name) | 30 | targetActor = await ActorModel.loadLocalByName(payload.name) |
29 | } else { | 31 | } else { |
30 | const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP) | 32 | const sanitizedHost = sanitizeHost(host, REMOTE_SCHEME.HTTP) |
31 | const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost) | 33 | const actorUrl = await loadActorUrlOrGetFromWebfinger(payload.name + '@' + sanitizedHost) |
32 | targetActor = await getOrCreateActorAndServerAndModel(actorUrl) | 34 | targetActor = await getOrCreateActorAndServerAndModel(actorUrl, 'all') |
33 | } | 35 | } |
34 | 36 | ||
35 | const fromActor = await ActorModel.load(payload.followerActorId) | 37 | const fromActor = await ActorModel.load(payload.followerActorId) |
36 | 38 | ||
37 | return retryTransactionWrapper(follow, fromActor, targetActor) | 39 | return retryTransactionWrapper(follow, fromActor, targetActor, payload.isAutoFollow) |
38 | } | 40 | } |
39 | // --------------------------------------------------------------------------- | 41 | // --------------------------------------------------------------------------- |
40 | 42 | ||
@@ -44,7 +46,7 @@ export { | |||
44 | 46 | ||
45 | // --------------------------------------------------------------------------- | 47 | // --------------------------------------------------------------------------- |
46 | 48 | ||
47 | async function follow (fromActor: ActorModel, targetActor: ActorModel) { | 49 | async function follow (fromActor: MActor, targetActor: MActorFull, isAutoFollow = false) { |
48 | if (fromActor.id === targetActor.id) { | 50 | if (fromActor.id === targetActor.id) { |
49 | throw new Error('Follower is the same than target actor.') | 51 | throw new Error('Follower is the same than target actor.') |
50 | } | 52 | } |
@@ -53,7 +55,7 @@ async function follow (fromActor: ActorModel, targetActor: ActorModel) { | |||
53 | const state = !fromActor.serverId && !targetActor.serverId ? 'accepted' : 'pending' | 55 | const state = !fromActor.serverId && !targetActor.serverId ? 'accepted' : 'pending' |
54 | 56 | ||
55 | const actorFollow = await sequelizeTypescript.transaction(async t => { | 57 | const actorFollow = await sequelizeTypescript.transaction(async t => { |
56 | const [ actorFollow ] = await ActorFollowModel.findOrCreate({ | 58 | const [ actorFollow ] = await ActorFollowModel.findOrCreate<MActorFollowActors>({ |
57 | where: { | 59 | where: { |
58 | actorId: fromActor.id, | 60 | actorId: fromActor.id, |
59 | targetActorId: targetActor.id | 61 | targetActorId: targetActor.id |
@@ -74,5 +76,15 @@ async function follow (fromActor: ActorModel, targetActor: ActorModel) { | |||
74 | return actorFollow | 76 | return actorFollow |
75 | }) | 77 | }) |
76 | 78 | ||
77 | if (actorFollow.state === 'accepted') Notifier.Instance.notifyOfNewUserFollow(actorFollow) | 79 | const followerFull = await ActorModel.loadFull(fromActor.id) |
80 | |||
81 | const actorFollowFull = Object.assign(actorFollow, { | ||
82 | ActorFollowing: targetActor, | ||
83 | ActorFollower: followerFull | ||
84 | }) | ||
85 | |||
86 | if (actorFollow.state === 'accepted') Notifier.Instance.notifyOfNewUserFollow(actorFollowFull) | ||
87 | if (isAutoFollow === true) Notifier.Instance.notifyOfAutoInstanceFollowing(actorFollowFull) | ||
88 | |||
89 | return actorFollow | ||
78 | } | 90 | } |
diff --git a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts index c3f59dc77..0182c5169 100644 --- a/server/lib/job-queue/handlers/activitypub-http-fetcher.ts +++ b/server/lib/job-queue/handlers/activitypub-http-fetcher.ts | |||
@@ -11,6 +11,7 @@ import { AccountModel } from '../../../models/account/account' | |||
11 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' | 11 | import { AccountVideoRateModel } from '../../../models/account/account-video-rate' |
12 | import { VideoShareModel } from '../../../models/video/video-share' | 12 | import { VideoShareModel } from '../../../models/video/video-share' |
13 | import { VideoCommentModel } from '../../../models/video/video-comment' | 13 | import { VideoCommentModel } from '../../../models/video/video-comment' |
14 | import { MAccountDefault, MVideoFullLight } from '../../../typings/models' | ||
14 | 15 | ||
15 | type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists' | 16 | type FetchType = 'activity' | 'video-likes' | 'video-dislikes' | 'video-shares' | 'video-comments' | 'account-playlists' |
16 | 17 | ||
@@ -26,10 +27,10 @@ async function processActivityPubHttpFetcher (job: Bull.Job) { | |||
26 | 27 | ||
27 | const payload = job.data as ActivitypubHttpFetcherPayload | 28 | const payload = job.data as ActivitypubHttpFetcherPayload |
28 | 29 | ||
29 | let video: VideoModel | 30 | let video: MVideoFullLight |
30 | if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId) | 31 | if (payload.videoId) video = await VideoModel.loadAndPopulateAccountAndServerAndTags(payload.videoId) |
31 | 32 | ||
32 | let account: AccountModel | 33 | let account: MAccountDefault |
33 | if (payload.accountId) account = await AccountModel.load(payload.accountId) | 34 | if (payload.accountId) account = await AccountModel.load(payload.accountId) |
34 | 35 | ||
35 | const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = { | 36 | const fetcherType: { [ id in FetchType ]: (items: any[]) => Promise<any> } = { |
diff --git a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts index cdee1f6fd..d3bde6e6a 100644 --- a/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts +++ b/server/lib/job-queue/handlers/utils/activitypub-http-utils.ts | |||
@@ -3,6 +3,7 @@ import { getServerActor } from '../../../../helpers/utils' | |||
3 | import { ActorModel } from '../../../../models/activitypub/actor' | 3 | import { ActorModel } from '../../../../models/activitypub/actor' |
4 | import { sha256 } from '../../../../helpers/core-utils' | 4 | import { sha256 } from '../../../../helpers/core-utils' |
5 | import { HTTP_SIGNATURE } from '../../../../initializers/constants' | 5 | import { HTTP_SIGNATURE } from '../../../../initializers/constants' |
6 | import { MActor } from '../../../../typings/models' | ||
6 | 7 | ||
7 | type Payload = { body: any, signatureActorId?: number } | 8 | type Payload = { body: any, signatureActorId?: number } |
8 | 9 | ||
@@ -19,7 +20,8 @@ async function computeBody (payload: Payload) { | |||
19 | } | 20 | } |
20 | 21 | ||
21 | async function buildSignedRequestOptions (payload: Payload) { | 22 | async function buildSignedRequestOptions (payload: Payload) { |
22 | let actor: ActorModel | null | 23 | let actor: MActor | null |
24 | |||
23 | if (payload.signatureActorId) { | 25 | if (payload.signatureActorId) { |
24 | actor = await ActorModel.load(payload.signatureActorId) | 26 | actor = await ActorModel.load(payload.signatureActorId) |
25 | if (!actor) throw new Error('Unknown signature actor id.') | 27 | if (!actor) throw new Error('Unknown signature actor id.') |
diff --git a/server/lib/job-queue/handlers/video-file-import.ts b/server/lib/job-queue/handlers/video-file-import.ts index 8cacb0ef3..5c5b7dccb 100644 --- a/server/lib/job-queue/handlers/video-file-import.ts +++ b/server/lib/job-queue/handlers/video-file-import.ts | |||
@@ -6,6 +6,7 @@ import { getVideoFileFPS, getVideoFileResolution } from '../../../helpers/ffmpeg | |||
6 | import { copy, stat } from 'fs-extra' | 6 | import { copy, stat } from 'fs-extra' |
7 | import { VideoFileModel } from '../../../models/video/video-file' | 7 | import { VideoFileModel } from '../../../models/video/video-file' |
8 | import { extname } from 'path' | 8 | import { extname } from 'path' |
9 | import { MVideoFile, MVideoWithFile } from '@server/typings/models' | ||
9 | 10 | ||
10 | export type VideoFileImportPayload = { | 11 | export type VideoFileImportPayload = { |
11 | videoUUID: string, | 12 | videoUUID: string, |
@@ -37,7 +38,7 @@ export { | |||
37 | 38 | ||
38 | // --------------------------------------------------------------------------- | 39 | // --------------------------------------------------------------------------- |
39 | 40 | ||
40 | async function updateVideoFile (video: VideoModel, inputFilePath: string) { | 41 | async function updateVideoFile (video: MVideoWithFile, inputFilePath: string) { |
41 | const { videoFileResolution } = await getVideoFileResolution(inputFilePath) | 42 | const { videoFileResolution } = await getVideoFileResolution(inputFilePath) |
42 | const { size } = await stat(inputFilePath) | 43 | const { size } = await stat(inputFilePath) |
43 | const fps = await getVideoFileFPS(inputFilePath) | 44 | const fps = await getVideoFileFPS(inputFilePath) |
@@ -48,7 +49,7 @@ async function updateVideoFile (video: VideoModel, inputFilePath: string) { | |||
48 | size, | 49 | size, |
49 | fps, | 50 | fps, |
50 | videoId: video.id | 51 | videoId: video.id |
51 | }) | 52 | }) as MVideoFile |
52 | 53 | ||
53 | const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution) | 54 | const currentVideoFile = video.VideoFiles.find(videoFile => videoFile.resolution === updatedVideoFile.resolution) |
54 | 55 | ||
@@ -60,9 +61,9 @@ async function updateVideoFile (video: VideoModel, inputFilePath: string) { | |||
60 | video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile) | 61 | video.VideoFiles = video.VideoFiles.filter(f => f !== currentVideoFile) |
61 | 62 | ||
62 | // Update the database | 63 | // Update the database |
63 | currentVideoFile.set('extname', updatedVideoFile.extname) | 64 | currentVideoFile.extname = updatedVideoFile.extname |
64 | currentVideoFile.set('size', updatedVideoFile.size) | 65 | currentVideoFile.size = updatedVideoFile.size |
65 | currentVideoFile.set('fps', updatedVideoFile.fps) | 66 | currentVideoFile.fps = updatedVideoFile.fps |
66 | 67 | ||
67 | updatedVideoFile = currentVideoFile | 68 | updatedVideoFile = currentVideoFile |
68 | } | 69 | } |
diff --git a/server/lib/job-queue/handlers/video-import.ts b/server/lib/job-queue/handlers/video-import.ts index 13b741180..93a3e9d90 100644 --- a/server/lib/job-queue/handlers/video-import.ts +++ b/server/lib/job-queue/handlers/video-import.ts | |||
@@ -17,9 +17,11 @@ import { move, remove, stat } from 'fs-extra' | |||
17 | import { Notifier } from '../../notifier' | 17 | import { Notifier } from '../../notifier' |
18 | import { CONFIG } from '../../../initializers/config' | 18 | import { CONFIG } from '../../../initializers/config' |
19 | import { sequelizeTypescript } from '../../../initializers/database' | 19 | import { sequelizeTypescript } from '../../../initializers/database' |
20 | import { ThumbnailModel } from '../../../models/video/thumbnail' | ||
21 | import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail' | 20 | import { createVideoMiniatureFromUrl, generateVideoMiniature } from '../../thumbnail' |
22 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' | 21 | import { ThumbnailType } from '../../../../shared/models/videos/thumbnail.type' |
22 | import { MThumbnail } from '../../../typings/models/video/thumbnail' | ||
23 | import { MVideoImportDefault, MVideoImportDefaultFiles, MVideoImportVideo } from '@server/typings/models/video/video-import' | ||
24 | import { MVideoBlacklistVideo, MVideoBlacklist } from '@server/typings/models' | ||
23 | 25 | ||
24 | type VideoImportYoutubeDLPayload = { | 26 | type VideoImportYoutubeDLPayload = { |
25 | type: 'youtube-dl' | 27 | type: 'youtube-dl' |
@@ -110,7 +112,7 @@ type ProcessFileOptions = { | |||
110 | generateThumbnail: boolean | 112 | generateThumbnail: boolean |
111 | generatePreview: boolean | 113 | generatePreview: boolean |
112 | } | 114 | } |
113 | async function processFile (downloader: () => Promise<string>, videoImport: VideoImportModel, options: ProcessFileOptions) { | 115 | async function processFile (downloader: () => Promise<string>, videoImport: MVideoImportDefault, options: ProcessFileOptions) { |
114 | let tempVideoPath: string | 116 | let tempVideoPath: string |
115 | let videoDestFile: string | 117 | let videoDestFile: string |
116 | let videoFile: VideoFileModel | 118 | let videoFile: VideoFileModel |
@@ -139,41 +141,44 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
139 | videoId: videoImport.videoId | 141 | videoId: videoImport.videoId |
140 | } | 142 | } |
141 | videoFile = new VideoFileModel(videoFileData) | 143 | videoFile = new VideoFileModel(videoFileData) |
144 | |||
145 | const videoWithFiles = Object.assign(videoImport.Video, { VideoFiles: [ videoFile ] }) | ||
142 | // To clean files if the import fails | 146 | // To clean files if the import fails |
143 | videoImport.Video.VideoFiles = [ videoFile ] | 147 | const videoImportWithFiles: MVideoImportDefaultFiles = Object.assign(videoImport, { Video: videoWithFiles }) |
144 | 148 | ||
145 | // Move file | 149 | // Move file |
146 | videoDestFile = join(CONFIG.STORAGE.VIDEOS_DIR, videoImport.Video.getVideoFilename(videoFile)) | 150 | videoDestFile = join(CONFIG.STORAGE.VIDEOS_DIR, videoImportWithFiles.Video.getVideoFilename(videoFile)) |
147 | await move(tempVideoPath, videoDestFile) | 151 | await move(tempVideoPath, videoDestFile) |
148 | tempVideoPath = null // This path is not used anymore | 152 | tempVideoPath = null // This path is not used anymore |
149 | 153 | ||
150 | // Process thumbnail | 154 | // Process thumbnail |
151 | let thumbnailModel: ThumbnailModel | 155 | let thumbnailModel: MThumbnail |
152 | if (options.downloadThumbnail && options.thumbnailUrl) { | 156 | if (options.downloadThumbnail && options.thumbnailUrl) { |
153 | thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.MINIATURE) | 157 | thumbnailModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImportWithFiles.Video, ThumbnailType.MINIATURE) |
154 | } else if (options.generateThumbnail || options.downloadThumbnail) { | 158 | } else if (options.generateThumbnail || options.downloadThumbnail) { |
155 | thumbnailModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.MINIATURE) | 159 | thumbnailModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.MINIATURE) |
156 | } | 160 | } |
157 | 161 | ||
158 | // Process preview | 162 | // Process preview |
159 | let previewModel: ThumbnailModel | 163 | let previewModel: MThumbnail |
160 | if (options.downloadPreview && options.thumbnailUrl) { | 164 | if (options.downloadPreview && options.thumbnailUrl) { |
161 | previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImport.Video, ThumbnailType.PREVIEW) | 165 | previewModel = await createVideoMiniatureFromUrl(options.thumbnailUrl, videoImportWithFiles.Video, ThumbnailType.PREVIEW) |
162 | } else if (options.generatePreview || options.downloadPreview) { | 166 | } else if (options.generatePreview || options.downloadPreview) { |
163 | previewModel = await generateVideoMiniature(videoImport.Video, videoFile, ThumbnailType.PREVIEW) | 167 | previewModel = await generateVideoMiniature(videoImportWithFiles.Video, videoFile, ThumbnailType.PREVIEW) |
164 | } | 168 | } |
165 | 169 | ||
166 | // Create torrent | 170 | // Create torrent |
167 | await videoImport.Video.createTorrentAndSetInfoHash(videoFile) | 171 | await videoImportWithFiles.Video.createTorrentAndSetInfoHash(videoFile) |
172 | |||
173 | const { videoImportUpdated, video } = await sequelizeTypescript.transaction(async t => { | ||
174 | const videoImportToUpdate = videoImportWithFiles as MVideoImportVideo | ||
168 | 175 | ||
169 | const videoImportUpdated: VideoImportModel = await sequelizeTypescript.transaction(async t => { | ||
170 | // Refresh video | 176 | // Refresh video |
171 | const video = await VideoModel.load(videoImport.videoId, t) | 177 | const video = await VideoModel.load(videoImportToUpdate.videoId, t) |
172 | if (!video) throw new Error('Video linked to import ' + videoImport.videoId + ' does not exist anymore.') | 178 | if (!video) throw new Error('Video linked to import ' + videoImportToUpdate.videoId + ' does not exist anymore.') |
173 | videoImport.Video = video | ||
174 | 179 | ||
175 | const videoFileCreated = await videoFile.save({ transaction: t }) | 180 | const videoFileCreated = await videoFile.save({ transaction: t }) |
176 | video.VideoFiles = [ videoFileCreated ] | 181 | videoImportToUpdate.Video = Object.assign(video, { VideoFiles: [ videoFileCreated ] }) |
177 | 182 | ||
178 | // Update video DB object | 183 | // Update video DB object |
179 | video.duration = duration | 184 | video.duration = duration |
@@ -188,25 +193,27 @@ async function processFile (downloader: () => Promise<string>, videoImport: Vide | |||
188 | await federateVideoIfNeeded(videoForFederation, true, t) | 193 | await federateVideoIfNeeded(videoForFederation, true, t) |
189 | 194 | ||
190 | // Update video import object | 195 | // Update video import object |
191 | videoImport.state = VideoImportState.SUCCESS | 196 | videoImportToUpdate.state = VideoImportState.SUCCESS |
192 | const videoImportUpdated = await videoImport.save({ transaction: t }) | 197 | const videoImportUpdated = await videoImportToUpdate.save({ transaction: t }) as MVideoImportVideo |
198 | videoImportUpdated.Video = video | ||
193 | 199 | ||
194 | logger.info('Video %s imported.', video.uuid) | 200 | logger.info('Video %s imported.', video.uuid) |
195 | 201 | ||
196 | videoImportUpdated.Video = videoForFederation | 202 | return { videoImportUpdated, video: videoForFederation } |
197 | return videoImportUpdated | ||
198 | }) | 203 | }) |
199 | 204 | ||
200 | Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true) | 205 | Notifier.Instance.notifyOnFinishedVideoImport(videoImportUpdated, true) |
201 | 206 | ||
202 | if (videoImportUpdated.Video.isBlacklisted()) { | 207 | if (video.isBlacklisted()) { |
203 | Notifier.Instance.notifyOnVideoAutoBlacklist(videoImportUpdated.Video) | 208 | const videoBlacklist = Object.assign(video.VideoBlacklist, { Video: video }) |
209 | |||
210 | Notifier.Instance.notifyOnVideoAutoBlacklist(videoBlacklist) | ||
204 | } else { | 211 | } else { |
205 | Notifier.Instance.notifyOnNewVideoIfNeeded(videoImportUpdated.Video) | 212 | Notifier.Instance.notifyOnNewVideoIfNeeded(video) |
206 | } | 213 | } |
207 | 214 | ||
208 | // Create transcoding jobs? | 215 | // Create transcoding jobs? |
209 | if (videoImportUpdated.Video.state === VideoState.TO_TRANSCODE) { | 216 | if (video.state === VideoState.TO_TRANSCODE) { |
210 | // Put uuid because we don't have id auto incremented for now | 217 | // Put uuid because we don't have id auto incremented for now |
211 | const dataInput = { | 218 | const dataInput = { |
212 | type: 'optimize' as 'optimize', | 219 | type: 'optimize' as 'optimize', |
diff --git a/server/lib/job-queue/handlers/video-transcoding.ts b/server/lib/job-queue/handlers/video-transcoding.ts index 981daf9a1..2ebe15bcb 100644 --- a/server/lib/job-queue/handlers/video-transcoding.ts +++ b/server/lib/job-queue/handlers/video-transcoding.ts | |||
@@ -11,6 +11,7 @@ import { computeResolutionsToTranscode } from '../../../helpers/ffmpeg-utils' | |||
11 | import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding' | 11 | import { generateHlsPlaylist, optimizeVideofile, transcodeOriginalVideofile, mergeAudioVideofile } from '../../video-transcoding' |
12 | import { Notifier } from '../../notifier' | 12 | import { Notifier } from '../../notifier' |
13 | import { CONFIG } from '../../../initializers/config' | 13 | import { CONFIG } from '../../../initializers/config' |
14 | import { MVideoUUID, MVideoWithFile } from '@server/typings/models' | ||
14 | 15 | ||
15 | interface BaseTranscodingPayload { | 16 | interface BaseTranscodingPayload { |
16 | videoUUID: string | 17 | videoUUID: string |
@@ -73,7 +74,7 @@ async function processVideoTranscoding (job: Bull.Job) { | |||
73 | return video | 74 | return video |
74 | } | 75 | } |
75 | 76 | ||
76 | async function onHlsPlaylistGenerationSuccess (video: VideoModel) { | 77 | async function onHlsPlaylistGenerationSuccess (video: MVideoUUID) { |
77 | if (video === undefined) return undefined | 78 | if (video === undefined) return undefined |
78 | 79 | ||
79 | await sequelizeTypescript.transaction(async t => { | 80 | await sequelizeTypescript.transaction(async t => { |
@@ -87,7 +88,7 @@ async function onHlsPlaylistGenerationSuccess (video: VideoModel) { | |||
87 | }) | 88 | }) |
88 | } | 89 | } |
89 | 90 | ||
90 | async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) { | 91 | async function publishNewResolutionIfNeeded (video: MVideoUUID, payload?: NewResolutionTranscodingPayload | MergeAudioTranscodingPayload) { |
91 | const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { | 92 | const { videoDatabase, videoPublished } = await sequelizeTypescript.transaction(async t => { |
92 | // Maybe the video changed in database, refresh it | 93 | // Maybe the video changed in database, refresh it |
93 | let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) | 94 | let videoDatabase = await VideoModel.loadAndPopulateAccountAndServerAndTags(video.uuid, t) |
@@ -119,7 +120,7 @@ async function publishNewResolutionIfNeeded (video: VideoModel, payload?: NewRes | |||
119 | await createHlsJobIfEnabled(payload) | 120 | await createHlsJobIfEnabled(payload) |
120 | } | 121 | } |
121 | 122 | ||
122 | async function onVideoFileOptimizerSuccess (videoArg: VideoModel, payload: OptimizeTranscodingPayload) { | 123 | async function onVideoFileOptimizerSuccess (videoArg: MVideoWithFile, payload: OptimizeTranscodingPayload) { |
123 | if (videoArg === undefined) return undefined | 124 | if (videoArg === undefined) return undefined |
124 | 125 | ||
125 | // Outside the transaction (IO on disk) | 126 | // Outside the transaction (IO on disk) |
diff --git a/server/lib/notifier.ts b/server/lib/notifier.ts index a7dfb0979..b7cc2607d 100644 --- a/server/lib/notifier.ts +++ b/server/lib/notifier.ts | |||
@@ -1,20 +1,30 @@ | |||
1 | import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users' | 1 | import { UserNotificationSettingValue, UserNotificationType, UserRight } from '../../shared/models/users' |
2 | import { logger } from '../helpers/logger' | 2 | import { logger } from '../helpers/logger' |
3 | import { VideoModel } from '../models/video/video' | ||
4 | import { Emailer } from './emailer' | 3 | import { Emailer } from './emailer' |
5 | import { UserNotificationModel } from '../models/account/user-notification' | 4 | import { UserNotificationModel } from '../models/account/user-notification' |
6 | import { VideoCommentModel } from '../models/video/video-comment' | ||
7 | import { UserModel } from '../models/account/user' | 5 | import { UserModel } from '../models/account/user' |
8 | import { PeerTubeSocket } from './peertube-socket' | 6 | import { PeerTubeSocket } from './peertube-socket' |
9 | import { CONFIG } from '../initializers/config' | 7 | import { CONFIG } from '../initializers/config' |
10 | import { VideoPrivacy, VideoState } from '../../shared/models/videos' | 8 | import { VideoPrivacy, VideoState } from '../../shared/models/videos' |
11 | import { VideoAbuseModel } from '../models/video/video-abuse' | ||
12 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | ||
13 | import * as Bluebird from 'bluebird' | 9 | import * as Bluebird from 'bluebird' |
14 | import { VideoImportModel } from '../models/video/video-import' | ||
15 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | 10 | import { AccountBlocklistModel } from '../models/account/account-blocklist' |
16 | import { ActorFollowModel } from '../models/activitypub/actor-follow' | 11 | import { |
17 | import { AccountModel } from '../models/account/account' | 12 | MCommentOwnerVideo, |
13 | MVideoAbuseVideo, | ||
14 | MVideoAccountLight, | ||
15 | MVideoBlacklistLightVideo, | ||
16 | MVideoBlacklistVideo, | ||
17 | MVideoFullLight | ||
18 | } from '../typings/models/video' | ||
19 | import { | ||
20 | MUser, | ||
21 | MUserDefault, | ||
22 | MUserNotifSettingAccount, | ||
23 | MUserWithNotificationSetting, | ||
24 | UserNotificationModelForApi | ||
25 | } from '@server/typings/models/user' | ||
26 | import { MActorFollowFull } from '../typings/models' | ||
27 | import { MVideoImportVideo } from '@server/typings/models/video/video-import' | ||
18 | 28 | ||
19 | class Notifier { | 29 | class Notifier { |
20 | 30 | ||
@@ -22,7 +32,7 @@ class Notifier { | |||
22 | 32 | ||
23 | private constructor () {} | 33 | private constructor () {} |
24 | 34 | ||
25 | notifyOnNewVideoIfNeeded (video: VideoModel): void { | 35 | notifyOnNewVideoIfNeeded (video: MVideoAccountLight): void { |
26 | // Only notify on public and published videos which are not blacklisted | 36 | // Only notify on public and published videos which are not blacklisted |
27 | if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return | 37 | if (video.privacy !== VideoPrivacy.PUBLIC || video.state !== VideoState.PUBLISHED || video.isBlacklisted()) return |
28 | 38 | ||
@@ -30,7 +40,7 @@ class Notifier { | |||
30 | .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) | 40 | .catch(err => logger.error('Cannot notify subscribers of new video %s.', video.url, { err })) |
31 | } | 41 | } |
32 | 42 | ||
33 | notifyOnVideoPublishedAfterTranscoding (video: VideoModel): void { | 43 | notifyOnVideoPublishedAfterTranscoding (video: MVideoFullLight): void { |
34 | // don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update | 44 | // don't notify if didn't wait for transcoding or video is still blacklisted/waiting for scheduled update |
35 | if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return | 45 | if (!video.waitTranscoding || video.VideoBlacklist || video.ScheduleVideoUpdate) return |
36 | 46 | ||
@@ -38,7 +48,7 @@ class Notifier { | |||
38 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err })) | 48 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after transcoding.', video.url, { err })) |
39 | } | 49 | } |
40 | 50 | ||
41 | notifyOnVideoPublishedAfterScheduledUpdate (video: VideoModel): void { | 51 | notifyOnVideoPublishedAfterScheduledUpdate (video: MVideoFullLight): void { |
42 | // don't notify if video is still blacklisted or waiting for transcoding | 52 | // don't notify if video is still blacklisted or waiting for transcoding |
43 | if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return | 53 | if (video.VideoBlacklist || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return |
44 | 54 | ||
@@ -46,7 +56,7 @@ class Notifier { | |||
46 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err })) | 56 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after scheduled update.', video.url, { err })) |
47 | } | 57 | } |
48 | 58 | ||
49 | notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: VideoModel): void { | 59 | notifyOnVideoPublishedAfterRemovedFromAutoBlacklist (video: MVideoFullLight): void { |
50 | // don't notify if video is still waiting for transcoding or scheduled update | 60 | // don't notify if video is still waiting for transcoding or scheduled update |
51 | if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return | 61 | if (video.ScheduleVideoUpdate || (video.waitTranscoding && video.state !== VideoState.PUBLISHED)) return |
52 | 62 | ||
@@ -54,7 +64,7 @@ class Notifier { | |||
54 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length | 64 | .catch(err => logger.error('Cannot notify owner that its video %s has been published after removed from auto-blacklist.', video.url, { err })) // tslint:disable-line:max-line-length |
55 | } | 65 | } |
56 | 66 | ||
57 | notifyOnNewComment (comment: VideoCommentModel): void { | 67 | notifyOnNewComment (comment: MCommentOwnerVideo): void { |
58 | this.notifyVideoOwnerOfNewComment(comment) | 68 | this.notifyVideoOwnerOfNewComment(comment) |
59 | .catch(err => logger.error('Cannot notify video owner of new comment %s.', comment.url, { err })) | 69 | .catch(err => logger.error('Cannot notify video owner of new comment %s.', comment.url, { err })) |
60 | 70 | ||
@@ -62,37 +72,37 @@ class Notifier { | |||
62 | .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) | 72 | .catch(err => logger.error('Cannot notify mentions of comment %s.', comment.url, { err })) |
63 | } | 73 | } |
64 | 74 | ||
65 | notifyOnNewVideoAbuse (videoAbuse: VideoAbuseModel): void { | 75 | notifyOnNewVideoAbuse (videoAbuse: MVideoAbuseVideo): void { |
66 | this.notifyModeratorsOfNewVideoAbuse(videoAbuse) | 76 | this.notifyModeratorsOfNewVideoAbuse(videoAbuse) |
67 | .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) | 77 | .catch(err => logger.error('Cannot notify of new video abuse of video %s.', videoAbuse.Video.url, { err })) |
68 | } | 78 | } |
69 | 79 | ||
70 | notifyOnVideoAutoBlacklist (video: VideoModel): void { | 80 | notifyOnVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo): void { |
71 | this.notifyModeratorsOfVideoAutoBlacklist(video) | 81 | this.notifyModeratorsOfVideoAutoBlacklist(videoBlacklist) |
72 | .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', video.url, { err })) | 82 | .catch(err => logger.error('Cannot notify of auto-blacklist of video %s.', videoBlacklist.Video.url, { err })) |
73 | } | 83 | } |
74 | 84 | ||
75 | notifyOnVideoBlacklist (videoBlacklist: VideoBlacklistModel): void { | 85 | notifyOnVideoBlacklist (videoBlacklist: MVideoBlacklistVideo): void { |
76 | this.notifyVideoOwnerOfBlacklist(videoBlacklist) | 86 | this.notifyVideoOwnerOfBlacklist(videoBlacklist) |
77 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) | 87 | .catch(err => logger.error('Cannot notify video owner of new video blacklist of %s.', videoBlacklist.Video.url, { err })) |
78 | } | 88 | } |
79 | 89 | ||
80 | notifyOnVideoUnblacklist (video: VideoModel): void { | 90 | notifyOnVideoUnblacklist (video: MVideoFullLight): void { |
81 | this.notifyVideoOwnerOfUnblacklist(video) | 91 | this.notifyVideoOwnerOfUnblacklist(video) |
82 | .catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err })) | 92 | .catch(err => logger.error('Cannot notify video owner of unblacklist of %s.', video.url, { err })) |
83 | } | 93 | } |
84 | 94 | ||
85 | notifyOnFinishedVideoImport (videoImport: VideoImportModel, success: boolean): void { | 95 | notifyOnFinishedVideoImport (videoImport: MVideoImportVideo, success: boolean): void { |
86 | this.notifyOwnerVideoImportIsFinished(videoImport, success) | 96 | this.notifyOwnerVideoImportIsFinished(videoImport, success) |
87 | .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err })) | 97 | .catch(err => logger.error('Cannot notify owner that its video import %s is finished.', videoImport.getTargetIdentifier(), { err })) |
88 | } | 98 | } |
89 | 99 | ||
90 | notifyOnNewUserRegistration (user: UserModel): void { | 100 | notifyOnNewUserRegistration (user: MUserDefault): void { |
91 | this.notifyModeratorsOfNewUserRegistration(user) | 101 | this.notifyModeratorsOfNewUserRegistration(user) |
92 | .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err })) | 102 | .catch(err => logger.error('Cannot notify moderators of new user registration (%s).', user.username, { err })) |
93 | } | 103 | } |
94 | 104 | ||
95 | notifyOfNewUserFollow (actorFollow: ActorFollowModel): void { | 105 | notifyOfNewUserFollow (actorFollow: MActorFollowFull): void { |
96 | this.notifyUserOfNewActorFollow(actorFollow) | 106 | this.notifyUserOfNewActorFollow(actorFollow) |
97 | .catch(err => { | 107 | .catch(err => { |
98 | logger.error( | 108 | logger.error( |
@@ -104,25 +114,32 @@ class Notifier { | |||
104 | }) | 114 | }) |
105 | } | 115 | } |
106 | 116 | ||
107 | notifyOfNewInstanceFollow (actorFollow: ActorFollowModel): void { | 117 | notifyOfNewInstanceFollow (actorFollow: MActorFollowFull): void { |
108 | this.notifyAdminsOfNewInstanceFollow(actorFollow) | 118 | this.notifyAdminsOfNewInstanceFollow(actorFollow) |
109 | .catch(err => { | 119 | .catch(err => { |
110 | logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err }) | 120 | logger.error('Cannot notify administrators of new follower %s.', actorFollow.ActorFollower.url, { err }) |
111 | }) | 121 | }) |
112 | } | 122 | } |
113 | 123 | ||
114 | private async notifySubscribersOfNewVideo (video: VideoModel) { | 124 | notifyOfAutoInstanceFollowing (actorFollow: MActorFollowFull): void { |
125 | this.notifyAdminsOfAutoInstanceFollowing(actorFollow) | ||
126 | .catch(err => { | ||
127 | logger.error('Cannot notify administrators of auto instance following %s.', actorFollow.ActorFollowing.url, { err }) | ||
128 | }) | ||
129 | } | ||
130 | |||
131 | private async notifySubscribersOfNewVideo (video: MVideoAccountLight) { | ||
115 | // List all followers that are users | 132 | // List all followers that are users |
116 | const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId) | 133 | const users = await UserModel.listUserSubscribersOf(video.VideoChannel.actorId) |
117 | 134 | ||
118 | logger.info('Notifying %d users of new video %s.', users.length, video.url) | 135 | logger.info('Notifying %d users of new video %s.', users.length, video.url) |
119 | 136 | ||
120 | function settingGetter (user: UserModel) { | 137 | function settingGetter (user: MUserWithNotificationSetting) { |
121 | return user.NotificationSetting.newVideoFromSubscription | 138 | return user.NotificationSetting.newVideoFromSubscription |
122 | } | 139 | } |
123 | 140 | ||
124 | async function notificationCreator (user: UserModel) { | 141 | async function notificationCreator (user: MUserWithNotificationSetting) { |
125 | const notification = await UserNotificationModel.create({ | 142 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
126 | type: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION, | 143 | type: UserNotificationType.NEW_VIDEO_FROM_SUBSCRIPTION, |
127 | userId: user.id, | 144 | userId: user.id, |
128 | videoId: video.id | 145 | videoId: video.id |
@@ -139,7 +156,7 @@ class Notifier { | |||
139 | return this.notify({ users, settingGetter, notificationCreator, emailSender }) | 156 | return this.notify({ users, settingGetter, notificationCreator, emailSender }) |
140 | } | 157 | } |
141 | 158 | ||
142 | private async notifyVideoOwnerOfNewComment (comment: VideoCommentModel) { | 159 | private async notifyVideoOwnerOfNewComment (comment: MCommentOwnerVideo) { |
143 | if (comment.Video.isOwned() === false) return | 160 | if (comment.Video.isOwned() === false) return |
144 | 161 | ||
145 | const user = await UserModel.loadByVideoId(comment.videoId) | 162 | const user = await UserModel.loadByVideoId(comment.videoId) |
@@ -152,12 +169,12 @@ class Notifier { | |||
152 | 169 | ||
153 | logger.info('Notifying user %s of new comment %s.', user.username, comment.url) | 170 | logger.info('Notifying user %s of new comment %s.', user.username, comment.url) |
154 | 171 | ||
155 | function settingGetter (user: UserModel) { | 172 | function settingGetter (user: MUserWithNotificationSetting) { |
156 | return user.NotificationSetting.newCommentOnMyVideo | 173 | return user.NotificationSetting.newCommentOnMyVideo |
157 | } | 174 | } |
158 | 175 | ||
159 | async function notificationCreator (user: UserModel) { | 176 | async function notificationCreator (user: MUserWithNotificationSetting) { |
160 | const notification = await UserNotificationModel.create({ | 177 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
161 | type: UserNotificationType.NEW_COMMENT_ON_MY_VIDEO, | 178 | type: UserNotificationType.NEW_COMMENT_ON_MY_VIDEO, |
162 | userId: user.id, | 179 | userId: user.id, |
163 | commentId: comment.id | 180 | commentId: comment.id |
@@ -174,7 +191,7 @@ class Notifier { | |||
174 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | 191 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) |
175 | } | 192 | } |
176 | 193 | ||
177 | private async notifyOfCommentMention (comment: VideoCommentModel) { | 194 | private async notifyOfCommentMention (comment: MCommentOwnerVideo) { |
178 | const extractedUsernames = comment.extractMentions() | 195 | const extractedUsernames = comment.extractMentions() |
179 | logger.debug( | 196 | logger.debug( |
180 | 'Extracted %d username from comment %s.', extractedUsernames.length, comment.url, | 197 | 'Extracted %d username from comment %s.', extractedUsernames.length, comment.url, |
@@ -197,14 +214,14 @@ class Notifier { | |||
197 | 214 | ||
198 | logger.info('Notifying %d users of new comment %s.', users.length, comment.url) | 215 | logger.info('Notifying %d users of new comment %s.', users.length, comment.url) |
199 | 216 | ||
200 | function settingGetter (user: UserModel) { | 217 | function settingGetter (user: MUserNotifSettingAccount) { |
201 | if (accountMutedHash[user.Account.id] === true) return UserNotificationSettingValue.NONE | 218 | if (accountMutedHash[user.Account.id] === true) return UserNotificationSettingValue.NONE |
202 | 219 | ||
203 | return user.NotificationSetting.commentMention | 220 | return user.NotificationSetting.commentMention |
204 | } | 221 | } |
205 | 222 | ||
206 | async function notificationCreator (user: UserModel) { | 223 | async function notificationCreator (user: MUserNotifSettingAccount) { |
207 | const notification = await UserNotificationModel.create({ | 224 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
208 | type: UserNotificationType.COMMENT_MENTION, | 225 | type: UserNotificationType.COMMENT_MENTION, |
209 | userId: user.id, | 226 | userId: user.id, |
210 | commentId: comment.id | 227 | commentId: comment.id |
@@ -221,7 +238,7 @@ class Notifier { | |||
221 | return this.notify({ users, settingGetter, notificationCreator, emailSender }) | 238 | return this.notify({ users, settingGetter, notificationCreator, emailSender }) |
222 | } | 239 | } |
223 | 240 | ||
224 | private async notifyUserOfNewActorFollow (actorFollow: ActorFollowModel) { | 241 | private async notifyUserOfNewActorFollow (actorFollow: MActorFollowFull) { |
225 | if (actorFollow.ActorFollowing.isOwned() === false) return | 242 | if (actorFollow.ActorFollowing.isOwned() === false) return |
226 | 243 | ||
227 | // Account follows one of our account? | 244 | // Account follows one of our account? |
@@ -236,9 +253,6 @@ class Notifier { | |||
236 | 253 | ||
237 | if (!user) return | 254 | if (!user) return |
238 | 255 | ||
239 | if (!actorFollow.ActorFollower.Account || !actorFollow.ActorFollower.Account.name) { | ||
240 | actorFollow.ActorFollower.Account = await actorFollow.ActorFollower.$get('Account') as AccountModel | ||
241 | } | ||
242 | const followerAccount = actorFollow.ActorFollower.Account | 256 | const followerAccount = actorFollow.ActorFollower.Account |
243 | 257 | ||
244 | const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, followerAccount.id) | 258 | const accountMuted = await AccountBlocklistModel.isAccountMutedBy(user.Account.id, followerAccount.id) |
@@ -246,12 +260,12 @@ class Notifier { | |||
246 | 260 | ||
247 | logger.info('Notifying user %s of new follower: %s.', user.username, followerAccount.getDisplayName()) | 261 | logger.info('Notifying user %s of new follower: %s.', user.username, followerAccount.getDisplayName()) |
248 | 262 | ||
249 | function settingGetter (user: UserModel) { | 263 | function settingGetter (user: MUserWithNotificationSetting) { |
250 | return user.NotificationSetting.newFollow | 264 | return user.NotificationSetting.newFollow |
251 | } | 265 | } |
252 | 266 | ||
253 | async function notificationCreator (user: UserModel) { | 267 | async function notificationCreator (user: MUserWithNotificationSetting) { |
254 | const notification = await UserNotificationModel.create({ | 268 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
255 | type: UserNotificationType.NEW_FOLLOW, | 269 | type: UserNotificationType.NEW_FOLLOW, |
256 | userId: user.id, | 270 | userId: user.id, |
257 | actorFollowId: actorFollow.id | 271 | actorFollowId: actorFollow.id |
@@ -268,17 +282,17 @@ class Notifier { | |||
268 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | 282 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) |
269 | } | 283 | } |
270 | 284 | ||
271 | private async notifyAdminsOfNewInstanceFollow (actorFollow: ActorFollowModel) { | 285 | private async notifyAdminsOfNewInstanceFollow (actorFollow: MActorFollowFull) { |
272 | const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW) | 286 | const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW) |
273 | 287 | ||
274 | logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url) | 288 | logger.info('Notifying %d administrators of new instance follower: %s.', admins.length, actorFollow.ActorFollower.url) |
275 | 289 | ||
276 | function settingGetter (user: UserModel) { | 290 | function settingGetter (user: MUserWithNotificationSetting) { |
277 | return user.NotificationSetting.newInstanceFollower | 291 | return user.NotificationSetting.newInstanceFollower |
278 | } | 292 | } |
279 | 293 | ||
280 | async function notificationCreator (user: UserModel) { | 294 | async function notificationCreator (user: MUserWithNotificationSetting) { |
281 | const notification = await UserNotificationModel.create({ | 295 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
282 | type: UserNotificationType.NEW_INSTANCE_FOLLOWER, | 296 | type: UserNotificationType.NEW_INSTANCE_FOLLOWER, |
283 | userId: user.id, | 297 | userId: user.id, |
284 | actorFollowId: actorFollow.id | 298 | actorFollowId: actorFollow.id |
@@ -295,18 +309,45 @@ class Notifier { | |||
295 | return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) | 309 | return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) |
296 | } | 310 | } |
297 | 311 | ||
298 | private async notifyModeratorsOfNewVideoAbuse (videoAbuse: VideoAbuseModel) { | 312 | private async notifyAdminsOfAutoInstanceFollowing (actorFollow: MActorFollowFull) { |
313 | const admins = await UserModel.listWithRight(UserRight.MANAGE_SERVER_FOLLOW) | ||
314 | |||
315 | logger.info('Notifying %d administrators of auto instance following: %s.', admins.length, actorFollow.ActorFollowing.url) | ||
316 | |||
317 | function settingGetter (user: MUserWithNotificationSetting) { | ||
318 | return user.NotificationSetting.autoInstanceFollowing | ||
319 | } | ||
320 | |||
321 | async function notificationCreator (user: MUserWithNotificationSetting) { | ||
322 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ | ||
323 | type: UserNotificationType.AUTO_INSTANCE_FOLLOWING, | ||
324 | userId: user.id, | ||
325 | actorFollowId: actorFollow.id | ||
326 | }) | ||
327 | notification.ActorFollow = actorFollow | ||
328 | |||
329 | return notification | ||
330 | } | ||
331 | |||
332 | function emailSender (emails: string[]) { | ||
333 | return Emailer.Instance.addAutoInstanceFollowingNotification(emails, actorFollow) | ||
334 | } | ||
335 | |||
336 | return this.notify({ users: admins, settingGetter, notificationCreator, emailSender }) | ||
337 | } | ||
338 | |||
339 | private async notifyModeratorsOfNewVideoAbuse (videoAbuse: MVideoAbuseVideo) { | ||
299 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) | 340 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_ABUSES) |
300 | if (moderators.length === 0) return | 341 | if (moderators.length === 0) return |
301 | 342 | ||
302 | logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, videoAbuse.Video.url) | 343 | logger.info('Notifying %s user/moderators of new video abuse %s.', moderators.length, videoAbuse.Video.url) |
303 | 344 | ||
304 | function settingGetter (user: UserModel) { | 345 | function settingGetter (user: MUserWithNotificationSetting) { |
305 | return user.NotificationSetting.videoAbuseAsModerator | 346 | return user.NotificationSetting.videoAbuseAsModerator |
306 | } | 347 | } |
307 | 348 | ||
308 | async function notificationCreator (user: UserModel) { | 349 | async function notificationCreator (user: MUserWithNotificationSetting) { |
309 | const notification = await UserNotificationModel.create({ | 350 | const notification: UserNotificationModelForApi = await UserNotificationModel.create<UserNotificationModelForApi>({ |
310 | type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, | 351 | type: UserNotificationType.NEW_VIDEO_ABUSE_FOR_MODERATORS, |
311 | userId: user.id, | 352 | userId: user.id, |
312 | videoAbuseId: videoAbuse.id | 353 | videoAbuseId: videoAbuse.id |
@@ -323,46 +364,46 @@ class Notifier { | |||
323 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | 364 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) |
324 | } | 365 | } |
325 | 366 | ||
326 | private async notifyModeratorsOfVideoAutoBlacklist (video: VideoModel) { | 367 | private async notifyModeratorsOfVideoAutoBlacklist (videoBlacklist: MVideoBlacklistLightVideo) { |
327 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST) | 368 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_VIDEO_BLACKLIST) |
328 | if (moderators.length === 0) return | 369 | if (moderators.length === 0) return |
329 | 370 | ||
330 | logger.info('Notifying %s moderators of video auto-blacklist %s.', moderators.length, video.url) | 371 | logger.info('Notifying %s moderators of video auto-blacklist %s.', moderators.length, videoBlacklist.Video.url) |
331 | 372 | ||
332 | function settingGetter (user: UserModel) { | 373 | function settingGetter (user: MUserWithNotificationSetting) { |
333 | return user.NotificationSetting.videoAutoBlacklistAsModerator | 374 | return user.NotificationSetting.videoAutoBlacklistAsModerator |
334 | } | 375 | } |
335 | async function notificationCreator (user: UserModel) { | ||
336 | 376 | ||
337 | const notification = await UserNotificationModel.create({ | 377 | async function notificationCreator (user: MUserWithNotificationSetting) { |
378 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ | ||
338 | type: UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS, | 379 | type: UserNotificationType.VIDEO_AUTO_BLACKLIST_FOR_MODERATORS, |
339 | userId: user.id, | 380 | userId: user.id, |
340 | videoId: video.id | 381 | videoBlacklistId: videoBlacklist.id |
341 | }) | 382 | }) |
342 | notification.Video = video | 383 | notification.VideoBlacklist = videoBlacklist |
343 | 384 | ||
344 | return notification | 385 | return notification |
345 | } | 386 | } |
346 | 387 | ||
347 | function emailSender (emails: string[]) { | 388 | function emailSender (emails: string[]) { |
348 | return Emailer.Instance.addVideoAutoBlacklistModeratorsNotification(emails, video) | 389 | return Emailer.Instance.addVideoAutoBlacklistModeratorsNotification(emails, videoBlacklist) |
349 | } | 390 | } |
350 | 391 | ||
351 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | 392 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) |
352 | } | 393 | } |
353 | 394 | ||
354 | private async notifyVideoOwnerOfBlacklist (videoBlacklist: VideoBlacklistModel) { | 395 | private async notifyVideoOwnerOfBlacklist (videoBlacklist: MVideoBlacklistVideo) { |
355 | const user = await UserModel.loadByVideoId(videoBlacklist.videoId) | 396 | const user = await UserModel.loadByVideoId(videoBlacklist.videoId) |
356 | if (!user) return | 397 | if (!user) return |
357 | 398 | ||
358 | logger.info('Notifying user %s that its video %s has been blacklisted.', user.username, videoBlacklist.Video.url) | 399 | logger.info('Notifying user %s that its video %s has been blacklisted.', user.username, videoBlacklist.Video.url) |
359 | 400 | ||
360 | function settingGetter (user: UserModel) { | 401 | function settingGetter (user: MUserWithNotificationSetting) { |
361 | return user.NotificationSetting.blacklistOnMyVideo | 402 | return user.NotificationSetting.blacklistOnMyVideo |
362 | } | 403 | } |
363 | 404 | ||
364 | async function notificationCreator (user: UserModel) { | 405 | async function notificationCreator (user: MUserWithNotificationSetting) { |
365 | const notification = await UserNotificationModel.create({ | 406 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
366 | type: UserNotificationType.BLACKLIST_ON_MY_VIDEO, | 407 | type: UserNotificationType.BLACKLIST_ON_MY_VIDEO, |
367 | userId: user.id, | 408 | userId: user.id, |
368 | videoBlacklistId: videoBlacklist.id | 409 | videoBlacklistId: videoBlacklist.id |
@@ -379,18 +420,18 @@ class Notifier { | |||
379 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | 420 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) |
380 | } | 421 | } |
381 | 422 | ||
382 | private async notifyVideoOwnerOfUnblacklist (video: VideoModel) { | 423 | private async notifyVideoOwnerOfUnblacklist (video: MVideoFullLight) { |
383 | const user = await UserModel.loadByVideoId(video.id) | 424 | const user = await UserModel.loadByVideoId(video.id) |
384 | if (!user) return | 425 | if (!user) return |
385 | 426 | ||
386 | logger.info('Notifying user %s that its video %s has been unblacklisted.', user.username, video.url) | 427 | logger.info('Notifying user %s that its video %s has been unblacklisted.', user.username, video.url) |
387 | 428 | ||
388 | function settingGetter (user: UserModel) { | 429 | function settingGetter (user: MUserWithNotificationSetting) { |
389 | return user.NotificationSetting.blacklistOnMyVideo | 430 | return user.NotificationSetting.blacklistOnMyVideo |
390 | } | 431 | } |
391 | 432 | ||
392 | async function notificationCreator (user: UserModel) { | 433 | async function notificationCreator (user: MUserWithNotificationSetting) { |
393 | const notification = await UserNotificationModel.create({ | 434 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
394 | type: UserNotificationType.UNBLACKLIST_ON_MY_VIDEO, | 435 | type: UserNotificationType.UNBLACKLIST_ON_MY_VIDEO, |
395 | userId: user.id, | 436 | userId: user.id, |
396 | videoId: video.id | 437 | videoId: video.id |
@@ -407,18 +448,18 @@ class Notifier { | |||
407 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | 448 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) |
408 | } | 449 | } |
409 | 450 | ||
410 | private async notifyOwnedVideoHasBeenPublished (video: VideoModel) { | 451 | private async notifyOwnedVideoHasBeenPublished (video: MVideoFullLight) { |
411 | const user = await UserModel.loadByVideoId(video.id) | 452 | const user = await UserModel.loadByVideoId(video.id) |
412 | if (!user) return | 453 | if (!user) return |
413 | 454 | ||
414 | logger.info('Notifying user %s of the publication of its video %s.', user.username, video.url) | 455 | logger.info('Notifying user %s of the publication of its video %s.', user.username, video.url) |
415 | 456 | ||
416 | function settingGetter (user: UserModel) { | 457 | function settingGetter (user: MUserWithNotificationSetting) { |
417 | return user.NotificationSetting.myVideoPublished | 458 | return user.NotificationSetting.myVideoPublished |
418 | } | 459 | } |
419 | 460 | ||
420 | async function notificationCreator (user: UserModel) { | 461 | async function notificationCreator (user: MUserWithNotificationSetting) { |
421 | const notification = await UserNotificationModel.create({ | 462 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
422 | type: UserNotificationType.MY_VIDEO_PUBLISHED, | 463 | type: UserNotificationType.MY_VIDEO_PUBLISHED, |
423 | userId: user.id, | 464 | userId: user.id, |
424 | videoId: video.id | 465 | videoId: video.id |
@@ -435,18 +476,18 @@ class Notifier { | |||
435 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | 476 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) |
436 | } | 477 | } |
437 | 478 | ||
438 | private async notifyOwnerVideoImportIsFinished (videoImport: VideoImportModel, success: boolean) { | 479 | private async notifyOwnerVideoImportIsFinished (videoImport: MVideoImportVideo, success: boolean) { |
439 | const user = await UserModel.loadByVideoImportId(videoImport.id) | 480 | const user = await UserModel.loadByVideoImportId(videoImport.id) |
440 | if (!user) return | 481 | if (!user) return |
441 | 482 | ||
442 | logger.info('Notifying user %s its video import %s is finished.', user.username, videoImport.getTargetIdentifier()) | 483 | logger.info('Notifying user %s its video import %s is finished.', user.username, videoImport.getTargetIdentifier()) |
443 | 484 | ||
444 | function settingGetter (user: UserModel) { | 485 | function settingGetter (user: MUserWithNotificationSetting) { |
445 | return user.NotificationSetting.myVideoImportFinished | 486 | return user.NotificationSetting.myVideoImportFinished |
446 | } | 487 | } |
447 | 488 | ||
448 | async function notificationCreator (user: UserModel) { | 489 | async function notificationCreator (user: MUserWithNotificationSetting) { |
449 | const notification = await UserNotificationModel.create({ | 490 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
450 | type: success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR, | 491 | type: success ? UserNotificationType.MY_VIDEO_IMPORT_SUCCESS : UserNotificationType.MY_VIDEO_IMPORT_ERROR, |
451 | userId: user.id, | 492 | userId: user.id, |
452 | videoImportId: videoImport.id | 493 | videoImportId: videoImport.id |
@@ -465,21 +506,21 @@ class Notifier { | |||
465 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) | 506 | return this.notify({ users: [ user ], settingGetter, notificationCreator, emailSender }) |
466 | } | 507 | } |
467 | 508 | ||
468 | private async notifyModeratorsOfNewUserRegistration (registeredUser: UserModel) { | 509 | private async notifyModeratorsOfNewUserRegistration (registeredUser: MUserDefault) { |
469 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS) | 510 | const moderators = await UserModel.listWithRight(UserRight.MANAGE_USERS) |
470 | if (moderators.length === 0) return | 511 | if (moderators.length === 0) return |
471 | 512 | ||
472 | logger.info( | 513 | logger.info( |
473 | 'Notifying %s moderators of new user registration of %s.', | 514 | 'Notifying %s moderators of new user registration of %s.', |
474 | moderators.length, registeredUser.Account.Actor.preferredUsername | 515 | moderators.length, registeredUser.username |
475 | ) | 516 | ) |
476 | 517 | ||
477 | function settingGetter (user: UserModel) { | 518 | function settingGetter (user: MUserWithNotificationSetting) { |
478 | return user.NotificationSetting.newUserRegistration | 519 | return user.NotificationSetting.newUserRegistration |
479 | } | 520 | } |
480 | 521 | ||
481 | async function notificationCreator (user: UserModel) { | 522 | async function notificationCreator (user: MUserWithNotificationSetting) { |
482 | const notification = await UserNotificationModel.create({ | 523 | const notification = await UserNotificationModel.create<UserNotificationModelForApi>({ |
483 | type: UserNotificationType.NEW_USER_REGISTRATION, | 524 | type: UserNotificationType.NEW_USER_REGISTRATION, |
484 | userId: user.id, | 525 | userId: user.id, |
485 | accountId: registeredUser.Account.id | 526 | accountId: registeredUser.Account.id |
@@ -496,11 +537,11 @@ class Notifier { | |||
496 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) | 537 | return this.notify({ users: moderators, settingGetter, notificationCreator, emailSender }) |
497 | } | 538 | } |
498 | 539 | ||
499 | private async notify (options: { | 540 | private async notify <T extends MUserWithNotificationSetting> (options: { |
500 | users: UserModel[], | 541 | users: T[], |
501 | notificationCreator: (user: UserModel) => Promise<UserNotificationModel>, | 542 | notificationCreator: (user: T) => Promise<UserNotificationModelForApi>, |
502 | emailSender: (emails: string[]) => Promise<any> | Bluebird<any>, | 543 | emailSender: (emails: string[]) => Promise<any> | Bluebird<any>, |
503 | settingGetter: (user: UserModel) => UserNotificationSettingValue | 544 | settingGetter: (user: T) => UserNotificationSettingValue |
504 | }) { | 545 | }) { |
505 | const emails: string[] = [] | 546 | const emails: string[] = [] |
506 | 547 | ||
@@ -521,7 +562,7 @@ class Notifier { | |||
521 | } | 562 | } |
522 | } | 563 | } |
523 | 564 | ||
524 | private isEmailEnabled (user: UserModel, value: UserNotificationSettingValue) { | 565 | private isEmailEnabled (user: MUser, value: UserNotificationSettingValue) { |
525 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false | 566 | if (CONFIG.SIGNUP.REQUIRES_EMAIL_VERIFICATION === true && user.emailVerified === false) return false |
526 | 567 | ||
527 | return value & UserNotificationSettingValue.EMAIL | 568 | return value & UserNotificationSettingValue.EMAIL |
diff --git a/server/lib/oauth-model.ts b/server/lib/oauth-model.ts index a1153e88a..086856f41 100644 --- a/server/lib/oauth-model.ts +++ b/server/lib/oauth-model.ts | |||
@@ -8,10 +8,11 @@ import { LRU_CACHE } from '../initializers/constants' | |||
8 | import { Transaction } from 'sequelize' | 8 | import { Transaction } from 'sequelize' |
9 | import { CONFIG } from '../initializers/config' | 9 | import { CONFIG } from '../initializers/config' |
10 | import * as LRUCache from 'lru-cache' | 10 | import * as LRUCache from 'lru-cache' |
11 | import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' | ||
11 | 12 | ||
12 | type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } | 13 | type TokenInfo = { accessToken: string, refreshToken: string, accessTokenExpiresAt: Date, refreshTokenExpiresAt: Date } |
13 | 14 | ||
14 | const accessTokenCache = new LRUCache<string, OAuthTokenModel>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) | 15 | const accessTokenCache = new LRUCache<string, MOAuthTokenUser>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) |
15 | const userHavingToken = new LRUCache<number, string>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) | 16 | const userHavingToken = new LRUCache<number, string>({ max: LRU_CACHE.USER_TOKENS.MAX_SIZE }) |
16 | 17 | ||
17 | // --------------------------------------------------------------------------- | 18 | // --------------------------------------------------------------------------- |
diff --git a/server/lib/peertube-socket.ts b/server/lib/peertube-socket.ts index 17748fd18..26ced351f 100644 --- a/server/lib/peertube-socket.ts +++ b/server/lib/peertube-socket.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import * as SocketIO from 'socket.io' | 1 | import * as SocketIO from 'socket.io' |
2 | import { authenticateSocket } from '../middlewares' | 2 | import { authenticateSocket } from '../middlewares' |
3 | import { UserNotificationModel } from '../models/account/user-notification' | ||
4 | import { logger } from '../helpers/logger' | 3 | import { logger } from '../helpers/logger' |
5 | import { Server } from 'http' | 4 | import { Server } from 'http' |
5 | import { UserNotificationModelForApi } from '@server/typings/models/user' | ||
6 | 6 | ||
7 | class PeerTubeSocket { | 7 | class PeerTubeSocket { |
8 | 8 | ||
@@ -34,13 +34,14 @@ class PeerTubeSocket { | |||
34 | }) | 34 | }) |
35 | } | 35 | } |
36 | 36 | ||
37 | sendNotification (userId: number, notification: UserNotificationModel) { | 37 | sendNotification (userId: number, notification: UserNotificationModelForApi) { |
38 | const sockets = this.userNotificationSockets[userId] | 38 | const sockets = this.userNotificationSockets[userId] |
39 | 39 | ||
40 | if (!sockets) return | 40 | if (!sockets) return |
41 | 41 | ||
42 | const notificationMessage = notification.toFormattedJSON() | ||
42 | for (const socket of sockets) { | 43 | for (const socket of sockets) { |
43 | socket.emit('new-notification', notification.toFormattedJSON()) | 44 | socket.emit('new-notification', notificationMessage) |
44 | } | 45 | } |
45 | } | 46 | } |
46 | 47 | ||
diff --git a/server/lib/redundancy.ts b/server/lib/redundancy.ts index 04d3ded8f..1b4ecd7c0 100644 --- a/server/lib/redundancy.ts +++ b/server/lib/redundancy.ts | |||
@@ -2,8 +2,9 @@ import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' | |||
2 | import { sendUndoCacheFile } from './activitypub/send' | 2 | import { sendUndoCacheFile } from './activitypub/send' |
3 | import { Transaction } from 'sequelize' | 3 | import { Transaction } from 'sequelize' |
4 | import { getServerActor } from '../helpers/utils' | 4 | import { getServerActor } from '../helpers/utils' |
5 | import { MVideoRedundancyVideo } from '@server/typings/models' | ||
5 | 6 | ||
6 | async function removeVideoRedundancy (videoRedundancy: VideoRedundancyModel, t?: Transaction) { | 7 | async function removeVideoRedundancy (videoRedundancy: MVideoRedundancyVideo, t?: Transaction) { |
7 | const serverActor = await getServerActor() | 8 | const serverActor = await getServerActor() |
8 | 9 | ||
9 | // Local cache, send undo to remote instances | 10 | // Local cache, send undo to remote instances |
diff --git a/server/lib/schedulers/auto-follow-index-instances.ts b/server/lib/schedulers/auto-follow-index-instances.ts new file mode 100644 index 000000000..ef11fc87f --- /dev/null +++ b/server/lib/schedulers/auto-follow-index-instances.ts | |||
@@ -0,0 +1,72 @@ | |||
1 | import { logger } from '../../helpers/logger' | ||
2 | import { AbstractScheduler } from './abstract-scheduler' | ||
3 | import { INSTANCES_INDEX, SCHEDULER_INTERVALS_MS, SERVER_ACTOR_NAME } from '../../initializers/constants' | ||
4 | import { CONFIG } from '../../initializers/config' | ||
5 | import { chunk } from 'lodash' | ||
6 | import { doRequest } from '@server/helpers/requests' | ||
7 | import { ActorFollowModel } from '@server/models/activitypub/actor-follow' | ||
8 | import { JobQueue } from '@server/lib/job-queue' | ||
9 | import { getServerActor } from '@server/helpers/utils' | ||
10 | |||
11 | export class AutoFollowIndexInstances extends AbstractScheduler { | ||
12 | |||
13 | private static instance: AbstractScheduler | ||
14 | |||
15 | protected schedulerIntervalMs = SCHEDULER_INTERVALS_MS.autoFollowIndexInstances | ||
16 | |||
17 | private lastCheck: Date | ||
18 | |||
19 | private constructor () { | ||
20 | super() | ||
21 | } | ||
22 | |||
23 | protected async internalExecute () { | ||
24 | return this.autoFollow() | ||
25 | } | ||
26 | |||
27 | private async autoFollow () { | ||
28 | if (CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.ENABLED === false) return | ||
29 | |||
30 | const indexUrl = CONFIG.FOLLOWINGS.INSTANCE.AUTO_FOLLOW_INDEX.INDEX_URL | ||
31 | |||
32 | logger.info('Auto follow instances of index %s.', indexUrl) | ||
33 | |||
34 | try { | ||
35 | const serverActor = await getServerActor() | ||
36 | |||
37 | const uri = indexUrl + INSTANCES_INDEX.HOSTS_PATH | ||
38 | |||
39 | const qs = this.lastCheck ? { since: this.lastCheck.toISOString() } : {} | ||
40 | this.lastCheck = new Date() | ||
41 | |||
42 | const { body } = await doRequest({ uri, qs, json: true }) | ||
43 | |||
44 | const hosts: string[] = body.data.map(o => o.host) | ||
45 | const chunks = chunk(hosts, 20) | ||
46 | |||
47 | for (const chunk of chunks) { | ||
48 | const unfollowedHosts = await ActorFollowModel.keepUnfollowedInstance(chunk) | ||
49 | |||
50 | for (const unfollowedHost of unfollowedHosts) { | ||
51 | const payload = { | ||
52 | host: unfollowedHost, | ||
53 | name: SERVER_ACTOR_NAME, | ||
54 | followerActorId: serverActor.id, | ||
55 | isAutoFollow: true | ||
56 | } | ||
57 | |||
58 | await JobQueue.Instance.createJob({ type: 'activitypub-follow', payload }) | ||
59 | .catch(err => logger.error('Cannot create follow job for %s.', unfollowedHost, err)) | ||
60 | } | ||
61 | } | ||
62 | |||
63 | } catch (err) { | ||
64 | logger.error('Cannot auto follow hosts of index %s.', indexUrl, { err }) | ||
65 | } | ||
66 | |||
67 | } | ||
68 | |||
69 | static get Instance () { | ||
70 | return this.instance || (this.instance = new this()) | ||
71 | } | ||
72 | } | ||
diff --git a/server/lib/schedulers/videos-redundancy-scheduler.ts b/server/lib/schedulers/videos-redundancy-scheduler.ts index 5f4aad66e..1e30f6ebc 100644 --- a/server/lib/schedulers/videos-redundancy-scheduler.ts +++ b/server/lib/schedulers/videos-redundancy-scheduler.ts | |||
@@ -3,7 +3,6 @@ import { HLS_REDUNDANCY_DIRECTORY, REDUNDANCY, VIDEO_IMPORT_TIMEOUT, WEBSERVER } | |||
3 | import { logger } from '../../helpers/logger' | 3 | import { logger } from '../../helpers/logger' |
4 | import { VideosRedundancy } from '../../../shared/models/redundancy' | 4 | import { VideosRedundancy } from '../../../shared/models/redundancy' |
5 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' | 5 | import { VideoRedundancyModel } from '../../models/redundancy/video-redundancy' |
6 | import { VideoFileModel } from '../../models/video/video-file' | ||
7 | import { downloadWebTorrentVideo } from '../../helpers/webtorrent' | 6 | import { downloadWebTorrentVideo } from '../../helpers/webtorrent' |
8 | import { join } from 'path' | 7 | import { join } from 'path' |
9 | import { move } from 'fs-extra' | 8 | import { move } from 'fs-extra' |
@@ -12,16 +11,31 @@ import { sendCreateCacheFile, sendUpdateCacheFile } from '../activitypub/send' | |||
12 | import { getVideoCacheFileActivityPubUrl, getVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url' | 11 | import { getVideoCacheFileActivityPubUrl, getVideoCacheStreamingPlaylistActivityPubUrl } from '../activitypub/url' |
13 | import { removeVideoRedundancy } from '../redundancy' | 12 | import { removeVideoRedundancy } from '../redundancy' |
14 | import { getOrCreateVideoAndAccountAndChannel } from '../activitypub' | 13 | import { getOrCreateVideoAndAccountAndChannel } from '../activitypub' |
15 | import { VideoStreamingPlaylistModel } from '../../models/video/video-streaming-playlist' | ||
16 | import { VideoModel } from '../../models/video/video' | ||
17 | import { downloadPlaylistSegments } from '../hls' | 14 | import { downloadPlaylistSegments } from '../hls' |
18 | import { CONFIG } from '../../initializers/config' | 15 | import { CONFIG } from '../../initializers/config' |
16 | import { | ||
17 | MStreamingPlaylist, | ||
18 | MStreamingPlaylistVideo, | ||
19 | MVideoAccountLight, | ||
20 | MVideoFile, | ||
21 | MVideoFileVideo, | ||
22 | MVideoRedundancyFileVideo, | ||
23 | MVideoRedundancyStreamingPlaylistVideo, | ||
24 | MVideoRedundancyVideo, | ||
25 | MVideoWithAllFiles | ||
26 | } from '@server/typings/models' | ||
19 | 27 | ||
20 | type CandidateToDuplicate = { | 28 | type CandidateToDuplicate = { |
21 | redundancy: VideosRedundancy, | 29 | redundancy: VideosRedundancy, |
22 | video: VideoModel, | 30 | video: MVideoWithAllFiles, |
23 | files: VideoFileModel[], | 31 | files: MVideoFile[], |
24 | streamingPlaylists: VideoStreamingPlaylistModel[] | 32 | streamingPlaylists: MStreamingPlaylist[] |
33 | } | ||
34 | |||
35 | function isMVideoRedundancyFileVideo ( | ||
36 | o: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo | ||
37 | ): o is MVideoRedundancyFileVideo { | ||
38 | return !!(o as MVideoRedundancyFileVideo).VideoFile | ||
25 | } | 39 | } |
26 | 40 | ||
27 | export class VideosRedundancyScheduler extends AbstractScheduler { | 41 | export class VideosRedundancyScheduler extends AbstractScheduler { |
@@ -102,7 +116,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
102 | } | 116 | } |
103 | } | 117 | } |
104 | 118 | ||
105 | private async extendsRedundancy (redundancyModel: VideoRedundancyModel) { | 119 | private async extendsRedundancy (redundancyModel: MVideoRedundancyVideo) { |
106 | const redundancy = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) | 120 | const redundancy = CONFIG.REDUNDANCY.VIDEOS.STRATEGIES.find(s => s.strategy === redundancyModel.strategy) |
107 | // Redundancy strategy disabled, remove our redundancy instead of extending expiration | 121 | // Redundancy strategy disabled, remove our redundancy instead of extending expiration |
108 | if (!redundancy) { | 122 | if (!redundancy) { |
@@ -172,7 +186,8 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
172 | } | 186 | } |
173 | } | 187 | } |
174 | 188 | ||
175 | private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: VideoModel, file: VideoFileModel) { | 189 | private async createVideoFileRedundancy (redundancy: VideosRedundancy, video: MVideoAccountLight, fileArg: MVideoFile) { |
190 | const file = fileArg as MVideoFileVideo | ||
176 | file.Video = video | 191 | file.Video = video |
177 | 192 | ||
178 | const serverActor = await getServerActor() | 193 | const serverActor = await getServerActor() |
@@ -187,7 +202,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
187 | const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, video.getVideoFilename(file)) | 202 | const destPath = join(CONFIG.STORAGE.REDUNDANCY_DIR, video.getVideoFilename(file)) |
188 | await move(tmpPath, destPath, { overwrite: true }) | 203 | await move(tmpPath, destPath, { overwrite: true }) |
189 | 204 | ||
190 | const createdModel = await VideoRedundancyModel.create({ | 205 | const createdModel: MVideoRedundancyFileVideo = await VideoRedundancyModel.create({ |
191 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), | 206 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), |
192 | url: getVideoCacheFileActivityPubUrl(file), | 207 | url: getVideoCacheFileActivityPubUrl(file), |
193 | fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL), | 208 | fileUrl: video.getVideoRedundancyUrl(file, WEBSERVER.URL), |
@@ -203,7 +218,12 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
203 | logger.info('Duplicated %s - %d -> %s.', video.url, file.resolution, createdModel.url) | 218 | logger.info('Duplicated %s - %d -> %s.', video.url, file.resolution, createdModel.url) |
204 | } | 219 | } |
205 | 220 | ||
206 | private async createStreamingPlaylistRedundancy (redundancy: VideosRedundancy, video: VideoModel, playlist: VideoStreamingPlaylistModel) { | 221 | private async createStreamingPlaylistRedundancy ( |
222 | redundancy: VideosRedundancy, | ||
223 | video: MVideoAccountLight, | ||
224 | playlistArg: MStreamingPlaylist | ||
225 | ) { | ||
226 | const playlist = playlistArg as MStreamingPlaylistVideo | ||
207 | playlist.Video = video | 227 | playlist.Video = video |
208 | 228 | ||
209 | const serverActor = await getServerActor() | 229 | const serverActor = await getServerActor() |
@@ -213,7 +233,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
213 | const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid) | 233 | const destDirectory = join(HLS_REDUNDANCY_DIRECTORY, video.uuid) |
214 | await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT) | 234 | await downloadPlaylistSegments(playlist.playlistUrl, destDirectory, VIDEO_IMPORT_TIMEOUT) |
215 | 235 | ||
216 | const createdModel = await VideoRedundancyModel.create({ | 236 | const createdModel: MVideoRedundancyStreamingPlaylistVideo = await VideoRedundancyModel.create({ |
217 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), | 237 | expiresOn: this.buildNewExpiration(redundancy.minLifetime), |
218 | url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), | 238 | url: getVideoCacheStreamingPlaylistActivityPubUrl(video, playlist), |
219 | fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL), | 239 | fileUrl: playlist.getVideoRedundancyUrl(WEBSERVER.URL), |
@@ -229,7 +249,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
229 | logger.info('Duplicated playlist %s -> %s.', playlist.playlistUrl, createdModel.url) | 249 | logger.info('Duplicated playlist %s -> %s.', playlist.playlistUrl, createdModel.url) |
230 | } | 250 | } |
231 | 251 | ||
232 | private async extendsExpirationOf (redundancy: VideoRedundancyModel, expiresAfterMs: number) { | 252 | private async extendsExpirationOf (redundancy: MVideoRedundancyVideo, expiresAfterMs: number) { |
233 | logger.info('Extending expiration of %s.', redundancy.url) | 253 | logger.info('Extending expiration of %s.', redundancy.url) |
234 | 254 | ||
235 | const serverActor = await getServerActor() | 255 | const serverActor = await getServerActor() |
@@ -243,7 +263,7 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
243 | private async purgeCacheIfNeeded (candidateToDuplicate: CandidateToDuplicate) { | 263 | private async purgeCacheIfNeeded (candidateToDuplicate: CandidateToDuplicate) { |
244 | while (await this.isTooHeavy(candidateToDuplicate)) { | 264 | while (await this.isTooHeavy(candidateToDuplicate)) { |
245 | const redundancy = candidateToDuplicate.redundancy | 265 | const redundancy = candidateToDuplicate.redundancy |
246 | const toDelete = await VideoRedundancyModel.loadOldestLocalThatAlreadyExpired(redundancy.strategy, redundancy.minLifetime) | 266 | const toDelete = await VideoRedundancyModel.loadOldestLocalExpired(redundancy.strategy, redundancy.minLifetime) |
247 | if (!toDelete) return | 267 | if (!toDelete) return |
248 | 268 | ||
249 | await removeVideoRedundancy(toDelete) | 269 | await removeVideoRedundancy(toDelete) |
@@ -263,19 +283,18 @@ export class VideosRedundancyScheduler extends AbstractScheduler { | |||
263 | return new Date(Date.now() + expiresAfterMs) | 283 | return new Date(Date.now() + expiresAfterMs) |
264 | } | 284 | } |
265 | 285 | ||
266 | private buildEntryLogId (object: VideoRedundancyModel) { | 286 | private buildEntryLogId (object: MVideoRedundancyFileVideo | MVideoRedundancyStreamingPlaylistVideo) { |
267 | if (object.VideoFile) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}` | 287 | if (isMVideoRedundancyFileVideo(object)) return `${object.VideoFile.Video.url}-${object.VideoFile.resolution}` |
268 | 288 | ||
269 | return `${object.VideoStreamingPlaylist.playlistUrl}` | 289 | return `${object.VideoStreamingPlaylist.playlistUrl}` |
270 | } | 290 | } |
271 | 291 | ||
272 | private getTotalFileSizes (files: VideoFileModel[], playlists: VideoStreamingPlaylistModel[]) { | 292 | private getTotalFileSizes (files: MVideoFile[], playlists: MStreamingPlaylist[]) { |
273 | const fileReducer = (previous: number, current: VideoFileModel) => previous + current.size | 293 | const fileReducer = (previous: number, current: MVideoFile) => previous + current.size |
274 | 294 | ||
275 | const totalSize = files.reduce(fileReducer, 0) | 295 | const totalSize = files.reduce(fileReducer, 0) |
276 | if (playlists.length === 0) return totalSize | ||
277 | 296 | ||
278 | return totalSize * playlists.length | 297 | return totalSize + (totalSize * playlists.length) |
279 | } | 298 | } |
280 | 299 | ||
281 | private async loadAndRefreshVideo (videoUrl: string) { | 300 | private async loadAndRefreshVideo (videoUrl: string) { |
diff --git a/server/lib/thumbnail.ts b/server/lib/thumbnail.ts index a59773f5a..84791955e 100644 --- a/server/lib/thumbnail.ts +++ b/server/lib/thumbnail.ts | |||
@@ -1,20 +1,20 @@ | |||
1 | import { VideoFileModel } from '../models/video/video-file' | ||
2 | import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' | 1 | import { generateImageFromVideoFile } from '../helpers/ffmpeg-utils' |
3 | import { CONFIG } from '../initializers/config' | 2 | import { CONFIG } from '../initializers/config' |
4 | import { PREVIEWS_SIZE, THUMBNAILS_SIZE, ASSETS_PATH } from '../initializers/constants' | 3 | import { ASSETS_PATH, PREVIEWS_SIZE, THUMBNAILS_SIZE } from '../initializers/constants' |
5 | import { VideoModel } from '../models/video/video' | ||
6 | import { ThumbnailModel } from '../models/video/thumbnail' | 4 | import { ThumbnailModel } from '../models/video/thumbnail' |
7 | import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' | 5 | import { ThumbnailType } from '../../shared/models/videos/thumbnail.type' |
8 | import { processImage } from '../helpers/image-utils' | 6 | import { processImage } from '../helpers/image-utils' |
9 | import { join } from 'path' | 7 | import { join } from 'path' |
10 | import { downloadImage } from '../helpers/requests' | 8 | import { downloadImage } from '../helpers/requests' |
11 | import { VideoPlaylistModel } from '../models/video/video-playlist' | 9 | import { MVideoPlaylistThumbnail } from '../typings/models/video/video-playlist' |
10 | import { MVideoFile, MVideoThumbnail } from '../typings/models' | ||
11 | import { MThumbnail } from '../typings/models/video/thumbnail' | ||
12 | 12 | ||
13 | type ImageSize = { height: number, width: number } | 13 | type ImageSize = { height: number, width: number } |
14 | 14 | ||
15 | function createPlaylistMiniatureFromExisting ( | 15 | function createPlaylistMiniatureFromExisting ( |
16 | inputPath: string, | 16 | inputPath: string, |
17 | playlist: VideoPlaylistModel, | 17 | playlist: MVideoPlaylistThumbnail, |
18 | automaticallyGenerated: boolean, | 18 | automaticallyGenerated: boolean, |
19 | keepOriginal = false, | 19 | keepOriginal = false, |
20 | size?: ImageSize | 20 | size?: ImageSize |
@@ -26,7 +26,7 @@ function createPlaylistMiniatureFromExisting ( | |||
26 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) | 26 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) |
27 | } | 27 | } |
28 | 28 | ||
29 | function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: VideoPlaylistModel, size?: ImageSize) { | 29 | function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: MVideoPlaylistThumbnail, size?: ImageSize) { |
30 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) | 30 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromPlaylist(playlist, size) |
31 | const type = ThumbnailType.MINIATURE | 31 | const type = ThumbnailType.MINIATURE |
32 | 32 | ||
@@ -34,7 +34,7 @@ function createPlaylistMiniatureFromUrl (fileUrl: string, playlist: VideoPlaylis | |||
34 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) | 34 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, existingThumbnail, fileUrl }) |
35 | } | 35 | } |
36 | 36 | ||
37 | function createVideoMiniatureFromUrl (fileUrl: string, video: VideoModel, type: ThumbnailType, size?: ImageSize) { | 37 | function createVideoMiniatureFromUrl (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) { |
38 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | 38 | const { filename, basePath, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) |
39 | const thumbnailCreator = () => downloadImage(fileUrl, basePath, filename, { width, height }) | 39 | const thumbnailCreator = () => downloadImage(fileUrl, basePath, filename, { width, height }) |
40 | 40 | ||
@@ -43,7 +43,7 @@ function createVideoMiniatureFromUrl (fileUrl: string, video: VideoModel, type: | |||
43 | 43 | ||
44 | function createVideoMiniatureFromExisting ( | 44 | function createVideoMiniatureFromExisting ( |
45 | inputPath: string, | 45 | inputPath: string, |
46 | video: VideoModel, | 46 | video: MVideoThumbnail, |
47 | type: ThumbnailType, | 47 | type: ThumbnailType, |
48 | automaticallyGenerated: boolean, | 48 | automaticallyGenerated: boolean, |
49 | size?: ImageSize | 49 | size?: ImageSize |
@@ -54,7 +54,7 @@ function createVideoMiniatureFromExisting ( | |||
54 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) | 54 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated, existingThumbnail }) |
55 | } | 55 | } |
56 | 56 | ||
57 | function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, type: ThumbnailType) { | 57 | function generateVideoMiniature (video: MVideoThumbnail, videoFile: MVideoFile, type: ThumbnailType) { |
58 | const input = video.getVideoFilePath(videoFile) | 58 | const input = video.getVideoFilePath(videoFile) |
59 | 59 | ||
60 | const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type) | 60 | const { filename, basePath, height, width, existingThumbnail, outputPath } = buildMetadataFromVideo(video, type) |
@@ -65,7 +65,7 @@ function generateVideoMiniature (video: VideoModel, videoFile: VideoFileModel, t | |||
65 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated: true, existingThumbnail }) | 65 | return createThumbnailFromFunction({ thumbnailCreator, filename, height, width, type, automaticallyGenerated: true, existingThumbnail }) |
66 | } | 66 | } |
67 | 67 | ||
68 | function createPlaceholderThumbnail (fileUrl: string, video: VideoModel, type: ThumbnailType, size: ImageSize) { | 68 | function createPlaceholderThumbnail (fileUrl: string, video: MVideoThumbnail, type: ThumbnailType, size: ImageSize) { |
69 | const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) | 69 | const { filename, height, width, existingThumbnail } = buildMetadataFromVideo(video, type, size) |
70 | 70 | ||
71 | const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() | 71 | const thumbnail = existingThumbnail ? existingThumbnail : new ThumbnailModel() |
@@ -90,7 +90,7 @@ export { | |||
90 | createPlaylistMiniatureFromExisting | 90 | createPlaylistMiniatureFromExisting |
91 | } | 91 | } |
92 | 92 | ||
93 | function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSize) { | 93 | function buildMetadataFromPlaylist (playlist: MVideoPlaylistThumbnail, size: ImageSize) { |
94 | const filename = playlist.generateThumbnailName() | 94 | const filename = playlist.generateThumbnailName() |
95 | const basePath = CONFIG.STORAGE.THUMBNAILS_DIR | 95 | const basePath = CONFIG.STORAGE.THUMBNAILS_DIR |
96 | 96 | ||
@@ -104,7 +104,7 @@ function buildMetadataFromPlaylist (playlist: VideoPlaylistModel, size: ImageSiz | |||
104 | } | 104 | } |
105 | } | 105 | } |
106 | 106 | ||
107 | function buildMetadataFromVideo (video: VideoModel, type: ThumbnailType, size?: ImageSize) { | 107 | function buildMetadataFromVideo (video: MVideoThumbnail, type: ThumbnailType, size?: ImageSize) { |
108 | const existingThumbnail = Array.isArray(video.Thumbnails) | 108 | const existingThumbnail = Array.isArray(video.Thumbnails) |
109 | ? video.Thumbnails.find(t => t.type === type) | 109 | ? video.Thumbnails.find(t => t.type === type) |
110 | : undefined | 110 | : undefined |
@@ -148,7 +148,7 @@ async function createThumbnailFromFunction (parameters: { | |||
148 | type: ThumbnailType, | 148 | type: ThumbnailType, |
149 | automaticallyGenerated?: boolean, | 149 | automaticallyGenerated?: boolean, |
150 | fileUrl?: string, | 150 | fileUrl?: string, |
151 | existingThumbnail?: ThumbnailModel | 151 | existingThumbnail?: MThumbnail |
152 | }) { | 152 | }) { |
153 | const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters | 153 | const { thumbnailCreator, filename, width, height, type, existingThumbnail, automaticallyGenerated = null, fileUrl = null } = parameters |
154 | 154 | ||
diff --git a/server/lib/user.ts b/server/lib/user.ts index 0e4007770..c45438d95 100644 --- a/server/lib/user.ts +++ b/server/lib/user.ts | |||
@@ -2,10 +2,8 @@ import * as uuidv4 from 'uuid/v4' | |||
2 | import { ActivityPubActorType } from '../../shared/models/activitypub' | 2 | import { ActivityPubActorType } from '../../shared/models/activitypub' |
3 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants' | 3 | import { SERVER_ACTOR_NAME, WEBSERVER } from '../initializers/constants' |
4 | import { AccountModel } from '../models/account/account' | 4 | import { AccountModel } from '../models/account/account' |
5 | import { UserModel } from '../models/account/user' | ||
6 | import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub' | 5 | import { buildActorInstance, getAccountActivityPubUrl, setAsyncActorKeys } from './activitypub' |
7 | import { createVideoChannel } from './video-channel' | 6 | import { createLocalVideoChannel } from './video-channel' |
8 | import { VideoChannelModel } from '../models/video/video-channel' | ||
9 | import { ActorModel } from '../models/activitypub/actor' | 7 | import { ActorModel } from '../models/activitypub/actor' |
10 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' | 8 | import { UserNotificationSettingModel } from '../models/account/user-notification-setting' |
11 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users' | 9 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../shared/models/users' |
@@ -14,14 +12,17 @@ import { sequelizeTypescript } from '../initializers/database' | |||
14 | import { Transaction } from 'sequelize/types' | 12 | import { Transaction } from 'sequelize/types' |
15 | import { Redis } from './redis' | 13 | import { Redis } from './redis' |
16 | import { Emailer } from './emailer' | 14 | import { Emailer } from './emailer' |
15 | import { MAccountDefault, MActorDefault, MChannelActor } from '../typings/models' | ||
16 | import { MUser, MUserDefault, MUserId } from '../typings/models/user' | ||
17 | 17 | ||
18 | type ChannelNames = { name: string, displayName: string } | 18 | type ChannelNames = { name: string, displayName: string } |
19 | |||
19 | async function createUserAccountAndChannelAndPlaylist (parameters: { | 20 | async function createUserAccountAndChannelAndPlaylist (parameters: { |
20 | userToCreate: UserModel, | 21 | userToCreate: MUser, |
21 | userDisplayName?: string, | 22 | userDisplayName?: string, |
22 | channelNames?: ChannelNames, | 23 | channelNames?: ChannelNames, |
23 | validateUser?: boolean | 24 | validateUser?: boolean |
24 | }) { | 25 | }): Promise<{ user: MUserDefault, account: MAccountDefault, videoChannel: MChannelActor }> { |
25 | const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters | 26 | const { userToCreate, userDisplayName, channelNames, validateUser = true } = parameters |
26 | 27 | ||
27 | const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { | 28 | const { user, account, videoChannel } = await sequelizeTypescript.transaction(async t => { |
@@ -30,7 +31,7 @@ async function createUserAccountAndChannelAndPlaylist (parameters: { | |||
30 | validate: validateUser | 31 | validate: validateUser |
31 | } | 32 | } |
32 | 33 | ||
33 | const userCreated = await userToCreate.save(userOptions) | 34 | const userCreated: MUserDefault = await userToCreate.save(userOptions) |
34 | userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t) | 35 | userCreated.NotificationSetting = await createDefaultUserNotificationSettings(userCreated, t) |
35 | 36 | ||
36 | const accountCreated = await createLocalAccountWithoutKeys({ | 37 | const accountCreated = await createLocalAccountWithoutKeys({ |
@@ -43,22 +44,22 @@ async function createUserAccountAndChannelAndPlaylist (parameters: { | |||
43 | userCreated.Account = accountCreated | 44 | userCreated.Account = accountCreated |
44 | 45 | ||
45 | const channelAttributes = await buildChannelAttributes(userCreated, channelNames) | 46 | const channelAttributes = await buildChannelAttributes(userCreated, channelNames) |
46 | const videoChannel = await createVideoChannel(channelAttributes, accountCreated, t) | 47 | const videoChannel = await createLocalVideoChannel(channelAttributes, accountCreated, t) |
47 | 48 | ||
48 | const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t) | 49 | const videoPlaylist = await createWatchLaterPlaylist(accountCreated, t) |
49 | 50 | ||
50 | return { user: userCreated, account: accountCreated, videoChannel, videoPlaylist } | 51 | return { user: userCreated, account: accountCreated, videoChannel, videoPlaylist } |
51 | }) | 52 | }) |
52 | 53 | ||
53 | const [ accountKeys, channelKeys ] = await Promise.all([ | 54 | const [ accountActorWithKeys, channelActorWithKeys ] = await Promise.all([ |
54 | setAsyncActorKeys(account.Actor), | 55 | setAsyncActorKeys(account.Actor), |
55 | setAsyncActorKeys(videoChannel.Actor) | 56 | setAsyncActorKeys(videoChannel.Actor) |
56 | ]) | 57 | ]) |
57 | 58 | ||
58 | account.Actor = accountKeys | 59 | account.Actor = accountActorWithKeys |
59 | videoChannel.Actor = channelKeys | 60 | videoChannel.Actor = channelActorWithKeys |
60 | 61 | ||
61 | return { user, account, videoChannel } as { user: UserModel, account: AccountModel, videoChannel: VideoChannelModel } | 62 | return { user, account, videoChannel } |
62 | } | 63 | } |
63 | 64 | ||
64 | async function createLocalAccountWithoutKeys (parameters: { | 65 | async function createLocalAccountWithoutKeys (parameters: { |
@@ -73,7 +74,7 @@ async function createLocalAccountWithoutKeys (parameters: { | |||
73 | const url = getAccountActivityPubUrl(name) | 74 | const url = getAccountActivityPubUrl(name) |
74 | 75 | ||
75 | const actorInstance = buildActorInstance(type, url, name) | 76 | const actorInstance = buildActorInstance(type, url, name) |
76 | const actorInstanceCreated = await actorInstance.save({ transaction: t }) | 77 | const actorInstanceCreated: MActorDefault = await actorInstance.save({ transaction: t }) |
77 | 78 | ||
78 | const accountInstance = new AccountModel({ | 79 | const accountInstance = new AccountModel({ |
79 | name: displayName || name, | 80 | name: displayName || name, |
@@ -82,7 +83,7 @@ async function createLocalAccountWithoutKeys (parameters: { | |||
82 | actorId: actorInstanceCreated.id | 83 | actorId: actorInstanceCreated.id |
83 | }) | 84 | }) |
84 | 85 | ||
85 | const accountInstanceCreated = await accountInstance.save({ transaction: t }) | 86 | const accountInstanceCreated: MAccountDefault = await accountInstance.save({ transaction: t }) |
86 | accountInstanceCreated.Actor = actorInstanceCreated | 87 | accountInstanceCreated.Actor = actorInstanceCreated |
87 | 88 | ||
88 | return accountInstanceCreated | 89 | return accountInstanceCreated |
@@ -102,7 +103,7 @@ async function createApplicationActor (applicationId: number) { | |||
102 | return accountCreated | 103 | return accountCreated |
103 | } | 104 | } |
104 | 105 | ||
105 | async function sendVerifyUserEmail (user: UserModel, isPendingEmail = false) { | 106 | async function sendVerifyUserEmail (user: MUser, isPendingEmail = false) { |
106 | const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id) | 107 | const verificationString = await Redis.Instance.setVerifyEmailVerificationString(user.id) |
107 | let url = WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString | 108 | let url = WEBSERVER.URL + '/verify-account/email?userId=' + user.id + '&verificationString=' + verificationString |
108 | 109 | ||
@@ -124,7 +125,7 @@ export { | |||
124 | 125 | ||
125 | // --------------------------------------------------------------------------- | 126 | // --------------------------------------------------------------------------- |
126 | 127 | ||
127 | function createDefaultUserNotificationSettings (user: UserModel, t: Transaction | undefined) { | 128 | function createDefaultUserNotificationSettings (user: MUserId, t: Transaction | undefined) { |
128 | const values: UserNotificationSetting & { userId: number } = { | 129 | const values: UserNotificationSetting & { userId: number } = { |
129 | userId: user.id, | 130 | userId: user.id, |
130 | newVideoFromSubscription: UserNotificationSettingValue.WEB, | 131 | newVideoFromSubscription: UserNotificationSettingValue.WEB, |
@@ -137,13 +138,14 @@ function createDefaultUserNotificationSettings (user: UserModel, t: Transaction | |||
137 | newUserRegistration: UserNotificationSettingValue.WEB, | 138 | newUserRegistration: UserNotificationSettingValue.WEB, |
138 | commentMention: UserNotificationSettingValue.WEB, | 139 | commentMention: UserNotificationSettingValue.WEB, |
139 | newFollow: UserNotificationSettingValue.WEB, | 140 | newFollow: UserNotificationSettingValue.WEB, |
140 | newInstanceFollower: UserNotificationSettingValue.WEB | 141 | newInstanceFollower: UserNotificationSettingValue.WEB, |
142 | autoInstanceFollowing: UserNotificationSettingValue.WEB | ||
141 | } | 143 | } |
142 | 144 | ||
143 | return UserNotificationSettingModel.create(values, { transaction: t }) | 145 | return UserNotificationSettingModel.create(values, { transaction: t }) |
144 | } | 146 | } |
145 | 147 | ||
146 | async function buildChannelAttributes (user: UserModel, channelNames?: ChannelNames) { | 148 | async function buildChannelAttributes (user: MUser, channelNames?: ChannelNames) { |
147 | if (channelNames) return channelNames | 149 | if (channelNames) return channelNames |
148 | 150 | ||
149 | let channelName = user.username + '_channel' | 151 | let channelName = user.username + '_channel' |
diff --git a/server/lib/video-blacklist.ts b/server/lib/video-blacklist.ts index bdaecd8e2..1dd45b76d 100644 --- a/server/lib/video-blacklist.ts +++ b/server/lib/video-blacklist.ts | |||
@@ -2,16 +2,15 @@ import { Transaction } from 'sequelize' | |||
2 | import { CONFIG } from '../initializers/config' | 2 | import { CONFIG } from '../initializers/config' |
3 | import { UserRight, VideoBlacklistType } from '../../shared/models' | 3 | import { UserRight, VideoBlacklistType } from '../../shared/models' |
4 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | 4 | import { VideoBlacklistModel } from '../models/video/video-blacklist' |
5 | import { UserModel } from '../models/account/user' | ||
6 | import { VideoModel } from '../models/video/video' | ||
7 | import { logger } from '../helpers/logger' | 5 | import { logger } from '../helpers/logger' |
8 | import { UserAdminFlag } from '../../shared/models/users/user-flag.model' | 6 | import { UserAdminFlag } from '../../shared/models/users/user-flag.model' |
9 | import { Hooks } from './plugins/hooks' | 7 | import { Hooks } from './plugins/hooks' |
10 | import { Notifier } from './notifier' | 8 | import { Notifier } from './notifier' |
9 | import { MUser, MVideoBlacklistVideo, MVideoWithBlacklistLight } from '@server/typings/models' | ||
11 | 10 | ||
12 | async function autoBlacklistVideoIfNeeded (parameters: { | 11 | async function autoBlacklistVideoIfNeeded (parameters: { |
13 | video: VideoModel, | 12 | video: MVideoWithBlacklistLight, |
14 | user?: UserModel, | 13 | user?: MUser, |
15 | isRemote: boolean, | 14 | isRemote: boolean, |
16 | isNew: boolean, | 15 | isNew: boolean, |
17 | notify?: boolean, | 16 | notify?: boolean, |
@@ -32,7 +31,7 @@ async function autoBlacklistVideoIfNeeded (parameters: { | |||
32 | reason: 'Auto-blacklisted. Moderator review required.', | 31 | reason: 'Auto-blacklisted. Moderator review required.', |
33 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED | 32 | type: VideoBlacklistType.AUTO_BEFORE_PUBLISHED |
34 | } | 33 | } |
35 | const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate({ | 34 | const [ videoBlacklist ] = await VideoBlacklistModel.findOrCreate<MVideoBlacklistVideo>({ |
36 | where: { | 35 | where: { |
37 | videoId: video.id | 36 | videoId: video.id |
38 | }, | 37 | }, |
@@ -41,7 +40,9 @@ async function autoBlacklistVideoIfNeeded (parameters: { | |||
41 | }) | 40 | }) |
42 | video.VideoBlacklist = videoBlacklist | 41 | video.VideoBlacklist = videoBlacklist |
43 | 42 | ||
44 | if (notify) Notifier.Instance.notifyOnVideoAutoBlacklist(video) | 43 | videoBlacklist.Video = video |
44 | |||
45 | if (notify) Notifier.Instance.notifyOnVideoAutoBlacklist(videoBlacklist) | ||
45 | 46 | ||
46 | logger.info('Video %s auto-blacklisted.', video.uuid) | 47 | logger.info('Video %s auto-blacklisted.', video.uuid) |
47 | 48 | ||
@@ -49,10 +50,10 @@ async function autoBlacklistVideoIfNeeded (parameters: { | |||
49 | } | 50 | } |
50 | 51 | ||
51 | async function autoBlacklistNeeded (parameters: { | 52 | async function autoBlacklistNeeded (parameters: { |
52 | video: VideoModel, | 53 | video: MVideoWithBlacklistLight, |
53 | isRemote: boolean, | 54 | isRemote: boolean, |
54 | isNew: boolean, | 55 | isNew: boolean, |
55 | user?: UserModel | 56 | user?: MUser |
56 | }) { | 57 | }) { |
57 | const { user, video, isRemote, isNew } = parameters | 58 | const { user, video, isRemote, isNew } = parameters |
58 | 59 | ||
diff --git a/server/lib/video-channel.ts b/server/lib/video-channel.ts index ee0482c36..41eab456b 100644 --- a/server/lib/video-channel.ts +++ b/server/lib/video-channel.ts | |||
@@ -1,12 +1,19 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import * as uuidv4 from 'uuid/v4' | 2 | import * as uuidv4 from 'uuid/v4' |
3 | import { VideoChannelCreate } from '../../shared/models' | 3 | import { VideoChannelCreate } from '../../shared/models' |
4 | import { AccountModel } from '../models/account/account' | ||
5 | import { VideoChannelModel } from '../models/video/video-channel' | 4 | import { VideoChannelModel } from '../models/video/video-channel' |
6 | import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUrl } from './activitypub' | 5 | import { buildActorInstance, federateVideoIfNeeded, getVideoChannelActivityPubUrl } from './activitypub' |
7 | import { VideoModel } from '../models/video/video' | 6 | import { VideoModel } from '../models/video/video' |
7 | import { MAccountId, MChannelDefault, MChannelId } from '../typings/models' | ||
8 | 8 | ||
9 | async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account: AccountModel, t: Sequelize.Transaction) { | 9 | type CustomVideoChannelModelAccount <T extends MAccountId> = MChannelDefault & |
10 | { Account?: T } | ||
11 | |||
12 | async function createLocalVideoChannel <T extends MAccountId> ( | ||
13 | videoChannelInfo: VideoChannelCreate, | ||
14 | account: T, | ||
15 | t: Sequelize.Transaction | ||
16 | ): Promise<CustomVideoChannelModelAccount<T>> { | ||
10 | const uuid = uuidv4() | 17 | const uuid = uuidv4() |
11 | const url = getVideoChannelActivityPubUrl(videoChannelInfo.name) | 18 | const url = getVideoChannelActivityPubUrl(videoChannelInfo.name) |
12 | const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name, uuid) | 19 | const actorInstance = buildActorInstance('Group', url, videoChannelInfo.name, uuid) |
@@ -21,10 +28,10 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account | |||
21 | actorId: actorInstanceCreated.id | 28 | actorId: actorInstanceCreated.id |
22 | } | 29 | } |
23 | 30 | ||
24 | const videoChannel = VideoChannelModel.build(videoChannelData) | 31 | const videoChannel = new VideoChannelModel(videoChannelData) |
25 | 32 | ||
26 | const options = { transaction: t } | 33 | const options = { transaction: t } |
27 | const videoChannelCreated = await videoChannel.save(options) | 34 | const videoChannelCreated: CustomVideoChannelModelAccount<T> = await videoChannel.save(options) as MChannelDefault |
28 | 35 | ||
29 | // Do not forget to add Account/Actor information to the created video channel | 36 | // Do not forget to add Account/Actor information to the created video channel |
30 | videoChannelCreated.Account = account | 37 | videoChannelCreated.Account = account |
@@ -34,7 +41,7 @@ async function createVideoChannel (videoChannelInfo: VideoChannelCreate, account | |||
34 | return videoChannelCreated | 41 | return videoChannelCreated |
35 | } | 42 | } |
36 | 43 | ||
37 | async function federateAllVideosOfChannel (videoChannel: VideoChannelModel) { | 44 | async function federateAllVideosOfChannel (videoChannel: MChannelId) { |
38 | const videoIds = await VideoModel.getAllIdsFromChannel(videoChannel) | 45 | const videoIds = await VideoModel.getAllIdsFromChannel(videoChannel) |
39 | 46 | ||
40 | for (const videoId of videoIds) { | 47 | for (const videoId of videoIds) { |
@@ -47,6 +54,6 @@ async function federateAllVideosOfChannel (videoChannel: VideoChannelModel) { | |||
47 | // --------------------------------------------------------------------------- | 54 | // --------------------------------------------------------------------------- |
48 | 55 | ||
49 | export { | 56 | export { |
50 | createVideoChannel, | 57 | createLocalVideoChannel, |
51 | federateAllVideosOfChannel | 58 | federateAllVideosOfChannel |
52 | } | 59 | } |
diff --git a/server/lib/video-comment.ts b/server/lib/video-comment.ts index 449aa74cb..bb811bd2c 100644 --- a/server/lib/video-comment.ts +++ b/server/lib/video-comment.ts | |||
@@ -1,17 +1,16 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { ResultList } from '../../shared/models' | 2 | import { ResultList } from '../../shared/models' |
3 | import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model' | 3 | import { VideoCommentThreadTree } from '../../shared/models/videos/video-comment.model' |
4 | import { AccountModel } from '../models/account/account' | ||
5 | import { VideoModel } from '../models/video/video' | ||
6 | import { VideoCommentModel } from '../models/video/video-comment' | 4 | import { VideoCommentModel } from '../models/video/video-comment' |
7 | import { getVideoCommentActivityPubUrl } from './activitypub' | 5 | import { getVideoCommentActivityPubUrl } from './activitypub' |
8 | import { sendCreateVideoComment } from './activitypub/send' | 6 | import { sendCreateVideoComment } from './activitypub/send' |
7 | import { MAccountDefault, MComment, MCommentOwnerVideoReply, MVideoFullLight } from '../typings/models' | ||
9 | 8 | ||
10 | async function createVideoComment (obj: { | 9 | async function createVideoComment (obj: { |
11 | text: string, | 10 | text: string, |
12 | inReplyToComment: VideoCommentModel | null, | 11 | inReplyToComment: MComment | null, |
13 | video: VideoModel | 12 | video: MVideoFullLight, |
14 | account: AccountModel | 13 | account: MAccountDefault |
15 | }, t: Sequelize.Transaction) { | 14 | }, t: Sequelize.Transaction) { |
16 | let originCommentId: number | null = null | 15 | let originCommentId: number | null = null |
17 | let inReplyToCommentId: number | null = null | 16 | let inReplyToCommentId: number | null = null |
@@ -32,7 +31,7 @@ async function createVideoComment (obj: { | |||
32 | 31 | ||
33 | comment.url = getVideoCommentActivityPubUrl(obj.video, comment) | 32 | comment.url = getVideoCommentActivityPubUrl(obj.video, comment) |
34 | 33 | ||
35 | const savedComment = await comment.save({ transaction: t }) | 34 | const savedComment: MCommentOwnerVideoReply = await comment.save({ transaction: t }) |
36 | savedComment.InReplyToVideoComment = obj.inReplyToComment | 35 | savedComment.InReplyToVideoComment = obj.inReplyToComment |
37 | savedComment.Video = obj.video | 36 | savedComment.Video = obj.video |
38 | savedComment.Account = obj.account | 37 | savedComment.Account = obj.account |
diff --git a/server/lib/video-playlist.ts b/server/lib/video-playlist.ts index 6e214e60f..29b70cfda 100644 --- a/server/lib/video-playlist.ts +++ b/server/lib/video-playlist.ts | |||
@@ -1,12 +1,13 @@ | |||
1 | import * as Sequelize from 'sequelize' | 1 | import * as Sequelize from 'sequelize' |
2 | import { AccountModel } from '../models/account/account' | ||
3 | import { VideoPlaylistModel } from '../models/video/video-playlist' | 2 | import { VideoPlaylistModel } from '../models/video/video-playlist' |
4 | import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model' | 3 | import { VideoPlaylistPrivacy } from '../../shared/models/videos/playlist/video-playlist-privacy.model' |
5 | import { getVideoPlaylistActivityPubUrl } from './activitypub' | 4 | import { getVideoPlaylistActivityPubUrl } from './activitypub' |
6 | import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model' | 5 | import { VideoPlaylistType } from '../../shared/models/videos/playlist/video-playlist-type.model' |
6 | import { MAccount } from '../typings/models' | ||
7 | import { MVideoPlaylistOwner } from '../typings/models/video/video-playlist' | ||
7 | 8 | ||
8 | async function createWatchLaterPlaylist (account: AccountModel, t: Sequelize.Transaction) { | 9 | async function createWatchLaterPlaylist (account: MAccount, t: Sequelize.Transaction) { |
9 | const videoPlaylist = new VideoPlaylistModel({ | 10 | const videoPlaylist: MVideoPlaylistOwner = new VideoPlaylistModel({ |
10 | name: 'Watch later', | 11 | name: 'Watch later', |
11 | privacy: VideoPlaylistPrivacy.PRIVATE, | 12 | privacy: VideoPlaylistPrivacy.PRIVATE, |
12 | type: VideoPlaylistType.WATCH_LATER, | 13 | type: VideoPlaylistType.WATCH_LATER, |
diff --git a/server/lib/video-transcoding.ts b/server/lib/video-transcoding.ts index ba6b29163..a204c0c63 100644 --- a/server/lib/video-transcoding.ts +++ b/server/lib/video-transcoding.ts | |||
@@ -5,16 +5,16 @@ import { ensureDir, move, remove, stat } from 'fs-extra' | |||
5 | import { logger } from '../helpers/logger' | 5 | import { logger } from '../helpers/logger' |
6 | import { VideoResolution } from '../../shared/models/videos' | 6 | import { VideoResolution } from '../../shared/models/videos' |
7 | import { VideoFileModel } from '../models/video/video-file' | 7 | import { VideoFileModel } from '../models/video/video-file' |
8 | import { VideoModel } from '../models/video/video' | ||
9 | import { updateMasterHLSPlaylist, updateSha256Segments } from './hls' | 8 | import { updateMasterHLSPlaylist, updateSha256Segments } from './hls' |
10 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | 9 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' |
11 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' | 10 | import { VideoStreamingPlaylistType } from '../../shared/models/videos/video-streaming-playlist.type' |
12 | import { CONFIG } from '../initializers/config' | 11 | import { CONFIG } from '../initializers/config' |
12 | import { MVideoFile, MVideoWithFile, MVideoWithFileThumbnail } from '@server/typings/models' | ||
13 | 13 | ||
14 | /** | 14 | /** |
15 | * Optimize the original video file and replace it. The resolution is not changed. | 15 | * Optimize the original video file and replace it. The resolution is not changed. |
16 | */ | 16 | */ |
17 | async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFileModel) { | 17 | async function optimizeVideofile (video: MVideoWithFile, inputVideoFileArg?: MVideoFile) { |
18 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 18 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
19 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | 19 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR |
20 | const newExtname = '.mp4' | 20 | const newExtname = '.mp4' |
@@ -57,7 +57,7 @@ async function optimizeVideofile (video: VideoModel, inputVideoFileArg?: VideoFi | |||
57 | /** | 57 | /** |
58 | * Transcode the original video file to a lower resolution. | 58 | * Transcode the original video file to a lower resolution. |
59 | */ | 59 | */ |
60 | async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoResolution, isPortrait: boolean) { | 60 | async function transcodeOriginalVideofile (video: MVideoWithFile, resolution: VideoResolution, isPortrait: boolean) { |
61 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 61 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
62 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | 62 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR |
63 | const extname = '.mp4' | 63 | const extname = '.mp4' |
@@ -87,7 +87,7 @@ async function transcodeOriginalVideofile (video: VideoModel, resolution: VideoR | |||
87 | return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) | 87 | return onVideoFileTranscoding(video, newVideoFile, videoTranscodedPath, videoOutputPath) |
88 | } | 88 | } |
89 | 89 | ||
90 | async function mergeAudioVideofile (video: VideoModel, resolution: VideoResolution) { | 90 | async function mergeAudioVideofile (video: MVideoWithFileThumbnail, resolution: VideoResolution) { |
91 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR | 91 | const videosDirectory = CONFIG.STORAGE.VIDEOS_DIR |
92 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR | 92 | const transcodeDirectory = CONFIG.STORAGE.TMP_DIR |
93 | const newExtname = '.mp4' | 93 | const newExtname = '.mp4' |
@@ -117,7 +117,7 @@ async function mergeAudioVideofile (video: VideoModel, resolution: VideoResoluti | |||
117 | return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) | 117 | return onVideoFileTranscoding(video, inputVideoFile, videoTranscodedPath, videoOutputPath) |
118 | } | 118 | } |
119 | 119 | ||
120 | async function generateHlsPlaylist (video: VideoModel, resolution: VideoResolution, isPortraitMode: boolean) { | 120 | async function generateHlsPlaylist (video: MVideoWithFile, resolution: VideoResolution, isPortraitMode: boolean) { |
121 | const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) | 121 | const baseHlsDirectory = join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid) |
122 | await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)) | 122 | await ensureDir(join(HLS_STREAMING_PLAYLIST_DIRECTORY, video.uuid)) |
123 | 123 | ||
@@ -165,14 +165,14 @@ export { | |||
165 | 165 | ||
166 | // --------------------------------------------------------------------------- | 166 | // --------------------------------------------------------------------------- |
167 | 167 | ||
168 | async function onVideoFileTranscoding (video: VideoModel, videoFile: VideoFileModel, transcodingPath: string, outputPath: string) { | 168 | async function onVideoFileTranscoding (video: MVideoWithFile, videoFile: MVideoFile, transcodingPath: string, outputPath: string) { |
169 | const stats = await stat(transcodingPath) | 169 | const stats = await stat(transcodingPath) |
170 | const fps = await getVideoFileFPS(transcodingPath) | 170 | const fps = await getVideoFileFPS(transcodingPath) |
171 | 171 | ||
172 | await move(transcodingPath, outputPath) | 172 | await move(transcodingPath, outputPath) |
173 | 173 | ||
174 | videoFile.set('size', stats.size) | 174 | videoFile.size = stats.size |
175 | videoFile.set('fps', fps) | 175 | videoFile.fps = fps |
176 | 176 | ||
177 | await video.createTorrentAndSetInfoHash(videoFile) | 177 | await video.createTorrentAndSetInfoHash(videoFile) |
178 | 178 | ||
diff --git a/server/middlewares/activitypub.ts b/server/middlewares/activitypub.ts index b1e5b5236..bea213d27 100644 --- a/server/middlewares/activitypub.ts +++ b/server/middlewares/activitypub.ts | |||
@@ -101,6 +101,8 @@ async function checkJsonLDSignature (req: Request, res: Response) { | |||
101 | const verified = await isJsonLDSignatureVerified(actor, req.body) | 101 | const verified = await isJsonLDSignatureVerified(actor, req.body) |
102 | 102 | ||
103 | if (verified !== true) { | 103 | if (verified !== true) { |
104 | logger.warn('Signature not verified.', req.body) | ||
105 | |||
104 | res.sendStatus(403) | 106 | res.sendStatus(403) |
105 | return false | 107 | return false |
106 | } | 108 | } |
diff --git a/server/middlewares/validators/follows.ts b/server/middlewares/validators/follows.ts index c3d772297..788735663 100644 --- a/server/middlewares/validators/follows.ts +++ b/server/middlewares/validators/follows.ts | |||
@@ -10,6 +10,7 @@ import { areValidationErrors } from './utils' | |||
10 | import { ActorModel } from '../../models/activitypub/actor' | 10 | import { ActorModel } from '../../models/activitypub/actor' |
11 | import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' | 11 | import { loadActorUrlOrGetFromWebfinger } from '../../helpers/webfinger' |
12 | import { isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' | 12 | import { isValidActorHandle } from '../../helpers/custom-validators/activitypub/actor' |
13 | import { MActorFollowActorsDefault } from '@server/typings/models' | ||
13 | 14 | ||
14 | const followValidator = [ | 15 | const followValidator = [ |
15 | body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'), | 16 | body('hosts').custom(isEachUniqueHostValid).withMessage('Should have an array of unique hosts'), |
@@ -65,7 +66,7 @@ const getFollowerValidator = [ | |||
65 | 66 | ||
66 | if (areValidationErrors(req, res)) return | 67 | if (areValidationErrors(req, res)) return |
67 | 68 | ||
68 | let follow: ActorFollowModel | 69 | let follow: MActorFollowActorsDefault |
69 | try { | 70 | try { |
70 | const actorUrl = await loadActorUrlOrGetFromWebfinger(req.params.nameWithHost) | 71 | const actorUrl = await loadActorUrlOrGetFromWebfinger(req.params.nameWithHost) |
71 | const actor = await ActorModel.loadByUrl(actorUrl) | 72 | const actor = await ActorModel.loadByUrl(actorUrl) |
diff --git a/server/middlewares/validators/redundancy.ts b/server/middlewares/validators/redundancy.ts index 1fdac0e4e..e65d3b8d3 100644 --- a/server/middlewares/validators/redundancy.ts +++ b/server/middlewares/validators/redundancy.ts | |||
@@ -24,7 +24,7 @@ const videoFileRedundancyGetValidator = [ | |||
24 | if (areValidationErrors(req, res)) return | 24 | if (areValidationErrors(req, res)) return |
25 | if (!await doesVideoExist(req.params.videoId, res)) return | 25 | if (!await doesVideoExist(req.params.videoId, res)) return |
26 | 26 | ||
27 | const video = res.locals.video | 27 | const video = res.locals.videoAll |
28 | const videoFile = video.VideoFiles.find(f => { | 28 | const videoFile = video.VideoFiles.find(f => { |
29 | return f.resolution === req.params.resolution && (!req.params.fps || f.fps === req.params.fps) | 29 | return f.resolution === req.params.resolution && (!req.params.fps || f.fps === req.params.fps) |
30 | }) | 30 | }) |
@@ -50,7 +50,7 @@ const videoPlaylistRedundancyGetValidator = [ | |||
50 | if (areValidationErrors(req, res)) return | 50 | if (areValidationErrors(req, res)) return |
51 | if (!await doesVideoExist(req.params.videoId, res)) return | 51 | if (!await doesVideoExist(req.params.videoId, res)) return |
52 | 52 | ||
53 | const video = res.locals.video | 53 | const video = res.locals.videoAll |
54 | const videoStreamingPlaylist = video.VideoStreamingPlaylists.find(p => p === req.params.streamingPlaylistType) | 54 | const videoStreamingPlaylist = video.VideoStreamingPlaylists.find(p => p === req.params.streamingPlaylistType) |
55 | 55 | ||
56 | if (!videoStreamingPlaylist) return res.status(404).json({ error: 'Video playlist not found.' }) | 56 | if (!videoStreamingPlaylist) return res.status(404).json({ error: 'Video playlist not found.' }) |
diff --git a/server/middlewares/validators/user-notifications.ts b/server/middlewares/validators/user-notifications.ts index 308b32655..fbfcb0a4c 100644 --- a/server/middlewares/validators/user-notifications.ts +++ b/server/middlewares/validators/user-notifications.ts | |||
@@ -43,6 +43,8 @@ const updateNotificationSettingsValidator = [ | |||
43 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new user registration notification setting'), | 43 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new user registration notification setting'), |
44 | body('newInstanceFollower') | 44 | body('newInstanceFollower') |
45 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new instance follower notification setting'), | 45 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new instance follower notification setting'), |
46 | body('autoInstanceFollowing') | ||
47 | .custom(isUserNotificationSettingValid).withMessage('Should have a valid new instance following notification setting'), | ||
46 | 48 | ||
47 | (req: express.Request, res: express.Response, next: express.NextFunction) => { | 49 | (req: express.Request, res: express.Response, next: express.NextFunction) => { |
48 | logger.debug('Checking updateNotificationSettingsValidator parameters', { parameters: req.body }) | 50 | logger.debug('Checking updateNotificationSettingsValidator parameters', { parameters: req.body }) |
diff --git a/server/middlewares/validators/users.ts b/server/middlewares/validators/users.ts index 8ee2ec1f5..544db76d7 100644 --- a/server/middlewares/validators/users.ts +++ b/server/middlewares/validators/users.ts | |||
@@ -4,6 +4,7 @@ import { body, param } from 'express-validator' | |||
4 | import { omit } from 'lodash' | 4 | import { omit } from 'lodash' |
5 | import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' | 5 | import { isIdOrUUIDValid, toBooleanOrNull, toIntOrNull } from '../../helpers/custom-validators/misc' |
6 | import { | 6 | import { |
7 | isNoInstanceConfigWarningModal, isNoWelcomeModal, | ||
7 | isUserAdminFlagsValid, | 8 | isUserAdminFlagsValid, |
8 | isUserAutoPlayVideoValid, | 9 | isUserAutoPlayVideoValid, |
9 | isUserBlockedReasonValid, | 10 | isUserBlockedReasonValid, |
@@ -31,6 +32,7 @@ import { isThemeNameValid } from '../../helpers/custom-validators/plugins' | |||
31 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' | 32 | import { isThemeRegistered } from '../../lib/plugins/theme-utils' |
32 | import { doesVideoExist } from '../../helpers/middlewares' | 33 | import { doesVideoExist } from '../../helpers/middlewares' |
33 | import { UserRole } from '../../../shared/models/users' | 34 | import { UserRole } from '../../../shared/models/users' |
35 | import { MUserDefault } from '@server/typings/models' | ||
34 | 36 | ||
35 | const usersAddValidator = [ | 37 | const usersAddValidator = [ |
36 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), | 38 | body('username').custom(isUserUsernameValid).withMessage('Should have a valid username (lowercase alphanumeric characters)'), |
@@ -215,6 +217,12 @@ const usersUpdateMeValidator = [ | |||
215 | body('theme') | 217 | body('theme') |
216 | .optional() | 218 | .optional() |
217 | .custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'), | 219 | .custom(v => isThemeNameValid(v) && isThemeRegistered(v)).withMessage('Should have a valid theme'), |
220 | body('noInstanceConfigWarningModal') | ||
221 | .optional() | ||
222 | .custom(v => isNoInstanceConfigWarningModal(v)).withMessage('Should have a valid noInstanceConfigWarningModal boolean'), | ||
223 | body('noWelcomeModal') | ||
224 | .optional() | ||
225 | .custom(v => isNoWelcomeModal(v)).withMessage('Should have a valid noWelcomeModal boolean'), | ||
218 | 226 | ||
219 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 227 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
220 | logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) | 228 | logger.debug('Checking usersUpdateMe parameters', { parameters: omit(req.body, 'password') }) |
@@ -462,7 +470,7 @@ async function checkUserNameOrEmailDoesNotAlreadyExist (username: string, email: | |||
462 | return true | 470 | return true |
463 | } | 471 | } |
464 | 472 | ||
465 | async function checkUserExist (finder: () => Bluebird<UserModel>, res: express.Response, abortResponse = true) { | 473 | async function checkUserExist (finder: () => Bluebird<MUserDefault>, res: express.Response, abortResponse = true) { |
466 | const user = await finder() | 474 | const user = await finder() |
467 | 475 | ||
468 | if (!user) { | 476 | if (!user) { |
diff --git a/server/middlewares/validators/videos/video-abuses.ts b/server/middlewares/validators/videos/video-abuses.ts index e27d91bb1..a4aef4024 100644 --- a/server/middlewares/validators/videos/video-abuses.ts +++ b/server/middlewares/validators/videos/video-abuses.ts | |||
@@ -33,7 +33,7 @@ const videoAbuseGetValidator = [ | |||
33 | 33 | ||
34 | if (areValidationErrors(req, res)) return | 34 | if (areValidationErrors(req, res)) return |
35 | if (!await doesVideoExist(req.params.videoId, res)) return | 35 | if (!await doesVideoExist(req.params.videoId, res)) return |
36 | if (!await doesVideoAbuseExist(req.params.id, res.locals.video.id, res)) return | 36 | if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return |
37 | 37 | ||
38 | return next() | 38 | return next() |
39 | } | 39 | } |
@@ -54,7 +54,7 @@ const videoAbuseUpdateValidator = [ | |||
54 | 54 | ||
55 | if (areValidationErrors(req, res)) return | 55 | if (areValidationErrors(req, res)) return |
56 | if (!await doesVideoExist(req.params.videoId, res)) return | 56 | if (!await doesVideoExist(req.params.videoId, res)) return |
57 | if (!await doesVideoAbuseExist(req.params.id, res.locals.video.id, res)) return | 57 | if (!await doesVideoAbuseExist(req.params.id, res.locals.videoAll.id, res)) return |
58 | 58 | ||
59 | return next() | 59 | return next() |
60 | } | 60 | } |
diff --git a/server/middlewares/validators/videos/video-blacklist.ts b/server/middlewares/validators/videos/video-blacklist.ts index 3e8c5b30c..5440e57e7 100644 --- a/server/middlewares/validators/videos/video-blacklist.ts +++ b/server/middlewares/validators/videos/video-blacklist.ts | |||
@@ -14,7 +14,7 @@ const videosBlacklistRemoveValidator = [ | |||
14 | 14 | ||
15 | if (areValidationErrors(req, res)) return | 15 | if (areValidationErrors(req, res)) return |
16 | if (!await doesVideoExist(req.params.videoId, res)) return | 16 | if (!await doesVideoExist(req.params.videoId, res)) return |
17 | if (!await doesVideoBlacklistExist(res.locals.video.id, res)) return | 17 | if (!await doesVideoBlacklistExist(res.locals.videoAll.id, res)) return |
18 | 18 | ||
19 | return next() | 19 | return next() |
20 | } | 20 | } |
@@ -36,7 +36,7 @@ const videosBlacklistAddValidator = [ | |||
36 | if (areValidationErrors(req, res)) return | 36 | if (areValidationErrors(req, res)) return |
37 | if (!await doesVideoExist(req.params.videoId, res)) return | 37 | if (!await doesVideoExist(req.params.videoId, res)) return |
38 | 38 | ||
39 | const video = res.locals.video | 39 | const video = res.locals.videoAll |
40 | if (req.body.unfederate === true && video.remote === true) { | 40 | if (req.body.unfederate === true && video.remote === true) { |
41 | return res | 41 | return res |
42 | .status(409) | 42 | .status(409) |
@@ -59,7 +59,7 @@ const videosBlacklistUpdateValidator = [ | |||
59 | 59 | ||
60 | if (areValidationErrors(req, res)) return | 60 | if (areValidationErrors(req, res)) return |
61 | if (!await doesVideoExist(req.params.videoId, res)) return | 61 | if (!await doesVideoExist(req.params.videoId, res)) return |
62 | if (!await doesVideoBlacklistExist(res.locals.video.id, res)) return | 62 | if (!await doesVideoBlacklistExist(res.locals.videoAll.id, res)) return |
63 | 63 | ||
64 | return next() | 64 | return next() |
65 | } | 65 | } |
diff --git a/server/middlewares/validators/videos/video-captions.ts b/server/middlewares/validators/videos/video-captions.ts index f5610222a..2fb1da5ce 100644 --- a/server/middlewares/validators/videos/video-captions.ts +++ b/server/middlewares/validators/videos/video-captions.ts | |||
@@ -26,7 +26,7 @@ const addVideoCaptionValidator = [ | |||
26 | 26 | ||
27 | // Check if the user who did the request is able to update the video | 27 | // Check if the user who did the request is able to update the video |
28 | const user = res.locals.oauth.token.User | 28 | const user = res.locals.oauth.token.User |
29 | if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) | 29 | if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) |
30 | 30 | ||
31 | return next() | 31 | return next() |
32 | } | 32 | } |
@@ -41,11 +41,11 @@ const deleteVideoCaptionValidator = [ | |||
41 | 41 | ||
42 | if (areValidationErrors(req, res)) return | 42 | if (areValidationErrors(req, res)) return |
43 | if (!await doesVideoExist(req.params.videoId, res)) return | 43 | if (!await doesVideoExist(req.params.videoId, res)) return |
44 | if (!await doesVideoCaptionExist(res.locals.video, req.params.captionLanguage, res)) return | 44 | if (!await doesVideoCaptionExist(res.locals.videoAll, req.params.captionLanguage, res)) return |
45 | 45 | ||
46 | // Check if the user who did the request is able to update the video | 46 | // Check if the user who did the request is able to update the video |
47 | const user = res.locals.oauth.token.User | 47 | const user = res.locals.oauth.token.User |
48 | if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return | 48 | if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return |
49 | 49 | ||
50 | return next() | 50 | return next() |
51 | } | 51 | } |
diff --git a/server/middlewares/validators/videos/video-channels.ts b/server/middlewares/validators/videos/video-channels.ts index 3ee5064fc..d21274527 100644 --- a/server/middlewares/validators/videos/video-channels.ts +++ b/server/middlewares/validators/videos/video-channels.ts | |||
@@ -7,13 +7,13 @@ import { | |||
7 | isVideoChannelSupportValid | 7 | isVideoChannelSupportValid |
8 | } from '../../../helpers/custom-validators/video-channels' | 8 | } from '../../../helpers/custom-validators/video-channels' |
9 | import { logger } from '../../../helpers/logger' | 9 | import { logger } from '../../../helpers/logger' |
10 | import { UserModel } from '../../../models/account/user' | ||
11 | import { VideoChannelModel } from '../../../models/video/video-channel' | 10 | import { VideoChannelModel } from '../../../models/video/video-channel' |
12 | import { areValidationErrors } from '../utils' | 11 | import { areValidationErrors } from '../utils' |
13 | import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' | 12 | import { isActorPreferredUsernameValid } from '../../../helpers/custom-validators/activitypub/actor' |
14 | import { ActorModel } from '../../../models/activitypub/actor' | 13 | import { ActorModel } from '../../../models/activitypub/actor' |
15 | import { isBooleanValid } from '../../../helpers/custom-validators/misc' | 14 | import { isBooleanValid } from '../../../helpers/custom-validators/misc' |
16 | import { doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../../../helpers/middlewares' | 15 | import { doesLocalVideoChannelNameExist, doesVideoChannelNameWithHostExist } from '../../../helpers/middlewares' |
16 | import { MChannelAccountDefault, MUser } from '@server/typings/models' | ||
17 | 17 | ||
18 | const videoChannelsAddValidator = [ | 18 | const videoChannelsAddValidator = [ |
19 | body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), | 19 | body('name').custom(isActorPreferredUsernameValid).withMessage('Should have a valid channel name'), |
@@ -131,7 +131,7 @@ export { | |||
131 | 131 | ||
132 | // --------------------------------------------------------------------------- | 132 | // --------------------------------------------------------------------------- |
133 | 133 | ||
134 | function checkUserCanDeleteVideoChannel (user: UserModel, videoChannel: VideoChannelModel, res: express.Response) { | 134 | function checkUserCanDeleteVideoChannel (user: MUser, videoChannel: MChannelAccountDefault, res: express.Response) { |
135 | if (videoChannel.Actor.isOwned() === false) { | 135 | if (videoChannel.Actor.isOwned() === false) { |
136 | res.status(403) | 136 | res.status(403) |
137 | .json({ error: 'Cannot remove video channel of another server.' }) | 137 | .json({ error: 'Cannot remove video channel of another server.' }) |
diff --git a/server/middlewares/validators/videos/video-comments.ts b/server/middlewares/validators/videos/video-comments.ts index 83a0c24b0..8adbb02ba 100644 --- a/server/middlewares/validators/videos/video-comments.ts +++ b/server/middlewares/validators/videos/video-comments.ts | |||
@@ -4,13 +4,13 @@ import { UserRight } from '../../../../shared' | |||
4 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' | 4 | import { isIdOrUUIDValid, isIdValid } from '../../../helpers/custom-validators/misc' |
5 | import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' | 5 | import { isValidVideoCommentText } from '../../../helpers/custom-validators/video-comments' |
6 | import { logger } from '../../../helpers/logger' | 6 | import { logger } from '../../../helpers/logger' |
7 | import { UserModel } from '../../../models/account/user' | ||
8 | import { VideoModel } from '../../../models/video/video' | ||
9 | import { VideoCommentModel } from '../../../models/video/video-comment' | 7 | import { VideoCommentModel } from '../../../models/video/video-comment' |
10 | import { areValidationErrors } from '../utils' | 8 | import { areValidationErrors } from '../utils' |
11 | import { Hooks } from '../../../lib/plugins/hooks' | 9 | import { Hooks } from '../../../lib/plugins/hooks' |
12 | import { isLocalVideoThreadAccepted, isLocalVideoCommentReplyAccepted, AcceptResult } from '../../../lib/moderation' | 10 | import { AcceptResult, isLocalVideoCommentReplyAccepted, isLocalVideoThreadAccepted } from '../../../lib/moderation' |
13 | import { doesVideoExist } from '../../../helpers/middlewares' | 11 | import { doesVideoExist } from '../../../helpers/middlewares' |
12 | import { MCommentOwner, MVideo, MVideoFullLight, MVideoId } from '../../../typings/models/video' | ||
13 | import { MUser } from '@server/typings/models' | ||
14 | 14 | ||
15 | const listVideoCommentThreadsValidator = [ | 15 | const listVideoCommentThreadsValidator = [ |
16 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), | 16 | param('videoId').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid videoId'), |
@@ -34,7 +34,7 @@ const listVideoThreadCommentsValidator = [ | |||
34 | 34 | ||
35 | if (areValidationErrors(req, res)) return | 35 | if (areValidationErrors(req, res)) return |
36 | if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return | 36 | if (!await doesVideoExist(req.params.videoId, res, 'only-video')) return |
37 | if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.video, res)) return | 37 | if (!await doesVideoCommentThreadExist(req.params.threadId, res.locals.onlyVideo, res)) return |
38 | 38 | ||
39 | return next() | 39 | return next() |
40 | } | 40 | } |
@@ -49,8 +49,8 @@ const addVideoCommentThreadValidator = [ | |||
49 | 49 | ||
50 | if (areValidationErrors(req, res)) return | 50 | if (areValidationErrors(req, res)) return |
51 | if (!await doesVideoExist(req.params.videoId, res)) return | 51 | if (!await doesVideoExist(req.params.videoId, res)) return |
52 | if (!isVideoCommentsEnabled(res.locals.video, res)) return | 52 | if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return |
53 | if (!await isVideoCommentAccepted(req, res, false)) return | 53 | if (!await isVideoCommentAccepted(req, res, res.locals.videoAll,false)) return |
54 | 54 | ||
55 | return next() | 55 | return next() |
56 | } | 56 | } |
@@ -66,9 +66,9 @@ const addVideoCommentReplyValidator = [ | |||
66 | 66 | ||
67 | if (areValidationErrors(req, res)) return | 67 | if (areValidationErrors(req, res)) return |
68 | if (!await doesVideoExist(req.params.videoId, res)) return | 68 | if (!await doesVideoExist(req.params.videoId, res)) return |
69 | if (!isVideoCommentsEnabled(res.locals.video, res)) return | 69 | if (!isVideoCommentsEnabled(res.locals.videoAll, res)) return |
70 | if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return | 70 | if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return |
71 | if (!await isVideoCommentAccepted(req, res, true)) return | 71 | if (!await isVideoCommentAccepted(req, res, res.locals.videoAll, true)) return |
72 | 72 | ||
73 | return next() | 73 | return next() |
74 | } | 74 | } |
@@ -83,7 +83,7 @@ const videoCommentGetValidator = [ | |||
83 | 83 | ||
84 | if (areValidationErrors(req, res)) return | 84 | if (areValidationErrors(req, res)) return |
85 | if (!await doesVideoExist(req.params.videoId, res, 'id')) return | 85 | if (!await doesVideoExist(req.params.videoId, res, 'id')) return |
86 | if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return | 86 | if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoId, res)) return |
87 | 87 | ||
88 | return next() | 88 | return next() |
89 | } | 89 | } |
@@ -98,10 +98,10 @@ const removeVideoCommentValidator = [ | |||
98 | 98 | ||
99 | if (areValidationErrors(req, res)) return | 99 | if (areValidationErrors(req, res)) return |
100 | if (!await doesVideoExist(req.params.videoId, res)) return | 100 | if (!await doesVideoExist(req.params.videoId, res)) return |
101 | if (!await doesVideoCommentExist(req.params.commentId, res.locals.video, res)) return | 101 | if (!await doesVideoCommentExist(req.params.commentId, res.locals.videoAll, res)) return |
102 | 102 | ||
103 | // Check if the user who did the request is able to delete the video | 103 | // Check if the user who did the request is able to delete the video |
104 | if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoComment, res)) return | 104 | if (!checkUserCanDeleteVideoComment(res.locals.oauth.token.User, res.locals.videoCommentFull, res)) return |
105 | 105 | ||
106 | return next() | 106 | return next() |
107 | } | 107 | } |
@@ -120,7 +120,7 @@ export { | |||
120 | 120 | ||
121 | // --------------------------------------------------------------------------- | 121 | // --------------------------------------------------------------------------- |
122 | 122 | ||
123 | async function doesVideoCommentThreadExist (id: number, video: VideoModel, res: express.Response) { | 123 | async function doesVideoCommentThreadExist (id: number, video: MVideoId, res: express.Response) { |
124 | const videoComment = await VideoCommentModel.loadById(id) | 124 | const videoComment = await VideoCommentModel.loadById(id) |
125 | 125 | ||
126 | if (!videoComment) { | 126 | if (!videoComment) { |
@@ -151,7 +151,7 @@ async function doesVideoCommentThreadExist (id: number, video: VideoModel, res: | |||
151 | return true | 151 | return true |
152 | } | 152 | } |
153 | 153 | ||
154 | async function doesVideoCommentExist (id: number, video: VideoModel, res: express.Response) { | 154 | async function doesVideoCommentExist (id: number, video: MVideoId, res: express.Response) { |
155 | const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) | 155 | const videoComment = await VideoCommentModel.loadByIdAndPopulateVideoAndAccountAndReply(id) |
156 | 156 | ||
157 | if (!videoComment) { | 157 | if (!videoComment) { |
@@ -170,11 +170,11 @@ async function doesVideoCommentExist (id: number, video: VideoModel, res: expres | |||
170 | return false | 170 | return false |
171 | } | 171 | } |
172 | 172 | ||
173 | res.locals.videoComment = videoComment | 173 | res.locals.videoCommentFull = videoComment |
174 | return true | 174 | return true |
175 | } | 175 | } |
176 | 176 | ||
177 | function isVideoCommentsEnabled (video: VideoModel, res: express.Response) { | 177 | function isVideoCommentsEnabled (video: MVideo, res: express.Response) { |
178 | if (video.commentsEnabled !== true) { | 178 | if (video.commentsEnabled !== true) { |
179 | res.status(409) | 179 | res.status(409) |
180 | .json({ error: 'Video comments are disabled for this video.' }) | 180 | .json({ error: 'Video comments are disabled for this video.' }) |
@@ -186,7 +186,7 @@ function isVideoCommentsEnabled (video: VideoModel, res: express.Response) { | |||
186 | return true | 186 | return true |
187 | } | 187 | } |
188 | 188 | ||
189 | function checkUserCanDeleteVideoComment (user: UserModel, videoComment: VideoCommentModel, res: express.Response) { | 189 | function checkUserCanDeleteVideoComment (user: MUser, videoComment: MCommentOwner, res: express.Response) { |
190 | const account = videoComment.Account | 190 | const account = videoComment.Account |
191 | if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) { | 191 | if (user.hasRight(UserRight.REMOVE_ANY_VIDEO_COMMENT) === false && account.userId !== user.id) { |
192 | res.status(403) | 192 | res.status(403) |
@@ -198,9 +198,9 @@ function checkUserCanDeleteVideoComment (user: UserModel, videoComment: VideoCom | |||
198 | return true | 198 | return true |
199 | } | 199 | } |
200 | 200 | ||
201 | async function isVideoCommentAccepted (req: express.Request, res: express.Response, isReply: boolean) { | 201 | async function isVideoCommentAccepted (req: express.Request, res: express.Response, video: MVideoFullLight, isReply: boolean) { |
202 | const acceptParameters = { | 202 | const acceptParameters = { |
203 | video: res.locals.video, | 203 | video, |
204 | commentBody: req.body, | 204 | commentBody: req.body, |
205 | user: res.locals.oauth.token.User | 205 | user: res.locals.oauth.token.User |
206 | } | 206 | } |
@@ -208,7 +208,7 @@ async function isVideoCommentAccepted (req: express.Request, res: express.Respon | |||
208 | let acceptedResult: AcceptResult | 208 | let acceptedResult: AcceptResult |
209 | 209 | ||
210 | if (isReply) { | 210 | if (isReply) { |
211 | const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoComment }) | 211 | const acceptReplyParameters = Object.assign(acceptParameters, { parentComment: res.locals.videoCommentFull }) |
212 | 212 | ||
213 | acceptedResult = await Hooks.wrapFun( | 213 | acceptedResult = await Hooks.wrapFun( |
214 | isLocalVideoCommentReplyAccepted, | 214 | isLocalVideoCommentReplyAccepted, |
diff --git a/server/middlewares/validators/videos/video-playlists.ts b/server/middlewares/validators/videos/video-playlists.ts index 5823795be..27ee62b1f 100644 --- a/server/middlewares/validators/videos/video-playlists.ts +++ b/server/middlewares/validators/videos/video-playlists.ts | |||
@@ -2,7 +2,6 @@ import * as express from 'express' | |||
2 | import { body, param, query, ValidationChain } from 'express-validator' | 2 | import { body, param, query, ValidationChain } from 'express-validator' |
3 | import { UserRight, VideoPlaylistCreate, VideoPlaylistUpdate } from '../../../../shared' | 3 | import { UserRight, VideoPlaylistCreate, VideoPlaylistUpdate } from '../../../../shared' |
4 | import { logger } from '../../../helpers/logger' | 4 | import { logger } from '../../../helpers/logger' |
5 | import { UserModel } from '../../../models/account/user' | ||
6 | import { areValidationErrors } from '../utils' | 5 | import { areValidationErrors } from '../utils' |
7 | import { isVideoImage } from '../../../helpers/custom-validators/videos' | 6 | import { isVideoImage } from '../../../helpers/custom-validators/videos' |
8 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' | 7 | import { CONSTRAINTS_FIELDS } from '../../../initializers/constants' |
@@ -22,13 +21,14 @@ import { | |||
22 | isVideoPlaylistTimestampValid, | 21 | isVideoPlaylistTimestampValid, |
23 | isVideoPlaylistTypeValid | 22 | isVideoPlaylistTypeValid |
24 | } from '../../../helpers/custom-validators/video-playlists' | 23 | } from '../../../helpers/custom-validators/video-playlists' |
25 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
26 | import { cleanUpReqFiles } from '../../../helpers/express-utils' | 24 | import { cleanUpReqFiles } from '../../../helpers/express-utils' |
27 | import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element' | 25 | import { VideoPlaylistElementModel } from '../../../models/video/video-playlist-element' |
28 | import { authenticatePromiseIfNeeded } from '../../oauth' | 26 | import { authenticatePromiseIfNeeded } from '../../oauth' |
29 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' | 27 | import { VideoPlaylistPrivacy } from '../../../../shared/models/videos/playlist/video-playlist-privacy.model' |
30 | import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' | 28 | import { VideoPlaylistType } from '../../../../shared/models/videos/playlist/video-playlist-type.model' |
31 | import { doesVideoChannelIdExist, doesVideoExist, doesVideoPlaylistExist } from '../../../helpers/middlewares' | 29 | import { doesVideoChannelIdExist, doesVideoExist, doesVideoPlaylistExist, VideoPlaylistFetchType } from '../../../helpers/middlewares' |
30 | import { MVideoPlaylist } from '../../../typings/models/video/video-playlist' | ||
31 | import { MUserAccountId } from '@server/typings/models' | ||
32 | 32 | ||
33 | const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ | 33 | const videoPlaylistsAddValidator = getCommonPlaylistEditAttributes().concat([ |
34 | body('displayName') | 34 | body('displayName') |
@@ -67,9 +67,9 @@ const videoPlaylistsUpdateValidator = getCommonPlaylistEditAttributes().concat([ | |||
67 | 67 | ||
68 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return cleanUpReqFiles(req) | 68 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return cleanUpReqFiles(req) |
69 | 69 | ||
70 | const videoPlaylist = res.locals.videoPlaylist | 70 | const videoPlaylist = getPlaylist(res) |
71 | 71 | ||
72 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { | 72 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { |
73 | return cleanUpReqFiles(req) | 73 | return cleanUpReqFiles(req) |
74 | } | 74 | } |
75 | 75 | ||
@@ -110,13 +110,13 @@ const videoPlaylistsDeleteValidator = [ | |||
110 | 110 | ||
111 | if (!await doesVideoPlaylistExist(req.params.playlistId, res)) return | 111 | if (!await doesVideoPlaylistExist(req.params.playlistId, res)) return |
112 | 112 | ||
113 | const videoPlaylist = res.locals.videoPlaylist | 113 | const videoPlaylist = getPlaylist(res) |
114 | if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) { | 114 | if (videoPlaylist.type === VideoPlaylistType.WATCH_LATER) { |
115 | return res.status(400) | 115 | return res.status(400) |
116 | .json({ error: 'Cannot delete a watch later playlist.' }) | 116 | .json({ error: 'Cannot delete a watch later playlist.' }) |
117 | } | 117 | } |
118 | 118 | ||
119 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { | 119 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.REMOVE_ANY_VIDEO_PLAYLIST, res)) { |
120 | return | 120 | return |
121 | } | 121 | } |
122 | 122 | ||
@@ -124,45 +124,47 @@ const videoPlaylistsDeleteValidator = [ | |||
124 | } | 124 | } |
125 | ] | 125 | ] |
126 | 126 | ||
127 | const videoPlaylistsGetValidator = [ | 127 | const videoPlaylistsGetValidator = (fetchType: VideoPlaylistFetchType) => { |
128 | param('playlistId') | 128 | return [ |
129 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | 129 | param('playlistId') |
130 | .custom(isIdOrUUIDValid).withMessage('Should have a valid playlist id/uuid'), | ||
130 | 131 | ||
131 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { | 132 | async (req: express.Request, res: express.Response, next: express.NextFunction) => { |
132 | logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params }) | 133 | logger.debug('Checking videoPlaylistsGetValidator parameters', { parameters: req.params }) |
133 | 134 | ||
134 | if (areValidationErrors(req, res)) return | 135 | if (areValidationErrors(req, res)) return |
135 | 136 | ||
136 | if (!await doesVideoPlaylistExist(req.params.playlistId, res)) return | 137 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, fetchType)) return |
137 | 138 | ||
138 | const videoPlaylist = res.locals.videoPlaylist | 139 | const videoPlaylist = res.locals.videoPlaylistFull || res.locals.videoPlaylistSummary |
139 | 140 | ||
140 | // Video is unlisted, check we used the uuid to fetch it | 141 | // Video is unlisted, check we used the uuid to fetch it |
141 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) { | 142 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.UNLISTED) { |
142 | if (isUUIDValid(req.params.playlistId)) return next() | 143 | if (isUUIDValid(req.params.playlistId)) return next() |
143 | 144 | ||
144 | return res.status(404).end() | 145 | return res.status(404).end() |
145 | } | 146 | } |
147 | |||
148 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { | ||
149 | await authenticatePromiseIfNeeded(req, res) | ||
146 | 150 | ||
147 | if (videoPlaylist.privacy === VideoPlaylistPrivacy.PRIVATE) { | 151 | const user = res.locals.oauth ? res.locals.oauth.token.User : null |
148 | await authenticatePromiseIfNeeded(req, res) | ||
149 | 152 | ||
150 | const user = res.locals.oauth ? res.locals.oauth.token.User : null | 153 | if ( |
154 | !user || | ||
155 | (videoPlaylist.OwnerAccount.id !== user.Account.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST)) | ||
156 | ) { | ||
157 | return res.status(403) | ||
158 | .json({ error: 'Cannot get this private video playlist.' }) | ||
159 | } | ||
151 | 160 | ||
152 | if ( | 161 | return next() |
153 | !user || | ||
154 | (videoPlaylist.OwnerAccount.id !== user.Account.id && !user.hasRight(UserRight.UPDATE_ANY_VIDEO_PLAYLIST)) | ||
155 | ) { | ||
156 | return res.status(403) | ||
157 | .json({ error: 'Cannot get this private video playlist.' }) | ||
158 | } | 162 | } |
159 | 163 | ||
160 | return next() | 164 | return next() |
161 | } | 165 | } |
162 | 166 | ] | |
163 | return next() | 167 | } |
164 | } | ||
165 | ] | ||
166 | 168 | ||
167 | const videoPlaylistsAddVideoValidator = [ | 169 | const videoPlaylistsAddVideoValidator = [ |
168 | param('playlistId') | 170 | param('playlistId') |
@@ -184,8 +186,8 @@ const videoPlaylistsAddVideoValidator = [ | |||
184 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return | 186 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return |
185 | if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return | 187 | if (!await doesVideoExist(req.body.videoId, res, 'only-video')) return |
186 | 188 | ||
187 | const videoPlaylist = res.locals.videoPlaylist | 189 | const videoPlaylist = getPlaylist(res) |
188 | const video = res.locals.video | 190 | const video = res.locals.onlyVideo |
189 | 191 | ||
190 | const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id) | 192 | const videoPlaylistElement = await VideoPlaylistElementModel.loadByPlaylistAndVideo(videoPlaylist.id, video.id) |
191 | if (videoPlaylistElement) { | 193 | if (videoPlaylistElement) { |
@@ -196,7 +198,7 @@ const videoPlaylistsAddVideoValidator = [ | |||
196 | return | 198 | return |
197 | } | 199 | } |
198 | 200 | ||
199 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, res.locals.videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) { | 201 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) { |
200 | return | 202 | return |
201 | } | 203 | } |
202 | 204 | ||
@@ -223,7 +225,7 @@ const videoPlaylistsUpdateOrRemoveVideoValidator = [ | |||
223 | 225 | ||
224 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return | 226 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return |
225 | 227 | ||
226 | const videoPlaylist = res.locals.videoPlaylist | 228 | const videoPlaylist = getPlaylist(res) |
227 | 229 | ||
228 | const videoPlaylistElement = await VideoPlaylistElementModel.loadById(req.params.playlistElementId) | 230 | const videoPlaylistElement = await VideoPlaylistElementModel.loadById(req.params.playlistElementId) |
229 | if (!videoPlaylistElement) { | 231 | if (!videoPlaylistElement) { |
@@ -265,7 +267,7 @@ const videoPlaylistElementAPGetValidator = [ | |||
265 | return res.status(403).end() | 267 | return res.status(403).end() |
266 | } | 268 | } |
267 | 269 | ||
268 | res.locals.videoPlaylistElement = videoPlaylistElement | 270 | res.locals.videoPlaylistElementAP = videoPlaylistElement |
269 | 271 | ||
270 | return next() | 272 | return next() |
271 | } | 273 | } |
@@ -289,7 +291,7 @@ const videoPlaylistsReorderVideosValidator = [ | |||
289 | 291 | ||
290 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return | 292 | if (!await doesVideoPlaylistExist(req.params.playlistId, res, 'all')) return |
291 | 293 | ||
292 | const videoPlaylist = res.locals.videoPlaylist | 294 | const videoPlaylist = getPlaylist(res) |
293 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return | 295 | if (!checkUserCanManageVideoPlaylist(res.locals.oauth.token.User, videoPlaylist, UserRight.UPDATE_ANY_VIDEO_PLAYLIST, res)) return |
294 | 296 | ||
295 | const nextPosition = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id) | 297 | const nextPosition = await VideoPlaylistElementModel.getNextPositionOf(videoPlaylist.id) |
@@ -388,7 +390,7 @@ function getCommonPlaylistEditAttributes () { | |||
388 | ] as (ValidationChain | express.Handler)[] | 390 | ] as (ValidationChain | express.Handler)[] |
389 | } | 391 | } |
390 | 392 | ||
391 | function checkUserCanManageVideoPlaylist (user: UserModel, videoPlaylist: VideoPlaylistModel, right: UserRight, res: express.Response) { | 393 | function checkUserCanManageVideoPlaylist (user: MUserAccountId, videoPlaylist: MVideoPlaylist, right: UserRight, res: express.Response) { |
392 | if (videoPlaylist.isOwned() === false) { | 394 | if (videoPlaylist.isOwned() === false) { |
393 | res.status(403) | 395 | res.status(403) |
394 | .json({ error: 'Cannot manage video playlist of another server.' }) | 396 | .json({ error: 'Cannot manage video playlist of another server.' }) |
@@ -410,3 +412,7 @@ function checkUserCanManageVideoPlaylist (user: UserModel, videoPlaylist: VideoP | |||
410 | 412 | ||
411 | return true | 413 | return true |
412 | } | 414 | } |
415 | |||
416 | function getPlaylist (res: express.Response) { | ||
417 | return res.locals.videoPlaylistFull || res.locals.videoPlaylistSummary | ||
418 | } | ||
diff --git a/server/middlewares/validators/videos/video-shares.ts b/server/middlewares/validators/videos/video-shares.ts index ace62be5c..20fc96243 100644 --- a/server/middlewares/validators/videos/video-shares.ts +++ b/server/middlewares/validators/videos/video-shares.ts | |||
@@ -16,7 +16,7 @@ const videosShareValidator = [ | |||
16 | if (areValidationErrors(req, res)) return | 16 | if (areValidationErrors(req, res)) return |
17 | if (!await doesVideoExist(req.params.id, res)) return | 17 | if (!await doesVideoExist(req.params.id, res)) return |
18 | 18 | ||
19 | const video = res.locals.video | 19 | const video = res.locals.videoAll |
20 | 20 | ||
21 | const share = await VideoShareModel.load(req.params.actorId, video.id) | 21 | const share = await VideoShareModel.load(req.params.actorId, video.id) |
22 | if (!share) { | 22 | if (!share) { |
diff --git a/server/middlewares/validators/videos/videos.ts b/server/middlewares/validators/videos/videos.ts index af06f3c62..1449903b7 100644 --- a/server/middlewares/validators/videos/videos.ts +++ b/server/middlewares/validators/videos/videos.ts | |||
@@ -37,13 +37,14 @@ import { VideoModel } from '../../../models/video/video' | |||
37 | import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership' | 37 | import { checkUserCanTerminateOwnershipChange, doesChangeVideoOwnershipExist } from '../../../helpers/custom-validators/video-ownership' |
38 | import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model' | 38 | import { VideoChangeOwnershipAccept } from '../../../../shared/models/videos/video-change-ownership-accept.model' |
39 | import { AccountModel } from '../../../models/account/account' | 39 | import { AccountModel } from '../../../models/account/account' |
40 | import { VideoFetchType } from '../../../helpers/video' | ||
41 | import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search' | 40 | import { isNSFWQueryValid, isNumberArray, isStringArray } from '../../../helpers/custom-validators/search' |
42 | import { getServerActor } from '../../../helpers/utils' | 41 | import { getServerActor } from '../../../helpers/utils' |
43 | import { CONFIG } from '../../../initializers/config' | 42 | import { CONFIG } from '../../../initializers/config' |
44 | import { isLocalVideoAccepted } from '../../../lib/moderation' | 43 | import { isLocalVideoAccepted } from '../../../lib/moderation' |
45 | import { Hooks } from '../../../lib/plugins/hooks' | 44 | import { Hooks } from '../../../lib/plugins/hooks' |
46 | import { checkUserCanManageVideo, doesVideoChannelOfAccountExist, doesVideoExist } from '../../../helpers/middlewares' | 45 | import { checkUserCanManageVideo, doesVideoChannelOfAccountExist, doesVideoExist } from '../../../helpers/middlewares' |
46 | import { MVideoFullLight } from '@server/typings/models' | ||
47 | import { getVideoWithAttributes } from '../../../helpers/video' | ||
47 | 48 | ||
48 | const videosAddValidator = getCommonVideoEditAttributes().concat([ | 49 | const videosAddValidator = getCommonVideoEditAttributes().concat([ |
49 | body('videofile') | 50 | body('videofile') |
@@ -113,7 +114,7 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([ | |||
113 | 114 | ||
114 | // Check if the user who did the request is able to update the video | 115 | // Check if the user who did the request is able to update the video |
115 | const user = res.locals.oauth.token.User | 116 | const user = res.locals.oauth.token.User |
116 | if (!checkUserCanManageVideo(user, res.locals.video, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) | 117 | if (!checkUserCanManageVideo(user, res.locals.videoAll, UserRight.UPDATE_ANY_VIDEO, res)) return cleanUpReqFiles(req) |
117 | 118 | ||
118 | if (req.body.channelId && !await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) | 119 | if (req.body.channelId && !await doesVideoChannelOfAccountExist(req.body.channelId, user, res)) return cleanUpReqFiles(req) |
119 | 120 | ||
@@ -122,7 +123,7 @@ const videosUpdateValidator = getCommonVideoEditAttributes().concat([ | |||
122 | ]) | 123 | ]) |
123 | 124 | ||
124 | async function checkVideoFollowConstraints (req: express.Request, res: express.Response, next: express.NextFunction) { | 125 | async function checkVideoFollowConstraints (req: express.Request, res: express.Response, next: express.NextFunction) { |
125 | const video = res.locals.video | 126 | const video = getVideoWithAttributes(res) |
126 | 127 | ||
127 | // Anybody can watch local videos | 128 | // Anybody can watch local videos |
128 | if (video.isOwned() === true) return next() | 129 | if (video.isOwned() === true) return next() |
@@ -146,7 +147,7 @@ async function checkVideoFollowConstraints (req: express.Request, res: express.R | |||
146 | }) | 147 | }) |
147 | } | 148 | } |
148 | 149 | ||
149 | const videosCustomGetValidator = (fetchType: VideoFetchType) => { | 150 | const videosCustomGetValidator = (fetchType: 'all' | 'only-video' | 'only-video-with-rights') => { |
150 | return [ | 151 | return [ |
151 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), | 152 | param('id').custom(isIdOrUUIDValid).not().isEmpty().withMessage('Should have a valid id'), |
152 | 153 | ||
@@ -156,10 +157,11 @@ const videosCustomGetValidator = (fetchType: VideoFetchType) => { | |||
156 | if (areValidationErrors(req, res)) return | 157 | if (areValidationErrors(req, res)) return |
157 | if (!await doesVideoExist(req.params.id, res, fetchType)) return | 158 | if (!await doesVideoExist(req.params.id, res, fetchType)) return |
158 | 159 | ||
159 | const video = res.locals.video | 160 | const video = getVideoWithAttributes(res) |
161 | const videoAll = video as MVideoFullLight | ||
160 | 162 | ||
161 | // Video private or blacklisted | 163 | // Video private or blacklisted |
162 | if (video.privacy === VideoPrivacy.PRIVATE || video.VideoBlacklist) { | 164 | if (video.privacy === VideoPrivacy.PRIVATE || videoAll.VideoBlacklist) { |
163 | await authenticatePromiseIfNeeded(req, res) | 165 | await authenticatePromiseIfNeeded(req, res) |
164 | 166 | ||
165 | const user = res.locals.oauth ? res.locals.oauth.token.User : null | 167 | const user = res.locals.oauth ? res.locals.oauth.token.User : null |
@@ -167,7 +169,7 @@ const videosCustomGetValidator = (fetchType: VideoFetchType) => { | |||
167 | // Only the owner or a user that have blacklist rights can see the video | 169 | // Only the owner or a user that have blacklist rights can see the video |
168 | if ( | 170 | if ( |
169 | !user || | 171 | !user || |
170 | (video.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) | 172 | (videoAll.VideoChannel && videoAll.VideoChannel.Account.userId !== user.id && !user.hasRight(UserRight.MANAGE_VIDEO_BLACKLIST)) |
171 | ) { | 173 | ) { |
172 | return res.status(403) | 174 | return res.status(403) |
173 | .json({ error: 'Cannot get this private or blacklisted video.' }) | 175 | .json({ error: 'Cannot get this private or blacklisted video.' }) |
@@ -202,7 +204,7 @@ const videosRemoveValidator = [ | |||
202 | if (!await doesVideoExist(req.params.id, res)) return | 204 | if (!await doesVideoExist(req.params.id, res)) return |
203 | 205 | ||
204 | // Check if the user who did the request is able to delete the video | 206 | // Check if the user who did the request is able to delete the video |
205 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.REMOVE_ANY_VIDEO, res)) return | 207 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.REMOVE_ANY_VIDEO, res)) return |
206 | 208 | ||
207 | return next() | 209 | return next() |
208 | } | 210 | } |
@@ -218,7 +220,7 @@ const videosChangeOwnershipValidator = [ | |||
218 | if (!await doesVideoExist(req.params.videoId, res)) return | 220 | if (!await doesVideoExist(req.params.videoId, res)) return |
219 | 221 | ||
220 | // Check if the user who did the request is able to change the ownership of the video | 222 | // Check if the user who did the request is able to change the ownership of the video |
221 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.video, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return | 223 | if (!checkUserCanManageVideo(res.locals.oauth.token.User, res.locals.videoAll, UserRight.CHANGE_VIDEO_OWNERSHIP, res)) return |
222 | 224 | ||
223 | const nextOwner = await AccountModel.loadLocalByName(req.body.username) | 225 | const nextOwner = await AccountModel.loadLocalByName(req.body.username) |
224 | if (!nextOwner) { | 226 | if (!nextOwner) { |
diff --git a/server/middlewares/validators/webfinger.ts b/server/middlewares/validators/webfinger.ts index d7cfe17f0..d50e6527f 100644 --- a/server/middlewares/validators/webfinger.ts +++ b/server/middlewares/validators/webfinger.ts | |||
@@ -18,6 +18,7 @@ const webfingerValidator = [ | |||
18 | const nameWithHost = getHostWithPort(req.query.resource.substr(5)) | 18 | const nameWithHost = getHostWithPort(req.query.resource.substr(5)) |
19 | const [ name ] = nameWithHost.split('@') | 19 | const [ name ] = nameWithHost.split('@') |
20 | 20 | ||
21 | // FIXME: we don't need the full actor | ||
21 | const actor = await ActorModel.loadLocalByName(name) | 22 | const actor = await ActorModel.loadLocalByName(name) |
22 | if (!actor) { | 23 | if (!actor) { |
23 | return res.status(404) | 24 | return res.status(404) |
@@ -25,7 +26,7 @@ const webfingerValidator = [ | |||
25 | .end() | 26 | .end() |
26 | } | 27 | } |
27 | 28 | ||
28 | res.locals.actor = actor | 29 | res.locals.actorFull = actor |
29 | return next() | 30 | return next() |
30 | } | 31 | } |
31 | ] | 32 | ] |
diff --git a/server/models/account/account-blocklist.ts b/server/models/account/account-blocklist.ts index d5746ad76..8bcaca828 100644 --- a/server/models/account/account-blocklist.ts +++ b/server/models/account/account-blocklist.ts | |||
@@ -3,6 +3,8 @@ import { AccountModel } from './account' | |||
3 | import { getSort } from '../utils' | 3 | import { getSort } from '../utils' |
4 | import { AccountBlock } from '../../../shared/models/blocklist' | 4 | import { AccountBlock } from '../../../shared/models/blocklist' |
5 | import { Op } from 'sequelize' | 5 | import { Op } from 'sequelize' |
6 | import * as Bluebird from 'bluebird' | ||
7 | import { MAccountBlocklist, MAccountBlocklistAccounts, MAccountBlocklistFormattable } from '@server/typings/models' | ||
6 | 8 | ||
7 | enum ScopeNames { | 9 | enum ScopeNames { |
8 | WITH_ACCOUNTS = 'WITH_ACCOUNTS' | 10 | WITH_ACCOUNTS = 'WITH_ACCOUNTS' |
@@ -103,7 +105,7 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> { | |||
103 | }) | 105 | }) |
104 | } | 106 | } |
105 | 107 | ||
106 | static loadByAccountAndTarget (accountId: number, targetAccountId: number) { | 108 | static loadByAccountAndTarget (accountId: number, targetAccountId: number): Bluebird<MAccountBlocklist> { |
107 | const query = { | 109 | const query = { |
108 | where: { | 110 | where: { |
109 | accountId, | 111 | accountId, |
@@ -126,13 +128,13 @@ export class AccountBlocklistModel extends Model<AccountBlocklistModel> { | |||
126 | 128 | ||
127 | return AccountBlocklistModel | 129 | return AccountBlocklistModel |
128 | .scope([ ScopeNames.WITH_ACCOUNTS ]) | 130 | .scope([ ScopeNames.WITH_ACCOUNTS ]) |
129 | .findAndCountAll(query) | 131 | .findAndCountAll<MAccountBlocklistAccounts>(query) |
130 | .then(({ rows, count }) => { | 132 | .then(({ rows, count }) => { |
131 | return { total: count, data: rows } | 133 | return { total: count, data: rows } |
132 | }) | 134 | }) |
133 | } | 135 | } |
134 | 136 | ||
135 | toFormattedJSON (): AccountBlock { | 137 | toFormattedJSON (this: MAccountBlocklistFormattable): AccountBlock { |
136 | return { | 138 | return { |
137 | byAccount: this.ByAccount.toFormattedJSON(), | 139 | byAccount: this.ByAccount.toFormattedJSON(), |
138 | blockedAccount: this.BlockedAccount.toFormattedJSON(), | 140 | blockedAccount: this.BlockedAccount.toFormattedJSON(), |
diff --git a/server/models/account/account-video-rate.ts b/server/models/account/account-video-rate.ts index 4bd8114cf..a6edbeee8 100644 --- a/server/models/account/account-video-rate.ts +++ b/server/models/account/account-video-rate.ts | |||
@@ -10,6 +10,13 @@ import { buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils' | |||
10 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 10 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
11 | import { AccountVideoRate } from '../../../shared' | 11 | import { AccountVideoRate } from '../../../shared' |
12 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel' | 12 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from '../video/video-channel' |
13 | import * as Bluebird from 'bluebird' | ||
14 | import { | ||
15 | MAccountVideoRate, | ||
16 | MAccountVideoRateAccountUrl, | ||
17 | MAccountVideoRateAccountVideo, | ||
18 | MAccountVideoRateFormattable | ||
19 | } from '@server/typings/models/video/video-rate' | ||
13 | 20 | ||
14 | /* | 21 | /* |
15 | Account rates per video. | 22 | Account rates per video. |
@@ -77,7 +84,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
77 | }) | 84 | }) |
78 | Account: AccountModel | 85 | Account: AccountModel |
79 | 86 | ||
80 | static load (accountId: number, videoId: number, transaction?: Transaction) { | 87 | static load (accountId: number, videoId: number, transaction?: Transaction): Bluebird<MAccountVideoRate> { |
81 | const options: FindOptions = { | 88 | const options: FindOptions = { |
82 | where: { | 89 | where: { |
83 | accountId, | 90 | accountId, |
@@ -89,7 +96,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
89 | return AccountVideoRateModel.findOne(options) | 96 | return AccountVideoRateModel.findOne(options) |
90 | } | 97 | } |
91 | 98 | ||
92 | static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, transaction?: Transaction) { | 99 | static loadByAccountAndVideoOrUrl (accountId: number, videoId: number, url: string, t?: Transaction): Bluebird<MAccountVideoRate> { |
93 | const options: FindOptions = { | 100 | const options: FindOptions = { |
94 | where: { | 101 | where: { |
95 | [ Op.or]: [ | 102 | [ Op.or]: [ |
@@ -103,7 +110,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
103 | ] | 110 | ] |
104 | } | 111 | } |
105 | } | 112 | } |
106 | if (transaction) options.transaction = transaction | 113 | if (t) options.transaction = t |
107 | 114 | ||
108 | return AccountVideoRateModel.findOne(options) | 115 | return AccountVideoRateModel.findOne(options) |
109 | } | 116 | } |
@@ -140,7 +147,12 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
140 | return AccountVideoRateModel.findAndCountAll(query) | 147 | return AccountVideoRateModel.findAndCountAll(query) |
141 | } | 148 | } |
142 | 149 | ||
143 | static loadLocalAndPopulateVideo (rateType: VideoRateType, accountName: string, videoId: number, transaction?: Transaction) { | 150 | static loadLocalAndPopulateVideo ( |
151 | rateType: VideoRateType, | ||
152 | accountName: string, | ||
153 | videoId: number, | ||
154 | t?: Transaction | ||
155 | ): Bluebird<MAccountVideoRateAccountVideo> { | ||
144 | const options: FindOptions = { | 156 | const options: FindOptions = { |
145 | where: { | 157 | where: { |
146 | videoId, | 158 | videoId, |
@@ -152,7 +164,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
152 | required: true, | 164 | required: true, |
153 | include: [ | 165 | include: [ |
154 | { | 166 | { |
155 | attributes: [ 'id', 'url', 'preferredUsername' ], | 167 | attributes: [ 'id', 'url', 'followersUrl', 'preferredUsername' ], |
156 | model: ActorModel.unscoped(), | 168 | model: ActorModel.unscoped(), |
157 | required: true, | 169 | required: true, |
158 | where: { | 170 | where: { |
@@ -167,7 +179,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
167 | } | 179 | } |
168 | ] | 180 | ] |
169 | } | 181 | } |
170 | if (transaction) options.transaction = transaction | 182 | if (t) options.transaction = t |
171 | 183 | ||
172 | return AccountVideoRateModel.findOne(options) | 184 | return AccountVideoRateModel.findOne(options) |
173 | } | 185 | } |
@@ -208,7 +220,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
208 | ] | 220 | ] |
209 | } | 221 | } |
210 | 222 | ||
211 | return AccountVideoRateModel.findAndCountAll(query) | 223 | return AccountVideoRateModel.findAndCountAll<MAccountVideoRateAccountUrl>(query) |
212 | } | 224 | } |
213 | 225 | ||
214 | static cleanOldRatesOf (videoId: number, type: VideoRateType, beforeUpdatedAt: Date) { | 226 | static cleanOldRatesOf (videoId: number, type: VideoRateType, beforeUpdatedAt: Date) { |
@@ -241,7 +253,7 @@ export class AccountVideoRateModel extends Model<AccountVideoRateModel> { | |||
241 | }) | 253 | }) |
242 | } | 254 | } |
243 | 255 | ||
244 | toFormattedJSON (): AccountVideoRate { | 256 | toFormattedJSON (this: MAccountVideoRateFormattable): AccountVideoRate { |
245 | return { | 257 | return { |
246 | video: this.Video.toFormattedJSON(), | 258 | video: this.Video.toFormattedJSON(), |
247 | rating: this.type | 259 | rating: this.type |
diff --git a/server/models/account/account.ts b/server/models/account/account.ts index 4dc412301..ba1094536 100644 --- a/server/models/account/account.ts +++ b/server/models/account/account.ts | |||
@@ -3,7 +3,8 @@ import { | |||
3 | BeforeDestroy, | 3 | BeforeDestroy, |
4 | BelongsTo, | 4 | BelongsTo, |
5 | Column, | 5 | Column, |
6 | CreatedAt, DataType, | 6 | CreatedAt, |
7 | DataType, | ||
7 | Default, | 8 | Default, |
8 | DefaultScope, | 9 | DefaultScope, |
9 | ForeignKey, | 10 | ForeignKey, |
@@ -31,6 +32,8 @@ import { FindOptions, IncludeOptions, Op, Transaction, WhereOptions } from 'sequ | |||
31 | import { AccountBlocklistModel } from './account-blocklist' | 32 | import { AccountBlocklistModel } from './account-blocklist' |
32 | import { ServerBlocklistModel } from '../server/server-blocklist' | 33 | import { ServerBlocklistModel } from '../server/server-blocklist' |
33 | import { ActorFollowModel } from '../activitypub/actor-follow' | 34 | import { ActorFollowModel } from '../activitypub/actor-follow' |
35 | import { MAccountActor, MAccountDefault, MAccountSummaryFormattable, MAccountFormattable, MAccountAP } from '../../typings/models' | ||
36 | import * as Bluebird from 'bluebird' | ||
34 | 37 | ||
35 | export enum ScopeNames { | 38 | export enum ScopeNames { |
36 | SUMMARY = 'SUMMARY' | 39 | SUMMARY = 'SUMMARY' |
@@ -229,11 +232,11 @@ export class AccountModel extends Model<AccountModel> { | |||
229 | return undefined | 232 | return undefined |
230 | } | 233 | } |
231 | 234 | ||
232 | static load (id: number, transaction?: Transaction) { | 235 | static load (id: number, transaction?: Transaction): Bluebird<MAccountDefault> { |
233 | return AccountModel.findByPk(id, { transaction }) | 236 | return AccountModel.findByPk(id, { transaction }) |
234 | } | 237 | } |
235 | 238 | ||
236 | static loadByNameWithHost (nameWithHost: string) { | 239 | static loadByNameWithHost (nameWithHost: string): Bluebird<MAccountDefault> { |
237 | const [ accountName, host ] = nameWithHost.split('@') | 240 | const [ accountName, host ] = nameWithHost.split('@') |
238 | 241 | ||
239 | if (!host || host === WEBSERVER.HOST) return AccountModel.loadLocalByName(accountName) | 242 | if (!host || host === WEBSERVER.HOST) return AccountModel.loadLocalByName(accountName) |
@@ -241,7 +244,7 @@ export class AccountModel extends Model<AccountModel> { | |||
241 | return AccountModel.loadByNameAndHost(accountName, host) | 244 | return AccountModel.loadByNameAndHost(accountName, host) |
242 | } | 245 | } |
243 | 246 | ||
244 | static loadLocalByName (name: string) { | 247 | static loadLocalByName (name: string): Bluebird<MAccountDefault> { |
245 | const query = { | 248 | const query = { |
246 | where: { | 249 | where: { |
247 | [ Op.or ]: [ | 250 | [ Op.or ]: [ |
@@ -271,7 +274,7 @@ export class AccountModel extends Model<AccountModel> { | |||
271 | return AccountModel.findOne(query) | 274 | return AccountModel.findOne(query) |
272 | } | 275 | } |
273 | 276 | ||
274 | static loadByNameAndHost (name: string, host: string) { | 277 | static loadByNameAndHost (name: string, host: string): Bluebird<MAccountDefault> { |
275 | const query = { | 278 | const query = { |
276 | include: [ | 279 | include: [ |
277 | { | 280 | { |
@@ -296,7 +299,7 @@ export class AccountModel extends Model<AccountModel> { | |||
296 | return AccountModel.findOne(query) | 299 | return AccountModel.findOne(query) |
297 | } | 300 | } |
298 | 301 | ||
299 | static loadByUrl (url: string, transaction?: Transaction) { | 302 | static loadByUrl (url: string, transaction?: Transaction): Bluebird<MAccountDefault> { |
300 | const query = { | 303 | const query = { |
301 | include: [ | 304 | include: [ |
302 | { | 305 | { |
@@ -329,7 +332,7 @@ export class AccountModel extends Model<AccountModel> { | |||
329 | }) | 332 | }) |
330 | } | 333 | } |
331 | 334 | ||
332 | static listLocalsForSitemap (sort: string) { | 335 | static listLocalsForSitemap (sort: string): Bluebird<MAccountActor[]> { |
333 | const query = { | 336 | const query = { |
334 | attributes: [ ], | 337 | attributes: [ ], |
335 | offset: 0, | 338 | offset: 0, |
@@ -350,7 +353,7 @@ export class AccountModel extends Model<AccountModel> { | |||
350 | .findAll(query) | 353 | .findAll(query) |
351 | } | 354 | } |
352 | 355 | ||
353 | toFormattedJSON (): Account { | 356 | toFormattedJSON (this: MAccountFormattable): Account { |
354 | const actor = this.Actor.toFormattedJSON() | 357 | const actor = this.Actor.toFormattedJSON() |
355 | const account = { | 358 | const account = { |
356 | id: this.id, | 359 | id: this.id, |
@@ -364,8 +367,8 @@ export class AccountModel extends Model<AccountModel> { | |||
364 | return Object.assign(actor, account) | 367 | return Object.assign(actor, account) |
365 | } | 368 | } |
366 | 369 | ||
367 | toFormattedSummaryJSON (): AccountSummary { | 370 | toFormattedSummaryJSON (this: MAccountSummaryFormattable): AccountSummary { |
368 | const actor = this.Actor.toFormattedJSON() | 371 | const actor = this.Actor.toFormattedSummaryJSON() |
369 | 372 | ||
370 | return { | 373 | return { |
371 | id: this.id, | 374 | id: this.id, |
@@ -377,8 +380,8 @@ export class AccountModel extends Model<AccountModel> { | |||
377 | } | 380 | } |
378 | } | 381 | } |
379 | 382 | ||
380 | toActivityPubObject () { | 383 | toActivityPubObject (this: MAccountAP) { |
381 | const obj = this.Actor.toActivityPubObject(this.name, 'Account') | 384 | const obj = this.Actor.toActivityPubObject(this.name) |
382 | 385 | ||
383 | return Object.assign(obj, { | 386 | return Object.assign(obj, { |
384 | summary: this.description | 387 | summary: this.description |
diff --git a/server/models/account/user-notification-setting.ts b/server/models/account/user-notification-setting.ts index c2fbc6d23..dc69a17fd 100644 --- a/server/models/account/user-notification-setting.ts +++ b/server/models/account/user-notification-setting.ts | |||
@@ -17,6 +17,7 @@ import { UserModel } from './user' | |||
17 | import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' | 17 | import { isUserNotificationSettingValid } from '../../helpers/custom-validators/user-notifications' |
18 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model' | 18 | import { UserNotificationSetting, UserNotificationSettingValue } from '../../../shared/models/users/user-notification-setting.model' |
19 | import { clearCacheByUserId } from '../../lib/oauth-model' | 19 | import { clearCacheByUserId } from '../../lib/oauth-model' |
20 | import { MNotificationSettingFormattable } from '@server/typings/models' | ||
20 | 21 | ||
21 | @Table({ | 22 | @Table({ |
22 | tableName: 'userNotificationSetting', | 23 | tableName: 'userNotificationSetting', |
@@ -113,6 +114,15 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM | |||
113 | @AllowNull(false) | 114 | @AllowNull(false) |
114 | @Default(null) | 115 | @Default(null) |
115 | @Is( | 116 | @Is( |
117 | 'UserNotificationSettingNewInstanceFollower', | ||
118 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'autoInstanceFollowing') | ||
119 | ) | ||
120 | @Column | ||
121 | autoInstanceFollowing: UserNotificationSettingValue | ||
122 | |||
123 | @AllowNull(false) | ||
124 | @Default(null) | ||
125 | @Is( | ||
116 | 'UserNotificationSettingNewFollow', | 126 | 'UserNotificationSettingNewFollow', |
117 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'newFollow') | 127 | value => throwIfNotValid(value, isUserNotificationSettingValid, 'newFollow') |
118 | ) | 128 | ) |
@@ -152,7 +162,7 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM | |||
152 | return clearCacheByUserId(instance.userId) | 162 | return clearCacheByUserId(instance.userId) |
153 | } | 163 | } |
154 | 164 | ||
155 | toFormattedJSON (): UserNotificationSetting { | 165 | toFormattedJSON (this: MNotificationSettingFormattable): UserNotificationSetting { |
156 | return { | 166 | return { |
157 | newCommentOnMyVideo: this.newCommentOnMyVideo, | 167 | newCommentOnMyVideo: this.newCommentOnMyVideo, |
158 | newVideoFromSubscription: this.newVideoFromSubscription, | 168 | newVideoFromSubscription: this.newVideoFromSubscription, |
@@ -164,7 +174,8 @@ export class UserNotificationSettingModel extends Model<UserNotificationSettingM | |||
164 | newUserRegistration: this.newUserRegistration, | 174 | newUserRegistration: this.newUserRegistration, |
165 | commentMention: this.commentMention, | 175 | commentMention: this.commentMention, |
166 | newFollow: this.newFollow, | 176 | newFollow: this.newFollow, |
167 | newInstanceFollower: this.newInstanceFollower | 177 | newInstanceFollower: this.newInstanceFollower, |
178 | autoInstanceFollowing: this.autoInstanceFollowing | ||
168 | } | 179 | } |
169 | } | 180 | } |
170 | } | 181 | } |
diff --git a/server/models/account/user-notification.ts b/server/models/account/user-notification.ts index f38cd7e78..ccb81b891 100644 --- a/server/models/account/user-notification.ts +++ b/server/models/account/user-notification.ts | |||
@@ -16,6 +16,7 @@ import { ActorModel } from '../activitypub/actor' | |||
16 | import { ActorFollowModel } from '../activitypub/actor-follow' | 16 | import { ActorFollowModel } from '../activitypub/actor-follow' |
17 | import { AvatarModel } from '../avatar/avatar' | 17 | import { AvatarModel } from '../avatar/avatar' |
18 | import { ServerModel } from '../server/server' | 18 | import { ServerModel } from '../server/server' |
19 | import { UserNotificationIncludes, UserNotificationModelForApi } from '@server/typings/models/user' | ||
19 | 20 | ||
20 | enum ScopeNames { | 21 | enum ScopeNames { |
21 | WITH_ALL = 'WITH_ALL' | 22 | WITH_ALL = 'WITH_ALL' |
@@ -134,13 +135,18 @@ function buildAccountInclude (required: boolean, withActor = false) { | |||
134 | ] | 135 | ] |
135 | }, | 136 | }, |
136 | { | 137 | { |
137 | attributes: [ 'preferredUsername' ], | 138 | attributes: [ 'preferredUsername', 'type' ], |
138 | model: ActorModel.unscoped(), | 139 | model: ActorModel.unscoped(), |
139 | required: true, | 140 | required: true, |
140 | as: 'ActorFollowing', | 141 | as: 'ActorFollowing', |
141 | include: [ | 142 | include: [ |
142 | buildChannelInclude(false), | 143 | buildChannelInclude(false), |
143 | buildAccountInclude(false) | 144 | buildAccountInclude(false), |
145 | { | ||
146 | attributes: [ 'host' ], | ||
147 | model: ServerModel.unscoped(), | ||
148 | required: false | ||
149 | } | ||
144 | ] | 150 | ] |
145 | } | 151 | } |
146 | ] | 152 | ] |
@@ -371,7 +377,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
371 | return UserNotificationModel.update({ read: true }, query) | 377 | return UserNotificationModel.update({ read: true }, query) |
372 | } | 378 | } |
373 | 379 | ||
374 | toFormattedJSON (): UserNotification { | 380 | toFormattedJSON (this: UserNotificationModelForApi): UserNotification { |
375 | const video = this.Video | 381 | const video = this.Video |
376 | ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) }) | 382 | ? Object.assign(this.formatVideo(this.Video),{ channel: this.formatActor(this.Video.VideoChannel) }) |
377 | : undefined | 383 | : undefined |
@@ -403,6 +409,11 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
403 | 409 | ||
404 | const account = this.Account ? this.formatActor(this.Account) : undefined | 410 | const account = this.Account ? this.formatActor(this.Account) : undefined |
405 | 411 | ||
412 | const actorFollowingType = { | ||
413 | Application: 'instance' as 'instance', | ||
414 | Group: 'channel' as 'channel', | ||
415 | Person: 'account' as 'account' | ||
416 | } | ||
406 | const actorFollow = this.ActorFollow ? { | 417 | const actorFollow = this.ActorFollow ? { |
407 | id: this.ActorFollow.id, | 418 | id: this.ActorFollow.id, |
408 | state: this.ActorFollow.state, | 419 | state: this.ActorFollow.state, |
@@ -414,9 +425,10 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
414 | host: this.ActorFollow.ActorFollower.getHost() | 425 | host: this.ActorFollow.ActorFollower.getHost() |
415 | }, | 426 | }, |
416 | following: { | 427 | following: { |
417 | type: this.ActorFollow.ActorFollowing.VideoChannel ? 'channel' as 'channel' : 'account' as 'account', | 428 | type: actorFollowingType[this.ActorFollow.ActorFollowing.type], |
418 | displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(), | 429 | displayName: (this.ActorFollow.ActorFollowing.VideoChannel || this.ActorFollow.ActorFollowing.Account).getDisplayName(), |
419 | name: this.ActorFollow.ActorFollowing.preferredUsername | 430 | name: this.ActorFollow.ActorFollowing.preferredUsername, |
431 | host: this.ActorFollow.ActorFollowing.getHost() | ||
420 | } | 432 | } |
421 | } : undefined | 433 | } : undefined |
422 | 434 | ||
@@ -436,7 +448,7 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
436 | } | 448 | } |
437 | } | 449 | } |
438 | 450 | ||
439 | private formatVideo (video: VideoModel) { | 451 | formatVideo (this: UserNotificationModelForApi, video: UserNotificationIncludes.VideoInclude) { |
440 | return { | 452 | return { |
441 | id: video.id, | 453 | id: video.id, |
442 | uuid: video.uuid, | 454 | uuid: video.uuid, |
@@ -444,7 +456,10 @@ export class UserNotificationModel extends Model<UserNotificationModel> { | |||
444 | } | 456 | } |
445 | } | 457 | } |
446 | 458 | ||
447 | private formatActor (accountOrChannel: AccountModel | VideoChannelModel) { | 459 | formatActor ( |
460 | this: UserNotificationModelForApi, | ||
461 | accountOrChannel: UserNotificationIncludes.AccountIncludeActor | UserNotificationIncludes.VideoChannelIncludeActor | ||
462 | ) { | ||
448 | const avatar = accountOrChannel.Actor.Avatar | 463 | const avatar = accountOrChannel.Actor.Avatar |
449 | ? { path: accountOrChannel.Actor.Avatar.getStaticPath() } | 464 | ? { path: accountOrChannel.Actor.Avatar.getStaticPath() } |
450 | : undefined | 465 | : undefined |
diff --git a/server/models/account/user-video-history.ts b/server/models/account/user-video-history.ts index a862fc45f..3fe4c8db1 100644 --- a/server/models/account/user-video-history.ts +++ b/server/models/account/user-video-history.ts | |||
@@ -1,7 +1,8 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, IsInt, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { VideoModel } from '../video/video' | 2 | import { VideoModel } from '../video/video' |
3 | import { UserModel } from './user' | 3 | import { UserModel } from './user' |
4 | import { Transaction, Op, DestroyOptions } from 'sequelize' | 4 | import { DestroyOptions, Op, Transaction } from 'sequelize' |
5 | import { MUserAccountId, MUserId } from '@server/typings/models' | ||
5 | 6 | ||
6 | @Table({ | 7 | @Table({ |
7 | tableName: 'userVideoHistory', | 8 | tableName: 'userVideoHistory', |
@@ -54,7 +55,7 @@ export class UserVideoHistoryModel extends Model<UserVideoHistoryModel> { | |||
54 | }) | 55 | }) |
55 | User: UserModel | 56 | User: UserModel |
56 | 57 | ||
57 | static listForApi (user: UserModel, start: number, count: number) { | 58 | static listForApi (user: MUserAccountId, start: number, count: number) { |
58 | return VideoModel.listForApi({ | 59 | return VideoModel.listForApi({ |
59 | start, | 60 | start, |
60 | count, | 61 | count, |
@@ -67,7 +68,7 @@ export class UserVideoHistoryModel extends Model<UserVideoHistoryModel> { | |||
67 | }) | 68 | }) |
68 | } | 69 | } |
69 | 70 | ||
70 | static removeUserHistoryBefore (user: UserModel, beforeDate: string, t: Transaction) { | 71 | static removeUserHistoryBefore (user: MUserId, beforeDate: string, t: Transaction) { |
71 | const query: DestroyOptions = { | 72 | const query: DestroyOptions = { |
72 | where: { | 73 | where: { |
73 | userId: user.id | 74 | userId: user.id |
diff --git a/server/models/account/user.ts b/server/models/account/user.ts index 0041bf577..451e1fd6b 100644 --- a/server/models/account/user.ts +++ b/server/models/account/user.ts | |||
@@ -22,6 +22,7 @@ import { | |||
22 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' | 22 | import { hasUserRight, USER_ROLE_LABELS, UserRight } from '../../../shared' |
23 | import { User, UserRole } from '../../../shared/models/users' | 23 | import { User, UserRole } from '../../../shared/models/users' |
24 | import { | 24 | import { |
25 | isNoInstanceConfigWarningModal, | ||
25 | isUserAdminFlagsValid, | 26 | isUserAdminFlagsValid, |
26 | isUserAutoPlayVideoValid, | 27 | isUserAutoPlayVideoValid, |
27 | isUserBlockedReasonValid, | 28 | isUserBlockedReasonValid, |
@@ -35,7 +36,8 @@ import { | |||
35 | isUserVideoQuotaDailyValid, | 36 | isUserVideoQuotaDailyValid, |
36 | isUserVideoQuotaValid, | 37 | isUserVideoQuotaValid, |
37 | isUserVideosHistoryEnabledValid, | 38 | isUserVideosHistoryEnabledValid, |
38 | isUserWebTorrentEnabledValid | 39 | isUserWebTorrentEnabledValid, |
40 | isNoWelcomeModal | ||
39 | } from '../../helpers/custom-validators/users' | 41 | } from '../../helpers/custom-validators/users' |
40 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' | 42 | import { comparePassword, cryptPassword } from '../../helpers/peertube-crypto' |
41 | import { OAuthTokenModel } from '../oauth/oauth-token' | 43 | import { OAuthTokenModel } from '../oauth/oauth-token' |
@@ -54,6 +56,14 @@ import { VideoImportModel } from '../video/video-import' | |||
54 | import { UserAdminFlag } from '../../../shared/models/users/user-flag.model' | 56 | import { UserAdminFlag } from '../../../shared/models/users/user-flag.model' |
55 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' | 57 | import { isThemeNameValid } from '../../helpers/custom-validators/plugins' |
56 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' | 58 | import { getThemeOrDefault } from '../../lib/plugins/theme-utils' |
59 | import * as Bluebird from 'bluebird' | ||
60 | import { | ||
61 | MUserDefault, | ||
62 | MUserFormattable, | ||
63 | MUserId, | ||
64 | MUserNotifSettingChannelDefault, | ||
65 | MUserWithNotificationSetting | ||
66 | } from '@server/typings/models' | ||
57 | 67 | ||
58 | enum ScopeNames { | 68 | enum ScopeNames { |
59 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' | 69 | WITH_VIDEO_CHANNEL = 'WITH_VIDEO_CHANNEL' |
@@ -195,6 +205,24 @@ export class UserModel extends Model<UserModel> { | |||
195 | @Column | 205 | @Column |
196 | theme: string | 206 | theme: string |
197 | 207 | ||
208 | @AllowNull(false) | ||
209 | @Default(false) | ||
210 | @Is( | ||
211 | 'UserNoInstanceConfigWarningModal', | ||
212 | value => throwIfNotValid(value, isNoInstanceConfigWarningModal, 'no instance config warning modal') | ||
213 | ) | ||
214 | @Column | ||
215 | noInstanceConfigWarningModal: boolean | ||
216 | |||
217 | @AllowNull(false) | ||
218 | @Default(false) | ||
219 | @Is( | ||
220 | 'UserNoInstanceConfigWarningModal', | ||
221 | value => throwIfNotValid(value, isNoWelcomeModal, 'no welcome modal') | ||
222 | ) | ||
223 | @Column | ||
224 | noWelcomeModal: boolean | ||
225 | |||
198 | @CreatedAt | 226 | @CreatedAt |
199 | createdAt: Date | 227 | createdAt: Date |
200 | 228 | ||
@@ -303,7 +331,7 @@ export class UserModel extends Model<UserModel> { | |||
303 | }) | 331 | }) |
304 | } | 332 | } |
305 | 333 | ||
306 | static listWithRight (right: UserRight) { | 334 | static listWithRight (right: UserRight): Bluebird<MUserDefault[]> { |
307 | const roles = Object.keys(USER_ROLE_LABELS) | 335 | const roles = Object.keys(USER_ROLE_LABELS) |
308 | .map(k => parseInt(k, 10) as UserRole) | 336 | .map(k => parseInt(k, 10) as UserRole) |
309 | .filter(role => hasUserRight(role, right)) | 337 | .filter(role => hasUserRight(role, right)) |
@@ -319,7 +347,7 @@ export class UserModel extends Model<UserModel> { | |||
319 | return UserModel.findAll(query) | 347 | return UserModel.findAll(query) |
320 | } | 348 | } |
321 | 349 | ||
322 | static listUserSubscribersOf (actorId: number) { | 350 | static listUserSubscribersOf (actorId: number): Bluebird<MUserWithNotificationSetting[]> { |
323 | const query = { | 351 | const query = { |
324 | include: [ | 352 | include: [ |
325 | { | 353 | { |
@@ -358,7 +386,7 @@ export class UserModel extends Model<UserModel> { | |||
358 | return UserModel.unscoped().findAll(query) | 386 | return UserModel.unscoped().findAll(query) |
359 | } | 387 | } |
360 | 388 | ||
361 | static listByUsernames (usernames: string[]) { | 389 | static listByUsernames (usernames: string[]): Bluebird<MUserDefault[]> { |
362 | const query = { | 390 | const query = { |
363 | where: { | 391 | where: { |
364 | username: usernames | 392 | username: usernames |
@@ -368,11 +396,11 @@ export class UserModel extends Model<UserModel> { | |||
368 | return UserModel.findAll(query) | 396 | return UserModel.findAll(query) |
369 | } | 397 | } |
370 | 398 | ||
371 | static loadById (id: number) { | 399 | static loadById (id: number): Bluebird<MUserDefault> { |
372 | return UserModel.findByPk(id) | 400 | return UserModel.findByPk(id) |
373 | } | 401 | } |
374 | 402 | ||
375 | static loadByUsername (username: string) { | 403 | static loadByUsername (username: string): Bluebird<MUserDefault> { |
376 | const query = { | 404 | const query = { |
377 | where: { | 405 | where: { |
378 | username: { [ Op.iLike ]: username } | 406 | username: { [ Op.iLike ]: username } |
@@ -382,7 +410,7 @@ export class UserModel extends Model<UserModel> { | |||
382 | return UserModel.findOne(query) | 410 | return UserModel.findOne(query) |
383 | } | 411 | } |
384 | 412 | ||
385 | static loadByUsernameAndPopulateChannels (username: string) { | 413 | static loadByUsernameAndPopulateChannels (username: string): Bluebird<MUserNotifSettingChannelDefault> { |
386 | const query = { | 414 | const query = { |
387 | where: { | 415 | where: { |
388 | username: { [ Op.iLike ]: username } | 416 | username: { [ Op.iLike ]: username } |
@@ -392,7 +420,7 @@ export class UserModel extends Model<UserModel> { | |||
392 | return UserModel.scope(ScopeNames.WITH_VIDEO_CHANNEL).findOne(query) | 420 | return UserModel.scope(ScopeNames.WITH_VIDEO_CHANNEL).findOne(query) |
393 | } | 421 | } |
394 | 422 | ||
395 | static loadByEmail (email: string) { | 423 | static loadByEmail (email: string): Bluebird<MUserDefault> { |
396 | const query = { | 424 | const query = { |
397 | where: { | 425 | where: { |
398 | 426 | ||
@@ -402,7 +430,7 @@ export class UserModel extends Model<UserModel> { | |||
402 | return UserModel.findOne(query) | 430 | return UserModel.findOne(query) |
403 | } | 431 | } |
404 | 432 | ||
405 | static loadByUsernameOrEmail (username: string, email?: string) { | 433 | static loadByUsernameOrEmail (username: string, email?: string): Bluebird<MUserDefault> { |
406 | if (!email) email = username | 434 | if (!email) email = username |
407 | 435 | ||
408 | const query = { | 436 | const query = { |
@@ -414,7 +442,7 @@ export class UserModel extends Model<UserModel> { | |||
414 | return UserModel.findOne(query) | 442 | return UserModel.findOne(query) |
415 | } | 443 | } |
416 | 444 | ||
417 | static loadByVideoId (videoId: number) { | 445 | static loadByVideoId (videoId: number): Bluebird<MUserDefault> { |
418 | const query = { | 446 | const query = { |
419 | include: [ | 447 | include: [ |
420 | { | 448 | { |
@@ -445,7 +473,7 @@ export class UserModel extends Model<UserModel> { | |||
445 | return UserModel.findOne(query) | 473 | return UserModel.findOne(query) |
446 | } | 474 | } |
447 | 475 | ||
448 | static loadByVideoImportId (videoImportId: number) { | 476 | static loadByVideoImportId (videoImportId: number): Bluebird<MUserDefault> { |
449 | const query = { | 477 | const query = { |
450 | include: [ | 478 | include: [ |
451 | { | 479 | { |
@@ -462,7 +490,7 @@ export class UserModel extends Model<UserModel> { | |||
462 | return UserModel.findOne(query) | 490 | return UserModel.findOne(query) |
463 | } | 491 | } |
464 | 492 | ||
465 | static loadByChannelActorId (videoChannelActorId: number) { | 493 | static loadByChannelActorId (videoChannelActorId: number): Bluebird<MUserDefault> { |
466 | const query = { | 494 | const query = { |
467 | include: [ | 495 | include: [ |
468 | { | 496 | { |
@@ -486,7 +514,7 @@ export class UserModel extends Model<UserModel> { | |||
486 | return UserModel.findOne(query) | 514 | return UserModel.findOne(query) |
487 | } | 515 | } |
488 | 516 | ||
489 | static loadByAccountActorId (accountActorId: number) { | 517 | static loadByAccountActorId (accountActorId: number): Bluebird<MUserDefault> { |
490 | const query = { | 518 | const query = { |
491 | include: [ | 519 | include: [ |
492 | { | 520 | { |
@@ -503,7 +531,7 @@ export class UserModel extends Model<UserModel> { | |||
503 | return UserModel.findOne(query) | 531 | return UserModel.findOne(query) |
504 | } | 532 | } |
505 | 533 | ||
506 | static getOriginalVideoFileTotalFromUser (user: UserModel) { | 534 | static getOriginalVideoFileTotalFromUser (user: MUserId) { |
507 | // Don't use sequelize because we need to use a sub query | 535 | // Don't use sequelize because we need to use a sub query |
508 | const query = UserModel.generateUserQuotaBaseSQL() | 536 | const query = UserModel.generateUserQuotaBaseSQL() |
509 | 537 | ||
@@ -511,7 +539,7 @@ export class UserModel extends Model<UserModel> { | |||
511 | } | 539 | } |
512 | 540 | ||
513 | // Returns cumulative size of all video files uploaded in the last 24 hours. | 541 | // Returns cumulative size of all video files uploaded in the last 24 hours. |
514 | static getOriginalVideoFileTotalDailyFromUser (user: UserModel) { | 542 | static getOriginalVideoFileTotalDailyFromUser (user: MUserId) { |
515 | // Don't use sequelize because we need to use a sub query | 543 | // Don't use sequelize because we need to use a sub query |
516 | const query = UserModel.generateUserQuotaBaseSQL('"video"."createdAt" > now() - interval \'24 hours\'') | 544 | const query = UserModel.generateUserQuotaBaseSQL('"video"."createdAt" > now() - interval \'24 hours\'') |
517 | 545 | ||
@@ -552,38 +580,52 @@ export class UserModel extends Model<UserModel> { | |||
552 | return comparePassword(password, this.password) | 580 | return comparePassword(password, this.password) |
553 | } | 581 | } |
554 | 582 | ||
555 | toFormattedJSON (parameters: { withAdminFlags?: boolean } = {}): User { | 583 | toFormattedJSON (this: MUserFormattable, parameters: { withAdminFlags?: boolean } = {}): User { |
556 | const videoQuotaUsed = this.get('videoQuotaUsed') | 584 | const videoQuotaUsed = this.get('videoQuotaUsed') |
557 | const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') | 585 | const videoQuotaUsedDaily = this.get('videoQuotaUsedDaily') |
558 | 586 | ||
559 | const json = { | 587 | const json: User = { |
560 | id: this.id, | 588 | id: this.id, |
561 | username: this.username, | 589 | username: this.username, |
562 | email: this.email, | 590 | email: this.email, |
591 | theme: getThemeOrDefault(this.theme, DEFAULT_USER_THEME_NAME), | ||
592 | |||
563 | pendingEmail: this.pendingEmail, | 593 | pendingEmail: this.pendingEmail, |
564 | emailVerified: this.emailVerified, | 594 | emailVerified: this.emailVerified, |
595 | |||
565 | nsfwPolicy: this.nsfwPolicy, | 596 | nsfwPolicy: this.nsfwPolicy, |
566 | webTorrentEnabled: this.webTorrentEnabled, | 597 | webTorrentEnabled: this.webTorrentEnabled, |
567 | videosHistoryEnabled: this.videosHistoryEnabled, | 598 | videosHistoryEnabled: this.videosHistoryEnabled, |
568 | autoPlayVideo: this.autoPlayVideo, | 599 | autoPlayVideo: this.autoPlayVideo, |
569 | videoLanguages: this.videoLanguages, | 600 | videoLanguages: this.videoLanguages, |
601 | |||
570 | role: this.role, | 602 | role: this.role, |
571 | theme: getThemeOrDefault(this.theme, DEFAULT_USER_THEME_NAME), | ||
572 | roleLabel: USER_ROLE_LABELS[ this.role ], | 603 | roleLabel: USER_ROLE_LABELS[ this.role ], |
604 | |||
573 | videoQuota: this.videoQuota, | 605 | videoQuota: this.videoQuota, |
574 | videoQuotaDaily: this.videoQuotaDaily, | 606 | videoQuotaDaily: this.videoQuotaDaily, |
575 | createdAt: this.createdAt, | 607 | videoQuotaUsed: videoQuotaUsed !== undefined |
608 | ? parseInt(videoQuotaUsed + '', 10) | ||
609 | : undefined, | ||
610 | videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined | ||
611 | ? parseInt(videoQuotaUsedDaily + '', 10) | ||
612 | : undefined, | ||
613 | |||
614 | noInstanceConfigWarningModal: this.noInstanceConfigWarningModal, | ||
615 | noWelcomeModal: this.noWelcomeModal, | ||
616 | |||
576 | blocked: this.blocked, | 617 | blocked: this.blocked, |
577 | blockedReason: this.blockedReason, | 618 | blockedReason: this.blockedReason, |
619 | |||
578 | account: this.Account.toFormattedJSON(), | 620 | account: this.Account.toFormattedJSON(), |
579 | notificationSettings: this.NotificationSetting ? this.NotificationSetting.toFormattedJSON() : undefined, | 621 | |
622 | notificationSettings: this.NotificationSetting | ||
623 | ? this.NotificationSetting.toFormattedJSON() | ||
624 | : undefined, | ||
625 | |||
580 | videoChannels: [], | 626 | videoChannels: [], |
581 | videoQuotaUsed: videoQuotaUsed !== undefined | 627 | |
582 | ? parseInt(videoQuotaUsed + '', 10) | 628 | createdAt: this.createdAt |
583 | : undefined, | ||
584 | videoQuotaUsedDaily: videoQuotaUsedDaily !== undefined | ||
585 | ? parseInt(videoQuotaUsedDaily + '', 10) | ||
586 | : undefined | ||
587 | } | 629 | } |
588 | 630 | ||
589 | if (parameters.withAdminFlags) { | 631 | if (parameters.withAdminFlags) { |
diff --git a/server/models/activitypub/actor-follow.ts b/server/models/activitypub/actor-follow.ts index 51b09e09b..8498692f0 100644 --- a/server/models/activitypub/actor-follow.ts +++ b/server/models/activitypub/actor-follow.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { values } from 'lodash' | 2 | import { values, difference } from 'lodash' |
3 | import { | 3 | import { |
4 | AfterCreate, | 4 | AfterCreate, |
5 | AfterDestroy, | 5 | AfterDestroy, |
@@ -21,13 +21,20 @@ import { FollowState } from '../../../shared/models/actors' | |||
21 | import { ActorFollow } from '../../../shared/models/actors/follow.model' | 21 | import { ActorFollow } from '../../../shared/models/actors/follow.model' |
22 | import { logger } from '../../helpers/logger' | 22 | import { logger } from '../../helpers/logger' |
23 | import { getServerActor } from '../../helpers/utils' | 23 | import { getServerActor } from '../../helpers/utils' |
24 | import { ACTOR_FOLLOW_SCORE, FOLLOW_STATES } from '../../initializers/constants' | 24 | import { ACTOR_FOLLOW_SCORE, FOLLOW_STATES, SERVER_ACTOR_NAME } from '../../initializers/constants' |
25 | import { ServerModel } from '../server/server' | 25 | import { ServerModel } from '../server/server' |
26 | import { createSafeIn, getSort } from '../utils' | 26 | import { createSafeIn, getSort } from '../utils' |
27 | import { ActorModel, unusedActorAttributesForAPI } from './actor' | 27 | import { ActorModel, unusedActorAttributesForAPI } from './actor' |
28 | import { VideoChannelModel } from '../video/video-channel' | 28 | import { VideoChannelModel } from '../video/video-channel' |
29 | import { AccountModel } from '../account/account' | 29 | import { AccountModel } from '../account/account' |
30 | import { IncludeOptions, Op, Transaction, QueryTypes } from 'sequelize' | 30 | import { IncludeOptions, Op, QueryTypes, Transaction } from 'sequelize' |
31 | import { | ||
32 | MActorFollowActorsDefault, | ||
33 | MActorFollowActorsDefaultSubscription, | ||
34 | MActorFollowFollowingHost, | ||
35 | MActorFollowFormattable, | ||
36 | MActorFollowSubscriptions | ||
37 | } from '@server/typings/models' | ||
31 | 38 | ||
32 | @Table({ | 39 | @Table({ |
33 | tableName: 'actorFollow', | 40 | tableName: 'actorFollow', |
@@ -143,7 +150,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
143 | if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved) | 150 | if (numberOfActorFollowsRemoved) logger.info('Removed bad %d actor follows.', numberOfActorFollowsRemoved) |
144 | } | 151 | } |
145 | 152 | ||
146 | static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction) { | 153 | static loadByActorAndTarget (actorId: number, targetActorId: number, t?: Transaction): Bluebird<MActorFollowActorsDefault> { |
147 | const query = { | 154 | const query = { |
148 | where: { | 155 | where: { |
149 | actorId, | 156 | actorId, |
@@ -167,7 +174,12 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
167 | return ActorFollowModel.findOne(query) | 174 | return ActorFollowModel.findOne(query) |
168 | } | 175 | } |
169 | 176 | ||
170 | static loadByActorAndTargetNameAndHostForAPI (actorId: number, targetName: string, targetHost: string, t?: Transaction) { | 177 | static loadByActorAndTargetNameAndHostForAPI ( |
178 | actorId: number, | ||
179 | targetName: string, | ||
180 | targetHost: string, | ||
181 | t?: Transaction | ||
182 | ): Bluebird<MActorFollowActorsDefaultSubscription> { | ||
171 | const actorFollowingPartInclude: IncludeOptions = { | 183 | const actorFollowingPartInclude: IncludeOptions = { |
172 | model: ActorModel, | 184 | model: ActorModel, |
173 | required: true, | 185 | required: true, |
@@ -220,7 +232,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
220 | }) | 232 | }) |
221 | } | 233 | } |
222 | 234 | ||
223 | static listSubscribedIn (actorId: number, targets: { name: string, host?: string }[]) { | 235 | static listSubscribedIn (actorId: number, targets: { name: string, host?: string }[]): Bluebird<MActorFollowFollowingHost[]> { |
224 | const whereTab = targets | 236 | const whereTab = targets |
225 | .map(t => { | 237 | .map(t => { |
226 | if (t.host) { | 238 | if (t.host) { |
@@ -314,7 +326,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
314 | ] | 326 | ] |
315 | } | 327 | } |
316 | 328 | ||
317 | return ActorFollowModel.findAndCountAll(query) | 329 | return ActorFollowModel.findAndCountAll<MActorFollowActorsDefault>(query) |
318 | .then(({ rows, count }) => { | 330 | .then(({ rows, count }) => { |
319 | return { | 331 | return { |
320 | data: rows, | 332 | data: rows, |
@@ -357,7 +369,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
357 | ] | 369 | ] |
358 | } | 370 | } |
359 | 371 | ||
360 | return ActorFollowModel.findAndCountAll(query) | 372 | return ActorFollowModel.findAndCountAll<MActorFollowActorsDefault>(query) |
361 | .then(({ rows, count }) => { | 373 | .then(({ rows, count }) => { |
362 | return { | 374 | return { |
363 | data: rows, | 375 | data: rows, |
@@ -414,7 +426,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
414 | ] | 426 | ] |
415 | } | 427 | } |
416 | 428 | ||
417 | return ActorFollowModel.findAndCountAll(query) | 429 | return ActorFollowModel.findAndCountAll<MActorFollowSubscriptions>(query) |
418 | .then(({ rows, count }) => { | 430 | .then(({ rows, count }) => { |
419 | return { | 431 | return { |
420 | data: rows.map(r => r.ActorFollowing.VideoChannel), | 432 | data: rows.map(r => r.ActorFollowing.VideoChannel), |
@@ -423,6 +435,45 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
423 | }) | 435 | }) |
424 | } | 436 | } |
425 | 437 | ||
438 | static async keepUnfollowedInstance (hosts: string[]) { | ||
439 | const followerId = (await getServerActor()).id | ||
440 | |||
441 | const query = { | ||
442 | attributes: [ 'id' ], | ||
443 | where: { | ||
444 | actorId: followerId | ||
445 | }, | ||
446 | include: [ | ||
447 | { | ||
448 | attributes: [ 'id' ], | ||
449 | model: ActorModel.unscoped(), | ||
450 | required: true, | ||
451 | as: 'ActorFollowing', | ||
452 | where: { | ||
453 | preferredUsername: SERVER_ACTOR_NAME | ||
454 | }, | ||
455 | include: [ | ||
456 | { | ||
457 | attributes: [ 'host' ], | ||
458 | model: ServerModel.unscoped(), | ||
459 | required: true, | ||
460 | where: { | ||
461 | host: { | ||
462 | [Op.in]: hosts | ||
463 | } | ||
464 | } | ||
465 | } | ||
466 | ] | ||
467 | } | ||
468 | ] | ||
469 | } | ||
470 | |||
471 | const res = await ActorFollowModel.findAll(query) | ||
472 | const followedHosts = res.map(row => row.ActorFollowing.Server.host) | ||
473 | |||
474 | return difference(hosts, followedHosts) | ||
475 | } | ||
476 | |||
426 | static listAcceptedFollowerUrlsForAP (actorIds: number[], t: Transaction, start?: number, count?: number) { | 477 | static listAcceptedFollowerUrlsForAP (actorIds: number[], t: Transaction, start?: number, count?: number) { |
427 | return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count) | 478 | return ActorFollowModel.createListAcceptedFollowForApiQuery('followers', actorIds, t, start, count) |
428 | } | 479 | } |
@@ -569,7 +620,7 @@ export class ActorFollowModel extends Model<ActorFollowModel> { | |||
569 | return ActorFollowModel.findAll(query) | 620 | return ActorFollowModel.findAll(query) |
570 | } | 621 | } |
571 | 622 | ||
572 | toFormattedJSON (): ActorFollow { | 623 | toFormattedJSON (this: MActorFollowFormattable): ActorFollow { |
573 | const follower = this.ActorFollower.toFormattedJSON() | 624 | const follower = this.ActorFollower.toFormattedJSON() |
574 | const following = this.ActorFollowing.toFormattedJSON() | 625 | const following = this.ActorFollowing.toFormattedJSON() |
575 | 626 | ||
diff --git a/server/models/activitypub/actor.ts b/server/models/activitypub/actor.ts index 9cc53f78a..05de1905d 100644 --- a/server/models/activitypub/actor.ts +++ b/server/models/activitypub/actor.ts | |||
@@ -36,6 +36,17 @@ import { isOutdated, throwIfNotValid } from '../utils' | |||
36 | import { VideoChannelModel } from '../video/video-channel' | 36 | import { VideoChannelModel } from '../video/video-channel' |
37 | import { ActorFollowModel } from './actor-follow' | 37 | import { ActorFollowModel } from './actor-follow' |
38 | import { VideoModel } from '../video/video' | 38 | import { VideoModel } from '../video/video' |
39 | import { | ||
40 | MActor, | ||
41 | MActorAccountChannelId, | ||
42 | MActorAP, | ||
43 | MActorFormattable, | ||
44 | MActorFull, | ||
45 | MActorHost, | ||
46 | MActorServer, | ||
47 | MActorSummaryFormattable | ||
48 | } from '../../typings/models' | ||
49 | import * as Bluebird from 'bluebird' | ||
39 | 50 | ||
40 | enum ScopeNames { | 51 | enum ScopeNames { |
41 | FULL = 'FULL' | 52 | FULL = 'FULL' |
@@ -163,8 +174,8 @@ export class ActorModel extends Model<ActorModel> { | |||
163 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | 174 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) |
164 | inboxUrl: string | 175 | inboxUrl: string |
165 | 176 | ||
166 | @AllowNull(false) | 177 | @AllowNull(true) |
167 | @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url')) | 178 | @Is('ActorOutboxUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'outbox url', true)) |
168 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | 179 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) |
169 | outboxUrl: string | 180 | outboxUrl: string |
170 | 181 | ||
@@ -173,13 +184,13 @@ export class ActorModel extends Model<ActorModel> { | |||
173 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | 184 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) |
174 | sharedInboxUrl: string | 185 | sharedInboxUrl: string |
175 | 186 | ||
176 | @AllowNull(false) | 187 | @AllowNull(true) |
177 | @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url')) | 188 | @Is('ActorFollowersUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'followers url', true)) |
178 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | 189 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) |
179 | followersUrl: string | 190 | followersUrl: string |
180 | 191 | ||
181 | @AllowNull(false) | 192 | @AllowNull(true) |
182 | @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url')) | 193 | @Is('ActorFollowingUrl', value => throwIfNotValid(value, isActivityPubUrlValid, 'following url', true)) |
183 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) | 194 | @Column(DataType.STRING(CONSTRAINTS_FIELDS.ACTORS.URL.max)) |
184 | followingUrl: string | 195 | followingUrl: string |
185 | 196 | ||
@@ -252,11 +263,15 @@ export class ActorModel extends Model<ActorModel> { | |||
252 | }) | 263 | }) |
253 | VideoChannel: VideoChannelModel | 264 | VideoChannel: VideoChannelModel |
254 | 265 | ||
255 | static load (id: number) { | 266 | static load (id: number): Bluebird<MActor> { |
256 | return ActorModel.unscoped().findByPk(id) | 267 | return ActorModel.unscoped().findByPk(id) |
257 | } | 268 | } |
258 | 269 | ||
259 | static loadAccountActorByVideoId (videoId: number, transaction: Sequelize.Transaction) { | 270 | static loadFull (id: number): Bluebird<MActorFull> { |
271 | return ActorModel.scope(ScopeNames.FULL).findByPk(id) | ||
272 | } | ||
273 | |||
274 | static loadFromAccountByVideoId (videoId: number, transaction: Sequelize.Transaction): Bluebird<MActor> { | ||
260 | const query = { | 275 | const query = { |
261 | include: [ | 276 | include: [ |
262 | { | 277 | { |
@@ -300,7 +315,7 @@ export class ActorModel extends Model<ActorModel> { | |||
300 | .then(a => !!a) | 315 | .then(a => !!a) |
301 | } | 316 | } |
302 | 317 | ||
303 | static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction) { | 318 | static listByFollowersUrls (followersUrls: string[], transaction?: Sequelize.Transaction): Bluebird<MActorFull[]> { |
304 | const query = { | 319 | const query = { |
305 | where: { | 320 | where: { |
306 | followersUrl: { | 321 | followersUrl: { |
@@ -313,7 +328,7 @@ export class ActorModel extends Model<ActorModel> { | |||
313 | return ActorModel.scope(ScopeNames.FULL).findAll(query) | 328 | return ActorModel.scope(ScopeNames.FULL).findAll(query) |
314 | } | 329 | } |
315 | 330 | ||
316 | static loadLocalByName (preferredUsername: string, transaction?: Sequelize.Transaction) { | 331 | static loadLocalByName (preferredUsername: string, transaction?: Sequelize.Transaction): Bluebird<MActorFull> { |
317 | const query = { | 332 | const query = { |
318 | where: { | 333 | where: { |
319 | preferredUsername, | 334 | preferredUsername, |
@@ -325,7 +340,7 @@ export class ActorModel extends Model<ActorModel> { | |||
325 | return ActorModel.scope(ScopeNames.FULL).findOne(query) | 340 | return ActorModel.scope(ScopeNames.FULL).findOne(query) |
326 | } | 341 | } |
327 | 342 | ||
328 | static loadByNameAndHost (preferredUsername: string, host: string) { | 343 | static loadByNameAndHost (preferredUsername: string, host: string): Bluebird<MActorFull> { |
329 | const query = { | 344 | const query = { |
330 | where: { | 345 | where: { |
331 | preferredUsername | 346 | preferredUsername |
@@ -344,7 +359,7 @@ export class ActorModel extends Model<ActorModel> { | |||
344 | return ActorModel.scope(ScopeNames.FULL).findOne(query) | 359 | return ActorModel.scope(ScopeNames.FULL).findOne(query) |
345 | } | 360 | } |
346 | 361 | ||
347 | static loadByUrl (url: string, transaction?: Sequelize.Transaction) { | 362 | static loadByUrl (url: string, transaction?: Sequelize.Transaction): Bluebird<MActorAccountChannelId> { |
348 | const query = { | 363 | const query = { |
349 | where: { | 364 | where: { |
350 | url | 365 | url |
@@ -367,7 +382,7 @@ export class ActorModel extends Model<ActorModel> { | |||
367 | return ActorModel.unscoped().findOne(query) | 382 | return ActorModel.unscoped().findOne(query) |
368 | } | 383 | } |
369 | 384 | ||
370 | static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Sequelize.Transaction) { | 385 | static loadByUrlAndPopulateAccountAndChannel (url: string, transaction?: Sequelize.Transaction): Bluebird<MActorFull> { |
371 | const query = { | 386 | const query = { |
372 | where: { | 387 | where: { |
373 | url | 388 | url |
@@ -387,35 +402,35 @@ export class ActorModel extends Model<ActorModel> { | |||
387 | }) | 402 | }) |
388 | } | 403 | } |
389 | 404 | ||
390 | toFormattedJSON () { | 405 | toFormattedSummaryJSON (this: MActorSummaryFormattable) { |
391 | let avatar: Avatar = null | 406 | let avatar: Avatar = null |
392 | if (this.Avatar) { | 407 | if (this.Avatar) { |
393 | avatar = this.Avatar.toFormattedJSON() | 408 | avatar = this.Avatar.toFormattedJSON() |
394 | } | 409 | } |
395 | 410 | ||
396 | return { | 411 | return { |
397 | id: this.id, | ||
398 | url: this.url, | 412 | url: this.url, |
399 | name: this.preferredUsername, | 413 | name: this.preferredUsername, |
400 | host: this.getHost(), | 414 | host: this.getHost(), |
415 | avatar | ||
416 | } | ||
417 | } | ||
418 | |||
419 | toFormattedJSON (this: MActorFormattable) { | ||
420 | const base = this.toFormattedSummaryJSON() | ||
421 | |||
422 | return Object.assign(base, { | ||
423 | id: this.id, | ||
401 | hostRedundancyAllowed: this.getRedundancyAllowed(), | 424 | hostRedundancyAllowed: this.getRedundancyAllowed(), |
402 | followingCount: this.followingCount, | 425 | followingCount: this.followingCount, |
403 | followersCount: this.followersCount, | 426 | followersCount: this.followersCount, |
404 | avatar, | ||
405 | createdAt: this.createdAt, | 427 | createdAt: this.createdAt, |
406 | updatedAt: this.updatedAt | 428 | updatedAt: this.updatedAt |
407 | } | 429 | }) |
408 | } | 430 | } |
409 | 431 | ||
410 | toActivityPubObject (name: string, type: 'Account' | 'Application' | 'VideoChannel') { | 432 | toActivityPubObject (this: MActorAP, name: string) { |
411 | let activityPubType | 433 | let activityPubType |
412 | if (type === 'Account') { | ||
413 | activityPubType = 'Person' as 'Person' | ||
414 | } else if (type === 'Application') { | ||
415 | activityPubType = 'Application' as 'Application' | ||
416 | } else { // VideoChannel | ||
417 | activityPubType = 'Group' as 'Group' | ||
418 | } | ||
419 | 434 | ||
420 | let icon = undefined | 435 | let icon = undefined |
421 | if (this.avatarId) { | 436 | if (this.avatarId) { |
@@ -428,7 +443,7 @@ export class ActorModel extends Model<ActorModel> { | |||
428 | } | 443 | } |
429 | 444 | ||
430 | const json = { | 445 | const json = { |
431 | type: activityPubType, | 446 | type: this.type, |
432 | id: this.url, | 447 | id: this.url, |
433 | following: this.getFollowingUrl(), | 448 | following: this.getFollowingUrl(), |
434 | followers: this.getFollowersUrl(), | 449 | followers: this.getFollowersUrl(), |
@@ -494,7 +509,7 @@ export class ActorModel extends Model<ActorModel> { | |||
494 | return this.serverId === null | 509 | return this.serverId === null |
495 | } | 510 | } |
496 | 511 | ||
497 | getWebfingerUrl () { | 512 | getWebfingerUrl (this: MActorServer) { |
498 | return 'acct:' + this.preferredUsername + '@' + this.getHost() | 513 | return 'acct:' + this.preferredUsername + '@' + this.getHost() |
499 | } | 514 | } |
500 | 515 | ||
@@ -502,7 +517,7 @@ export class ActorModel extends Model<ActorModel> { | |||
502 | return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername | 517 | return this.Server ? `${this.preferredUsername}@${this.Server.host}` : this.preferredUsername |
503 | } | 518 | } |
504 | 519 | ||
505 | getHost () { | 520 | getHost (this: MActorHost) { |
506 | return this.Server ? this.Server.host : WEBSERVER.HOST | 521 | return this.Server ? this.Server.host : WEBSERVER.HOST |
507 | } | 522 | } |
508 | 523 | ||
diff --git a/server/models/avatar/avatar.ts b/server/models/avatar/avatar.ts index b40144592..950e4b181 100644 --- a/server/models/avatar/avatar.ts +++ b/server/models/avatar/avatar.ts | |||
@@ -7,6 +7,7 @@ import { remove } from 'fs-extra' | |||
7 | import { CONFIG } from '../../initializers/config' | 7 | import { CONFIG } from '../../initializers/config' |
8 | import { throwIfNotValid } from '../utils' | 8 | import { throwIfNotValid } from '../utils' |
9 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 9 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
10 | import { MAvatarFormattable } from '@server/typings/models' | ||
10 | 11 | ||
11 | @Table({ | 12 | @Table({ |
12 | tableName: 'avatar', | 13 | tableName: 'avatar', |
@@ -57,7 +58,7 @@ export class AvatarModel extends Model<AvatarModel> { | |||
57 | return AvatarModel.findOne(query) | 58 | return AvatarModel.findOne(query) |
58 | } | 59 | } |
59 | 60 | ||
60 | toFormattedJSON (): Avatar { | 61 | toFormattedJSON (this: MAvatarFormattable): Avatar { |
61 | return { | 62 | return { |
62 | path: this.getStaticPath(), | 63 | path: this.getStaticPath(), |
63 | createdAt: this.createdAt, | 64 | createdAt: this.createdAt, |
diff --git a/server/models/oauth/oauth-token.ts b/server/models/oauth/oauth-token.ts index 903d551df..b680be237 100644 --- a/server/models/oauth/oauth-token.ts +++ b/server/models/oauth/oauth-token.ts | |||
@@ -18,6 +18,8 @@ import { Transaction } from 'sequelize' | |||
18 | import { AccountModel } from '../account/account' | 18 | import { AccountModel } from '../account/account' |
19 | import { ActorModel } from '../activitypub/actor' | 19 | import { ActorModel } from '../activitypub/actor' |
20 | import { clearCacheByToken } from '../../lib/oauth-model' | 20 | import { clearCacheByToken } from '../../lib/oauth-model' |
21 | import * as Bluebird from 'bluebird' | ||
22 | import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' | ||
21 | 23 | ||
22 | export type OAuthTokenInfo = { | 24 | export type OAuthTokenInfo = { |
23 | refreshToken: string | 25 | refreshToken: string |
@@ -160,7 +162,7 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> { | |||
160 | }) | 162 | }) |
161 | } | 163 | } |
162 | 164 | ||
163 | static getByTokenAndPopulateUser (bearerToken: string) { | 165 | static getByTokenAndPopulateUser (bearerToken: string): Bluebird<MOAuthTokenUser> { |
164 | const query = { | 166 | const query = { |
165 | where: { | 167 | where: { |
166 | accessToken: bearerToken | 168 | accessToken: bearerToken |
@@ -170,13 +172,13 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> { | |||
170 | return OAuthTokenModel.scope(ScopeNames.WITH_USER) | 172 | return OAuthTokenModel.scope(ScopeNames.WITH_USER) |
171 | .findOne(query) | 173 | .findOne(query) |
172 | .then(token => { | 174 | .then(token => { |
173 | if (token) token[ 'user' ] = token.User | 175 | if (!token) return null |
174 | 176 | ||
175 | return token | 177 | return Object.assign(token, { user: token.User }) |
176 | }) | 178 | }) |
177 | } | 179 | } |
178 | 180 | ||
179 | static getByRefreshTokenAndPopulateUser (refreshToken: string) { | 181 | static getByRefreshTokenAndPopulateUser (refreshToken: string): Bluebird<MOAuthTokenUser> { |
180 | const query = { | 182 | const query = { |
181 | where: { | 183 | where: { |
182 | refreshToken: refreshToken | 184 | refreshToken: refreshToken |
@@ -186,12 +188,9 @@ export class OAuthTokenModel extends Model<OAuthTokenModel> { | |||
186 | return OAuthTokenModel.scope(ScopeNames.WITH_USER) | 188 | return OAuthTokenModel.scope(ScopeNames.WITH_USER) |
187 | .findOne(query) | 189 | .findOne(query) |
188 | .then(token => { | 190 | .then(token => { |
189 | if (token) { | 191 | if (!token) return new OAuthTokenModel() |
190 | token['user'] = token.User | 192 | |
191 | return token | 193 | return Object.assign(token, { user: token.User }) |
192 | } else { | ||
193 | return new OAuthTokenModel() | ||
194 | } | ||
195 | }) | 194 | }) |
196 | } | 195 | } |
197 | 196 | ||
diff --git a/server/models/redundancy/video-redundancy.ts b/server/models/redundancy/video-redundancy.ts index 3df1c4f9c..61d9a5612 100644 --- a/server/models/redundancy/video-redundancy.ts +++ b/server/models/redundancy/video-redundancy.ts | |||
@@ -30,6 +30,7 @@ import * as Bluebird from 'bluebird' | |||
30 | import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize' | 30 | import { col, FindOptions, fn, literal, Op, Transaction } from 'sequelize' |
31 | import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' | 31 | import { VideoStreamingPlaylistModel } from '../video/video-streaming-playlist' |
32 | import { CONFIG } from '../../initializers/config' | 32 | import { CONFIG } from '../../initializers/config' |
33 | import { MVideoRedundancy, MVideoRedundancyAP, MVideoRedundancyVideo } from '@server/typings/models' | ||
33 | 34 | ||
34 | export enum ScopeNames { | 35 | export enum ScopeNames { |
35 | WITH_VIDEO = 'WITH_VIDEO' | 36 | WITH_VIDEO = 'WITH_VIDEO' |
@@ -166,7 +167,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
166 | return undefined | 167 | return undefined |
167 | } | 168 | } |
168 | 169 | ||
169 | static async loadLocalByFileId (videoFileId: number) { | 170 | static async loadLocalByFileId (videoFileId: number): Promise<MVideoRedundancyVideo> { |
170 | const actor = await getServerActor() | 171 | const actor = await getServerActor() |
171 | 172 | ||
172 | const query = { | 173 | const query = { |
@@ -179,7 +180,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
179 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) | 180 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) |
180 | } | 181 | } |
181 | 182 | ||
182 | static async loadLocalByStreamingPlaylistId (videoStreamingPlaylistId: number) { | 183 | static async loadLocalByStreamingPlaylistId (videoStreamingPlaylistId: number): Promise<MVideoRedundancyVideo> { |
183 | const actor = await getServerActor() | 184 | const actor = await getServerActor() |
184 | 185 | ||
185 | const query = { | 186 | const query = { |
@@ -192,7 +193,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
192 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) | 193 | return VideoRedundancyModel.scope(ScopeNames.WITH_VIDEO).findOne(query) |
193 | } | 194 | } |
194 | 195 | ||
195 | static loadByUrl (url: string, transaction?: Transaction) { | 196 | static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoRedundancy> { |
196 | const query = { | 197 | const query = { |
197 | where: { | 198 | where: { |
198 | url | 199 | url |
@@ -306,7 +307,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
306 | return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query)) | 307 | return VideoRedundancyModel.getVideoSample(VideoModel.unscoped().findAll(query)) |
307 | } | 308 | } |
308 | 309 | ||
309 | static async loadOldestLocalThatAlreadyExpired (strategy: VideoRedundancyStrategy, expiresAfterMs: number) { | 310 | static async loadOldestLocalExpired (strategy: VideoRedundancyStrategy, expiresAfterMs: number): Promise<MVideoRedundancyVideo> { |
310 | const expiredDate = new Date() | 311 | const expiredDate = new Date() |
311 | expiredDate.setMilliseconds(expiredDate.getMilliseconds() - expiresAfterMs) | 312 | expiredDate.setMilliseconds(expiredDate.getMilliseconds() - expiresAfterMs) |
312 | 313 | ||
@@ -487,7 +488,7 @@ export class VideoRedundancyModel extends Model<VideoRedundancyModel> { | |||
487 | return !!this.strategy | 488 | return !!this.strategy |
488 | } | 489 | } |
489 | 490 | ||
490 | toActivityPubObject (): CacheFileObject { | 491 | toActivityPubObject (this: MVideoRedundancyAP): CacheFileObject { |
491 | if (this.VideoStreamingPlaylist) { | 492 | if (this.VideoStreamingPlaylist) { |
492 | return { | 493 | return { |
493 | id: this.url, | 494 | id: this.url, |
diff --git a/server/models/server/plugin.ts b/server/models/server/plugin.ts index a15f9a7e2..d094da1f5 100644 --- a/server/models/server/plugin.ts +++ b/server/models/server/plugin.ts | |||
@@ -11,6 +11,8 @@ import { PluginType } from '../../../shared/models/plugins/plugin.type' | |||
11 | import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model' | 11 | import { PeerTubePlugin } from '../../../shared/models/plugins/peertube-plugin.model' |
12 | import { FindAndCountOptions, json } from 'sequelize' | 12 | import { FindAndCountOptions, json } from 'sequelize' |
13 | import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' | 13 | import { RegisterServerSettingOptions } from '../../../shared/models/plugins/register-server-setting.model' |
14 | import * as Bluebird from 'bluebird' | ||
15 | import { MPlugin, MPluginFormattable } from '@server/typings/models' | ||
14 | 16 | ||
15 | @DefaultScope(() => ({ | 17 | @DefaultScope(() => ({ |
16 | attributes: { | 18 | attributes: { |
@@ -85,7 +87,7 @@ export class PluginModel extends Model<PluginModel> { | |||
85 | @UpdatedAt | 87 | @UpdatedAt |
86 | updatedAt: Date | 88 | updatedAt: Date |
87 | 89 | ||
88 | static listEnabledPluginsAndThemes () { | 90 | static listEnabledPluginsAndThemes (): Bluebird<MPlugin[]> { |
89 | const query = { | 91 | const query = { |
90 | where: { | 92 | where: { |
91 | enabled: true, | 93 | enabled: true, |
@@ -96,7 +98,7 @@ export class PluginModel extends Model<PluginModel> { | |||
96 | return PluginModel.findAll(query) | 98 | return PluginModel.findAll(query) |
97 | } | 99 | } |
98 | 100 | ||
99 | static loadByNpmName (npmName: string) { | 101 | static loadByNpmName (npmName: string): Bluebird<MPlugin> { |
100 | const name = this.normalizePluginName(npmName) | 102 | const name = this.normalizePluginName(npmName) |
101 | const type = this.getTypeFromNpmName(npmName) | 103 | const type = this.getTypeFromNpmName(npmName) |
102 | 104 | ||
@@ -206,13 +208,13 @@ export class PluginModel extends Model<PluginModel> { | |||
206 | if (options.pluginType) query.where['type'] = options.pluginType | 208 | if (options.pluginType) query.where['type'] = options.pluginType |
207 | 209 | ||
208 | return PluginModel | 210 | return PluginModel |
209 | .findAndCountAll(query) | 211 | .findAndCountAll<MPlugin>(query) |
210 | .then(({ rows, count }) => { | 212 | .then(({ rows, count }) => { |
211 | return { total: count, data: rows } | 213 | return { total: count, data: rows } |
212 | }) | 214 | }) |
213 | } | 215 | } |
214 | 216 | ||
215 | static listInstalled () { | 217 | static listInstalled (): Bluebird<MPlugin[]> { |
216 | const query = { | 218 | const query = { |
217 | where: { | 219 | where: { |
218 | uninstalled: false | 220 | uninstalled: false |
@@ -251,7 +253,7 @@ export class PluginModel extends Model<PluginModel> { | |||
251 | return result | 253 | return result |
252 | } | 254 | } |
253 | 255 | ||
254 | toFormattedJSON (): PeerTubePlugin { | 256 | toFormattedJSON (this: MPluginFormattable): PeerTubePlugin { |
255 | return { | 257 | return { |
256 | name: this.name, | 258 | name: this.name, |
257 | type: this.type, | 259 | type: this.type, |
diff --git a/server/models/server/server-blocklist.ts b/server/models/server/server-blocklist.ts index 5138b0f76..3e9687191 100644 --- a/server/models/server/server-blocklist.ts +++ b/server/models/server/server-blocklist.ts | |||
@@ -3,6 +3,8 @@ import { AccountModel } from '../account/account' | |||
3 | import { ServerModel } from './server' | 3 | import { ServerModel } from './server' |
4 | import { ServerBlock } from '../../../shared/models/blocklist' | 4 | import { ServerBlock } from '../../../shared/models/blocklist' |
5 | import { getSort } from '../utils' | 5 | import { getSort } from '../utils' |
6 | import * as Bluebird from 'bluebird' | ||
7 | import { MServerBlocklist, MServerBlocklistAccountServer, MServerBlocklistFormattable } from '@server/typings/models' | ||
6 | 8 | ||
7 | enum ScopeNames { | 9 | enum ScopeNames { |
8 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 10 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
@@ -73,7 +75,7 @@ export class ServerBlocklistModel extends Model<ServerBlocklistModel> { | |||
73 | }) | 75 | }) |
74 | BlockedServer: ServerModel | 76 | BlockedServer: ServerModel |
75 | 77 | ||
76 | static loadByAccountAndHost (accountId: number, host: string) { | 78 | static loadByAccountAndHost (accountId: number, host: string): Bluebird<MServerBlocklist> { |
77 | const query = { | 79 | const query = { |
78 | where: { | 80 | where: { |
79 | accountId | 81 | accountId |
@@ -104,13 +106,13 @@ export class ServerBlocklistModel extends Model<ServerBlocklistModel> { | |||
104 | 106 | ||
105 | return ServerBlocklistModel | 107 | return ServerBlocklistModel |
106 | .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_SERVER ]) | 108 | .scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_SERVER ]) |
107 | .findAndCountAll(query) | 109 | .findAndCountAll<MServerBlocklistAccountServer>(query) |
108 | .then(({ rows, count }) => { | 110 | .then(({ rows, count }) => { |
109 | return { total: count, data: rows } | 111 | return { total: count, data: rows } |
110 | }) | 112 | }) |
111 | } | 113 | } |
112 | 114 | ||
113 | toFormattedJSON (): ServerBlock { | 115 | toFormattedJSON (this: MServerBlocklistFormattable): ServerBlock { |
114 | return { | 116 | return { |
115 | byAccount: this.ByAccount.toFormattedJSON(), | 117 | byAccount: this.ByAccount.toFormattedJSON(), |
116 | blockedServer: this.BlockedServer.toFormattedJSON(), | 118 | blockedServer: this.BlockedServer.toFormattedJSON(), |
diff --git a/server/models/server/server.ts b/server/models/server/server.ts index 1d211f1e0..8b07115f1 100644 --- a/server/models/server/server.ts +++ b/server/models/server/server.ts | |||
@@ -2,8 +2,9 @@ import { AllowNull, Column, CreatedAt, Default, HasMany, Is, Model, Table, Updat | |||
2 | import { isHostValid } from '../../helpers/custom-validators/servers' | 2 | import { isHostValid } from '../../helpers/custom-validators/servers' |
3 | import { ActorModel } from '../activitypub/actor' | 3 | import { ActorModel } from '../activitypub/actor' |
4 | import { throwIfNotValid } from '../utils' | 4 | import { throwIfNotValid } from '../utils' |
5 | import { AccountBlocklistModel } from '../account/account-blocklist' | ||
6 | import { ServerBlocklistModel } from './server-blocklist' | 5 | import { ServerBlocklistModel } from './server-blocklist' |
6 | import * as Bluebird from 'bluebird' | ||
7 | import { MServer, MServerFormattable } from '@server/typings/models/server' | ||
7 | 8 | ||
8 | @Table({ | 9 | @Table({ |
9 | tableName: 'server', | 10 | tableName: 'server', |
@@ -50,7 +51,17 @@ export class ServerModel extends Model<ServerModel> { | |||
50 | }) | 51 | }) |
51 | BlockedByAccounts: ServerBlocklistModel[] | 52 | BlockedByAccounts: ServerBlocklistModel[] |
52 | 53 | ||
53 | static loadByHost (host: string) { | 54 | static load (id: number): Bluebird<MServer> { |
55 | const query = { | ||
56 | where: { | ||
57 | id | ||
58 | } | ||
59 | } | ||
60 | |||
61 | return ServerModel.findOne(query) | ||
62 | } | ||
63 | |||
64 | static loadByHost (host: string): Bluebird<MServer> { | ||
54 | const query = { | 65 | const query = { |
55 | where: { | 66 | where: { |
56 | host | 67 | host |
@@ -64,7 +75,7 @@ export class ServerModel extends Model<ServerModel> { | |||
64 | return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0 | 75 | return this.BlockedByAccounts && this.BlockedByAccounts.length !== 0 |
65 | } | 76 | } |
66 | 77 | ||
67 | toFormattedJSON () { | 78 | toFormattedJSON (this: MServerFormattable) { |
68 | return { | 79 | return { |
69 | host: this.host | 80 | host: this.host |
70 | } | 81 | } |
diff --git a/server/models/video/schedule-video-update.ts b/server/models/video/schedule-video-update.ts index 603d55692..fc2a424aa 100644 --- a/server/models/video/schedule-video-update.ts +++ b/server/models/video/schedule-video-update.ts | |||
@@ -2,6 +2,7 @@ import { AllowNull, BelongsTo, Column, CreatedAt, Default, ForeignKey, Model, Ta | |||
2 | import { ScopeNames as VideoScopeNames, VideoModel } from './video' | 2 | import { ScopeNames as VideoScopeNames, VideoModel } from './video' |
3 | import { VideoPrivacy } from '../../../shared/models/videos' | 3 | import { VideoPrivacy } from '../../../shared/models/videos' |
4 | import { Op, Transaction } from 'sequelize' | 4 | import { Op, Transaction } from 'sequelize' |
5 | import { MScheduleVideoUpdateFormattable } from '@server/typings/models' | ||
5 | 6 | ||
6 | @Table({ | 7 | @Table({ |
7 | tableName: 'scheduleVideoUpdate', | 8 | tableName: 'scheduleVideoUpdate', |
@@ -96,7 +97,7 @@ export class ScheduleVideoUpdateModel extends Model<ScheduleVideoUpdateModel> { | |||
96 | return ScheduleVideoUpdateModel.destroy(query) | 97 | return ScheduleVideoUpdateModel.destroy(query) |
97 | } | 98 | } |
98 | 99 | ||
99 | toFormattedJSON () { | 100 | toFormattedJSON (this: MScheduleVideoUpdateFormattable) { |
100 | return { | 101 | return { |
101 | updateAt: this.updateAt, | 102 | updateAt: this.updateAt, |
102 | privacy: this.privacy || undefined | 103 | privacy: this.privacy || undefined |
diff --git a/server/models/video/tag.ts b/server/models/video/tag.ts index 0fc3cfd4c..ed8df8b48 100644 --- a/server/models/video/tag.ts +++ b/server/models/video/tag.ts | |||
@@ -1,11 +1,12 @@ | |||
1 | import * as Bluebird from 'bluebird' | 1 | import * as Bluebird from 'bluebird' |
2 | import { QueryTypes, Transaction } from 'sequelize' | 2 | import { fn, QueryTypes, Transaction, col } from 'sequelize' |
3 | import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 3 | import { AllowNull, BelongsToMany, Column, CreatedAt, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
4 | import { isVideoTagValid } from '../../helpers/custom-validators/videos' | 4 | import { isVideoTagValid } from '../../helpers/custom-validators/videos' |
5 | import { throwIfNotValid } from '../utils' | 5 | import { throwIfNotValid } from '../utils' |
6 | import { VideoModel } from './video' | 6 | import { VideoModel } from './video' |
7 | import { VideoTagModel } from './video-tag' | 7 | import { VideoTagModel } from './video-tag' |
8 | import { VideoPrivacy, VideoState } from '../../../shared/models/videos' | 8 | import { VideoPrivacy, VideoState } from '../../../shared/models/videos' |
9 | import { MTag } from '@server/typings/models' | ||
9 | 10 | ||
10 | @Table({ | 11 | @Table({ |
11 | tableName: 'tag', | 12 | tableName: 'tag', |
@@ -14,6 +15,10 @@ import { VideoPrivacy, VideoState } from '../../../shared/models/videos' | |||
14 | { | 15 | { |
15 | fields: [ 'name' ], | 16 | fields: [ 'name' ], |
16 | unique: true | 17 | unique: true |
18 | }, | ||
19 | { | ||
20 | name: 'tag_lower_name', | ||
21 | fields: [ fn('lower', col('name')) ] as any // FIXME: typings | ||
17 | } | 22 | } |
18 | ] | 23 | ] |
19 | }) | 24 | }) |
@@ -37,10 +42,10 @@ export class TagModel extends Model<TagModel> { | |||
37 | }) | 42 | }) |
38 | Videos: VideoModel[] | 43 | Videos: VideoModel[] |
39 | 44 | ||
40 | static findOrCreateTags (tags: string[], transaction: Transaction) { | 45 | static findOrCreateTags (tags: string[], transaction: Transaction): Promise<MTag[]> { |
41 | if (tags === null) return [] | 46 | if (tags === null) return Promise.resolve([]) |
42 | 47 | ||
43 | const tasks: Bluebird<TagModel>[] = [] | 48 | const tasks: Bluebird<MTag>[] = [] |
44 | tags.forEach(tag => { | 49 | tags.forEach(tag => { |
45 | const query = { | 50 | const query = { |
46 | where: { | 51 | where: { |
@@ -52,7 +57,7 @@ export class TagModel extends Model<TagModel> { | |||
52 | transaction | 57 | transaction |
53 | } | 58 | } |
54 | 59 | ||
55 | const promise = TagModel.findOrCreate(query) | 60 | const promise = TagModel.findOrCreate<MTag>(query) |
56 | .then(([ tagInstance ]) => tagInstance) | 61 | .then(([ tagInstance ]) => tagInstance) |
57 | tasks.push(promise) | 62 | tasks.push(promise) |
58 | }) | 63 | }) |
diff --git a/server/models/video/video-abuse.ts b/server/models/video/video-abuse.ts index 1ac7919b3..3636db18d 100644 --- a/server/models/video/video-abuse.ts +++ b/server/models/video/video-abuse.ts | |||
@@ -7,10 +7,13 @@ import { | |||
7 | isVideoAbuseStateValid | 7 | isVideoAbuseStateValid |
8 | } from '../../helpers/custom-validators/video-abuses' | 8 | } from '../../helpers/custom-validators/video-abuses' |
9 | import { AccountModel } from '../account/account' | 9 | import { AccountModel } from '../account/account' |
10 | import { getSort, throwIfNotValid } from '../utils' | 10 | import { buildBlockedAccountSQL, getSort, throwIfNotValid } from '../utils' |
11 | import { VideoModel } from './video' | 11 | import { VideoModel } from './video' |
12 | import { VideoAbuseState } from '../../../shared' | 12 | import { VideoAbuseState } from '../../../shared' |
13 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' | 13 | import { CONSTRAINTS_FIELDS, VIDEO_ABUSE_STATES } from '../../initializers/constants' |
14 | import { MUserAccountId, MVideoAbuse, MVideoAbuseFormattable, MVideoAbuseVideo } from '../../typings/models' | ||
15 | import * as Bluebird from 'bluebird' | ||
16 | import { literal, Op } from 'sequelize' | ||
14 | 17 | ||
15 | @Table({ | 18 | @Table({ |
16 | tableName: 'videoAbuse', | 19 | tableName: 'videoAbuse', |
@@ -73,7 +76,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
73 | }) | 76 | }) |
74 | Video: VideoModel | 77 | Video: VideoModel |
75 | 78 | ||
76 | static loadByIdAndVideoId (id: number, videoId: number) { | 79 | static loadByIdAndVideoId (id: number, videoId: number): Bluebird<MVideoAbuse> { |
77 | const query = { | 80 | const query = { |
78 | where: { | 81 | where: { |
79 | id, | 82 | id, |
@@ -83,11 +86,25 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
83 | return VideoAbuseModel.findOne(query) | 86 | return VideoAbuseModel.findOne(query) |
84 | } | 87 | } |
85 | 88 | ||
86 | static listForApi (start: number, count: number, sort: string) { | 89 | static listForApi (parameters: { |
90 | start: number, | ||
91 | count: number, | ||
92 | sort: string, | ||
93 | serverAccountId: number | ||
94 | user?: MUserAccountId | ||
95 | }) { | ||
96 | const { start, count, sort, user, serverAccountId } = parameters | ||
97 | const userAccountId = user ? user.Account.id : undefined | ||
98 | |||
87 | const query = { | 99 | const query = { |
88 | offset: start, | 100 | offset: start, |
89 | limit: count, | 101 | limit: count, |
90 | order: getSort(sort), | 102 | order: getSort(sort), |
103 | where: { | ||
104 | reporterAccountId: { | ||
105 | [Op.notIn]: literal('(' + buildBlockedAccountSQL(serverAccountId, userAccountId) + ')') | ||
106 | } | ||
107 | }, | ||
91 | include: [ | 108 | include: [ |
92 | { | 109 | { |
93 | model: AccountModel, | 110 | model: AccountModel, |
@@ -106,7 +123,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
106 | }) | 123 | }) |
107 | } | 124 | } |
108 | 125 | ||
109 | toFormattedJSON (): VideoAbuse { | 126 | toFormattedJSON (this: MVideoAbuseFormattable): VideoAbuse { |
110 | return { | 127 | return { |
111 | id: this.id, | 128 | id: this.id, |
112 | reason: this.reason, | 129 | reason: this.reason, |
@@ -125,7 +142,7 @@ export class VideoAbuseModel extends Model<VideoAbuseModel> { | |||
125 | } | 142 | } |
126 | } | 143 | } |
127 | 144 | ||
128 | toActivityPubObject (): VideoAbuseObject { | 145 | toActivityPubObject (this: MVideoAbuseVideo): VideoAbuseObject { |
129 | return { | 146 | return { |
130 | type: 'Flag' as 'Flag', | 147 | type: 'Flag' as 'Flag', |
131 | content: this.reason, | 148 | content: this.reason, |
diff --git a/server/models/video/video-blacklist.ts b/server/models/video/video-blacklist.ts index cdb725e7a..533bfe1ad 100644 --- a/server/models/video/video-blacklist.ts +++ b/server/models/video/video-blacklist.ts | |||
@@ -1,12 +1,14 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, Default, ForeignKey, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { getBlacklistSort, getSort, SortType, throwIfNotValid } from '../utils' | 2 | import { getBlacklistSort, SortType, throwIfNotValid } from '../utils' |
3 | import { ScopeNames as VideoModelScopeNames, VideoModel } from './video' | 3 | import { VideoModel } from './video' |
4 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' | 4 | import { ScopeNames as VideoChannelScopeNames, SummaryOptions, VideoChannelModel } from './video-channel' |
5 | import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' | 5 | import { isVideoBlacklistReasonValid, isVideoBlacklistTypeValid } from '../../helpers/custom-validators/video-blacklist' |
6 | import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos' | 6 | import { VideoBlacklist, VideoBlacklistType } from '../../../shared/models/videos' |
7 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | 7 | import { CONSTRAINTS_FIELDS } from '../../initializers/constants' |
8 | import { FindOptions, literal } from 'sequelize' | 8 | import { FindOptions, literal } from 'sequelize' |
9 | import { ThumbnailModel } from './thumbnail' | 9 | import { ThumbnailModel } from './thumbnail' |
10 | import * as Bluebird from 'bluebird' | ||
11 | import { MVideoBlacklist, MVideoBlacklistFormattable } from '@server/typings/models' | ||
10 | 12 | ||
11 | @Table({ | 13 | @Table({ |
12 | tableName: 'videoBlacklist', | 14 | tableName: 'videoBlacklist', |
@@ -98,7 +100,7 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> { | |||
98 | }) | 100 | }) |
99 | } | 101 | } |
100 | 102 | ||
101 | static loadByVideoId (id: number) { | 103 | static loadByVideoId (id: number): Bluebird<MVideoBlacklist> { |
102 | const query = { | 104 | const query = { |
103 | where: { | 105 | where: { |
104 | videoId: id | 106 | videoId: id |
@@ -108,7 +110,7 @@ export class VideoBlacklistModel extends Model<VideoBlacklistModel> { | |||
108 | return VideoBlacklistModel.findOne(query) | 110 | return VideoBlacklistModel.findOne(query) |
109 | } | 111 | } |
110 | 112 | ||
111 | toFormattedJSON (): VideoBlacklist { | 113 | toFormattedJSON (this: MVideoBlacklistFormattable): VideoBlacklist { |
112 | return { | 114 | return { |
113 | id: this.id, | 115 | id: this.id, |
114 | createdAt: this.createdAt, | 116 | createdAt: this.createdAt, |
diff --git a/server/models/video/video-caption.ts b/server/models/video/video-caption.ts index a01565851..ad5801768 100644 --- a/server/models/video/video-caption.ts +++ b/server/models/video/video-caption.ts | |||
@@ -21,6 +21,8 @@ import { join } from 'path' | |||
21 | import { logger } from '../../helpers/logger' | 21 | import { logger } from '../../helpers/logger' |
22 | import { remove } from 'fs-extra' | 22 | import { remove } from 'fs-extra' |
23 | import { CONFIG } from '../../initializers/config' | 23 | import { CONFIG } from '../../initializers/config' |
24 | import * as Bluebird from 'bluebird' | ||
25 | import { MVideoCaptionFormattable, MVideoCaptionVideo } from '@server/typings/models' | ||
24 | 26 | ||
25 | export enum ScopeNames { | 27 | export enum ScopeNames { |
26 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' | 28 | WITH_VIDEO_UUID_AND_REMOTE = 'WITH_VIDEO_UUID_AND_REMOTE' |
@@ -30,7 +32,7 @@ export enum ScopeNames { | |||
30 | [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { | 32 | [ScopeNames.WITH_VIDEO_UUID_AND_REMOTE]: { |
31 | include: [ | 33 | include: [ |
32 | { | 34 | { |
33 | attributes: [ 'uuid', 'remote' ], | 35 | attributes: [ 'id', 'uuid', 'remote' ], |
34 | model: VideoModel.unscoped(), | 36 | model: VideoModel.unscoped(), |
35 | required: true | 37 | required: true |
36 | } | 38 | } |
@@ -93,7 +95,7 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
93 | return undefined | 95 | return undefined |
94 | } | 96 | } |
95 | 97 | ||
96 | static loadByVideoIdAndLanguage (videoId: string | number, language: string) { | 98 | static loadByVideoIdAndLanguage (videoId: string | number, language: string): Bluebird<MVideoCaptionVideo> { |
97 | const videoInclude = { | 99 | const videoInclude = { |
98 | model: VideoModel.unscoped(), | 100 | model: VideoModel.unscoped(), |
99 | attributes: [ 'id', 'remote', 'uuid' ], | 101 | attributes: [ 'id', 'remote', 'uuid' ], |
@@ -122,7 +124,7 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
122 | .then(([ caption ]) => caption) | 124 | .then(([ caption ]) => caption) |
123 | } | 125 | } |
124 | 126 | ||
125 | static listVideoCaptions (videoId: number) { | 127 | static listVideoCaptions (videoId: number): Bluebird<MVideoCaptionVideo[]> { |
126 | const query = { | 128 | const query = { |
127 | order: [ [ 'language', 'ASC' ] ] as OrderItem[], | 129 | order: [ [ 'language', 'ASC' ] ] as OrderItem[], |
128 | where: { | 130 | where: { |
@@ -152,7 +154,7 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
152 | return this.Video.remote === false | 154 | return this.Video.remote === false |
153 | } | 155 | } |
154 | 156 | ||
155 | toFormattedJSON (): VideoCaption { | 157 | toFormattedJSON (this: MVideoCaptionFormattable): VideoCaption { |
156 | return { | 158 | return { |
157 | language: { | 159 | language: { |
158 | id: this.language, | 160 | id: this.language, |
@@ -162,15 +164,15 @@ export class VideoCaptionModel extends Model<VideoCaptionModel> { | |||
162 | } | 164 | } |
163 | } | 165 | } |
164 | 166 | ||
165 | getCaptionStaticPath () { | 167 | getCaptionStaticPath (this: MVideoCaptionFormattable) { |
166 | return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName()) | 168 | return join(LAZY_STATIC_PATHS.VIDEO_CAPTIONS, this.getCaptionName()) |
167 | } | 169 | } |
168 | 170 | ||
169 | getCaptionName () { | 171 | getCaptionName (this: MVideoCaptionFormattable) { |
170 | return `${this.Video.uuid}-${this.language}.vtt` | 172 | return `${this.Video.uuid}-${this.language}.vtt` |
171 | } | 173 | } |
172 | 174 | ||
173 | removeCaptionFile () { | 175 | removeCaptionFile (this: MVideoCaptionFormattable) { |
174 | return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName()) | 176 | return remove(CONFIG.STORAGE.CAPTIONS_DIR + this.getCaptionName()) |
175 | } | 177 | } |
176 | } | 178 | } |
diff --git a/server/models/video/video-change-ownership.ts b/server/models/video/video-change-ownership.ts index b545a2f8c..f7a351329 100644 --- a/server/models/video/video-change-ownership.ts +++ b/server/models/video/video-change-ownership.ts | |||
@@ -3,6 +3,8 @@ import { AccountModel } from '../account/account' | |||
3 | import { ScopeNames as VideoScopeNames, VideoModel } from './video' | 3 | import { ScopeNames as VideoScopeNames, VideoModel } from './video' |
4 | import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos' | 4 | import { VideoChangeOwnership, VideoChangeOwnershipStatus } from '../../../shared/models/videos' |
5 | import { getSort } from '../utils' | 5 | import { getSort } from '../utils' |
6 | import { MVideoChangeOwnershipFormattable, MVideoChangeOwnershipFull } from '@server/typings/models/video/video-change-ownership' | ||
7 | import * as Bluebird from 'bluebird' | ||
6 | 8 | ||
7 | enum ScopeNames { | 9 | enum ScopeNames { |
8 | WITH_ACCOUNTS = 'WITH_ACCOUNTS', | 10 | WITH_ACCOUNTS = 'WITH_ACCOUNTS', |
@@ -108,16 +110,16 @@ export class VideoChangeOwnershipModel extends Model<VideoChangeOwnershipModel> | |||
108 | 110 | ||
109 | return Promise.all([ | 111 | return Promise.all([ |
110 | VideoChangeOwnershipModel.scope(ScopeNames.WITH_ACCOUNTS).count(query), | 112 | VideoChangeOwnershipModel.scope(ScopeNames.WITH_ACCOUNTS).count(query), |
111 | VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]).findAll(query) | 113 | VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]).findAll<MVideoChangeOwnershipFull>(query) |
112 | ]).then(([ count, rows ]) => ({ total: count, data: rows })) | 114 | ]).then(([ count, rows ]) => ({ total: count, data: rows })) |
113 | } | 115 | } |
114 | 116 | ||
115 | static load (id: number) { | 117 | static load (id: number): Bluebird<MVideoChangeOwnershipFull> { |
116 | return VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]) | 118 | return VideoChangeOwnershipModel.scope([ ScopeNames.WITH_ACCOUNTS, ScopeNames.WITH_VIDEO ]) |
117 | .findByPk(id) | 119 | .findByPk(id) |
118 | } | 120 | } |
119 | 121 | ||
120 | toFormattedJSON (): VideoChangeOwnership { | 122 | toFormattedJSON (this: MVideoChangeOwnershipFormattable): VideoChangeOwnership { |
121 | return { | 123 | return { |
122 | id: this.id, | 124 | id: this.id, |
123 | status: this.status, | 125 | status: this.status, |
diff --git a/server/models/video/video-channel.ts b/server/models/video/video-channel.ts index 6241a75a3..05545bd9d 100644 --- a/server/models/video/video-channel.ts +++ b/server/models/video/video-channel.ts | |||
@@ -33,6 +33,15 @@ import { ServerModel } from '../server/server' | |||
33 | import { FindOptions, ModelIndexesOptions, Op } from 'sequelize' | 33 | import { FindOptions, ModelIndexesOptions, Op } from 'sequelize' |
34 | import { AvatarModel } from '../avatar/avatar' | 34 | import { AvatarModel } from '../avatar/avatar' |
35 | import { VideoPlaylistModel } from './video-playlist' | 35 | import { VideoPlaylistModel } from './video-playlist' |
36 | import * as Bluebird from 'bluebird' | ||
37 | import { | ||
38 | MChannelAccountDefault, | ||
39 | MChannelActor, | ||
40 | MChannelActorAccountDefaultVideos, | ||
41 | MChannelAP, | ||
42 | MChannelFormattable, | ||
43 | MChannelSummaryFormattable | ||
44 | } from '../../typings/models/video' | ||
36 | 45 | ||
37 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | 46 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation |
38 | const indexes: ModelIndexesOptions[] = [ | 47 | const indexes: ModelIndexesOptions[] = [ |
@@ -47,7 +56,7 @@ const indexes: ModelIndexesOptions[] = [ | |||
47 | ] | 56 | ] |
48 | 57 | ||
49 | export enum ScopeNames { | 58 | export enum ScopeNames { |
50 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', | 59 | FOR_API = 'FOR_API', |
51 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 60 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
52 | WITH_ACTOR = 'WITH_ACTOR', | 61 | WITH_ACTOR = 'WITH_ACTOR', |
53 | WITH_VIDEOS = 'WITH_VIDEOS', | 62 | WITH_VIDEOS = 'WITH_VIDEOS', |
@@ -74,10 +83,10 @@ export type SummaryOptions = { | |||
74 | @Scopes(() => ({ | 83 | @Scopes(() => ({ |
75 | [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => { | 84 | [ScopeNames.SUMMARY]: (options: SummaryOptions = {}) => { |
76 | const base: FindOptions = { | 85 | const base: FindOptions = { |
77 | attributes: [ 'name', 'description', 'id', 'actorId' ], | 86 | attributes: [ 'id', 'name', 'description', 'actorId' ], |
78 | include: [ | 87 | include: [ |
79 | { | 88 | { |
80 | attributes: [ 'preferredUsername', 'url', 'serverId', 'avatarId' ], | 89 | attributes: [ 'id', 'preferredUsername', 'url', 'serverId', 'avatarId' ], |
81 | model: ActorModel.unscoped(), | 90 | model: ActorModel.unscoped(), |
82 | required: true, | 91 | required: true, |
83 | include: [ | 92 | include: [ |
@@ -106,7 +115,7 @@ export type SummaryOptions = { | |||
106 | 115 | ||
107 | return base | 116 | return base |
108 | }, | 117 | }, |
109 | [ScopeNames.AVAILABLE_FOR_LIST]: (options: AvailableForListOptions) => { | 118 | [ScopeNames.FOR_API]: (options: AvailableForListOptions) => { |
110 | // Only list local channels OR channels that are on an instance followed by actorId | 119 | // Only list local channels OR channels that are on an instance followed by actorId |
111 | const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId) | 120 | const inQueryInstanceFollow = buildServerIdsFollowedBy(options.actorId) |
112 | 121 | ||
@@ -268,7 +277,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
268 | } | 277 | } |
269 | 278 | ||
270 | const scopes = { | 279 | const scopes = { |
271 | method: [ ScopeNames.AVAILABLE_FOR_LIST, { actorId } as AvailableForListOptions ] | 280 | method: [ ScopeNames.FOR_API, { actorId } as AvailableForListOptions ] |
272 | } | 281 | } |
273 | return VideoChannelModel | 282 | return VideoChannelModel |
274 | .scope(scopes) | 283 | .scope(scopes) |
@@ -278,7 +287,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
278 | }) | 287 | }) |
279 | } | 288 | } |
280 | 289 | ||
281 | static listLocalsForSitemap (sort: string) { | 290 | static listLocalsForSitemap (sort: string): Bluebird<MChannelActor[]> { |
282 | const query = { | 291 | const query = { |
283 | attributes: [ ], | 292 | attributes: [ ], |
284 | offset: 0, | 293 | offset: 0, |
@@ -331,7 +340,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
331 | } | 340 | } |
332 | 341 | ||
333 | const scopes = { | 342 | const scopes = { |
334 | method: [ ScopeNames.AVAILABLE_FOR_LIST, { actorId: options.actorId } as AvailableForListOptions ] | 343 | method: [ ScopeNames.FOR_API, { actorId: options.actorId } as AvailableForListOptions ] |
335 | } | 344 | } |
336 | return VideoChannelModel | 345 | return VideoChannelModel |
337 | .scope(scopes) | 346 | .scope(scopes) |
@@ -369,13 +378,13 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
369 | }) | 378 | }) |
370 | } | 379 | } |
371 | 380 | ||
372 | static loadByIdAndPopulateAccount (id: number) { | 381 | static loadByIdAndPopulateAccount (id: number): Bluebird<MChannelAccountDefault> { |
373 | return VideoChannelModel.unscoped() | 382 | return VideoChannelModel.unscoped() |
374 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) | 383 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) |
375 | .findByPk(id) | 384 | .findByPk(id) |
376 | } | 385 | } |
377 | 386 | ||
378 | static loadByIdAndAccount (id: number, accountId: number) { | 387 | static loadByIdAndAccount (id: number, accountId: number): Bluebird<MChannelAccountDefault> { |
379 | const query = { | 388 | const query = { |
380 | where: { | 389 | where: { |
381 | id, | 390 | id, |
@@ -388,13 +397,13 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
388 | .findOne(query) | 397 | .findOne(query) |
389 | } | 398 | } |
390 | 399 | ||
391 | static loadAndPopulateAccount (id: number) { | 400 | static loadAndPopulateAccount (id: number): Bluebird<MChannelAccountDefault> { |
392 | return VideoChannelModel.unscoped() | 401 | return VideoChannelModel.unscoped() |
393 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) | 402 | .scope([ ScopeNames.WITH_ACTOR, ScopeNames.WITH_ACCOUNT ]) |
394 | .findByPk(id) | 403 | .findByPk(id) |
395 | } | 404 | } |
396 | 405 | ||
397 | static loadByUrlAndPopulateAccount (url: string) { | 406 | static loadByUrlAndPopulateAccount (url: string): Bluebird<MChannelAccountDefault> { |
398 | const query = { | 407 | const query = { |
399 | include: [ | 408 | include: [ |
400 | { | 409 | { |
@@ -420,7 +429,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
420 | return VideoChannelModel.loadByNameAndHostAndPopulateAccount(name, host) | 429 | return VideoChannelModel.loadByNameAndHostAndPopulateAccount(name, host) |
421 | } | 430 | } |
422 | 431 | ||
423 | static loadLocalByNameAndPopulateAccount (name: string) { | 432 | static loadLocalByNameAndPopulateAccount (name: string): Bluebird<MChannelAccountDefault> { |
424 | const query = { | 433 | const query = { |
425 | include: [ | 434 | include: [ |
426 | { | 435 | { |
@@ -439,7 +448,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
439 | .findOne(query) | 448 | .findOne(query) |
440 | } | 449 | } |
441 | 450 | ||
442 | static loadByNameAndHostAndPopulateAccount (name: string, host: string) { | 451 | static loadByNameAndHostAndPopulateAccount (name: string, host: string): Bluebird<MChannelAccountDefault> { |
443 | const query = { | 452 | const query = { |
444 | include: [ | 453 | include: [ |
445 | { | 454 | { |
@@ -464,7 +473,7 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
464 | .findOne(query) | 473 | .findOne(query) |
465 | } | 474 | } |
466 | 475 | ||
467 | static loadAndPopulateAccountAndVideos (id: number) { | 476 | static loadAndPopulateAccountAndVideos (id: number): Bluebird<MChannelActorAccountDefaultVideos> { |
468 | const options = { | 477 | const options = { |
469 | include: [ | 478 | include: [ |
470 | VideoModel | 479 | VideoModel |
@@ -476,7 +485,20 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
476 | .findByPk(id, options) | 485 | .findByPk(id, options) |
477 | } | 486 | } |
478 | 487 | ||
479 | toFormattedJSON (): VideoChannel { | 488 | toFormattedSummaryJSON (this: MChannelSummaryFormattable): VideoChannelSummary { |
489 | const actor = this.Actor.toFormattedSummaryJSON() | ||
490 | |||
491 | return { | ||
492 | id: this.id, | ||
493 | name: actor.name, | ||
494 | displayName: this.getDisplayName(), | ||
495 | url: actor.url, | ||
496 | host: actor.host, | ||
497 | avatar: actor.avatar | ||
498 | } | ||
499 | } | ||
500 | |||
501 | toFormattedJSON (this: MChannelFormattable): VideoChannel { | ||
480 | const actor = this.Actor.toFormattedJSON() | 502 | const actor = this.Actor.toFormattedJSON() |
481 | const videoChannel = { | 503 | const videoChannel = { |
482 | id: this.id, | 504 | id: this.id, |
@@ -494,21 +516,8 @@ export class VideoChannelModel extends Model<VideoChannelModel> { | |||
494 | return Object.assign(actor, videoChannel) | 516 | return Object.assign(actor, videoChannel) |
495 | } | 517 | } |
496 | 518 | ||
497 | toFormattedSummaryJSON (): VideoChannelSummary { | 519 | toActivityPubObject (this: MChannelAP): ActivityPubActor { |
498 | const actor = this.Actor.toFormattedJSON() | 520 | const obj = this.Actor.toActivityPubObject(this.name) |
499 | |||
500 | return { | ||
501 | id: this.id, | ||
502 | name: actor.name, | ||
503 | displayName: this.getDisplayName(), | ||
504 | url: actor.url, | ||
505 | host: actor.host, | ||
506 | avatar: actor.avatar | ||
507 | } | ||
508 | } | ||
509 | |||
510 | toActivityPubObject (): ActivityPubActor { | ||
511 | const obj = this.Actor.toActivityPubObject(this.name, 'VideoChannel') | ||
512 | 521 | ||
513 | return Object.assign(obj, { | 522 | return Object.assign(obj, { |
514 | summary: this.description, | 523 | summary: this.description, |
diff --git a/server/models/video/video-comment.ts b/server/models/video/video-comment.ts index 58b75510d..2e4220434 100644 --- a/server/models/video/video-comment.ts +++ b/server/models/video/video-comment.ts | |||
@@ -1,36 +1,32 @@ | |||
1 | import { | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, Is, Model, Scopes, Table, UpdatedAt } from 'sequelize-typescript' |
2 | AllowNull, | ||
3 | BeforeDestroy, | ||
4 | BelongsTo, | ||
5 | Column, | ||
6 | CreatedAt, | ||
7 | DataType, | ||
8 | ForeignKey, | ||
9 | Is, | ||
10 | Model, | ||
11 | Scopes, | ||
12 | Table, | ||
13 | UpdatedAt | ||
14 | } from 'sequelize-typescript' | ||
15 | import { ActivityTagObject } from '../../../shared/models/activitypub/objects/common-objects' | 2 | import { ActivityTagObject } from '../../../shared/models/activitypub/objects/common-objects' |
16 | import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' | 3 | import { VideoCommentObject } from '../../../shared/models/activitypub/objects/video-comment-object' |
17 | import { VideoComment } from '../../../shared/models/videos/video-comment.model' | 4 | import { VideoComment } from '../../../shared/models/videos/video-comment.model' |
18 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 5 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
19 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' | 6 | import { CONSTRAINTS_FIELDS, WEBSERVER } from '../../initializers/constants' |
20 | import { sendDeleteVideoComment } from '../../lib/activitypub/send' | ||
21 | import { AccountModel } from '../account/account' | 7 | import { AccountModel } from '../account/account' |
22 | import { ActorModel } from '../activitypub/actor' | 8 | import { ActorModel } from '../activitypub/actor' |
23 | import { AvatarModel } from '../avatar/avatar' | ||
24 | import { ServerModel } from '../server/server' | ||
25 | import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils' | 9 | import { buildBlockedAccountSQL, buildLocalAccountIdsIn, getSort, throwIfNotValid } from '../utils' |
26 | import { VideoModel } from './video' | 10 | import { VideoModel } from './video' |
27 | import { VideoChannelModel } from './video-channel' | 11 | import { VideoChannelModel } from './video-channel' |
28 | import { getServerActor } from '../../helpers/utils' | 12 | import { getServerActor } from '../../helpers/utils' |
29 | import { UserModel } from '../account/user' | ||
30 | import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' | 13 | import { actorNameAlphabet } from '../../helpers/custom-validators/activitypub/actor' |
31 | import { regexpCapture } from '../../helpers/regexp' | 14 | import { regexpCapture } from '../../helpers/regexp' |
32 | import { uniq } from 'lodash' | 15 | import { uniq } from 'lodash' |
33 | import { FindOptions, literal, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' | 16 | import { FindOptions, Op, Order, ScopeOptions, Sequelize, Transaction } from 'sequelize' |
17 | import * as Bluebird from 'bluebird' | ||
18 | import { | ||
19 | MComment, | ||
20 | MCommentAP, | ||
21 | MCommentFormattable, | ||
22 | MCommentId, | ||
23 | MCommentOwner, | ||
24 | MCommentOwnerReplyVideoLight, | ||
25 | MCommentOwnerVideo, | ||
26 | MCommentOwnerVideoFeed, | ||
27 | MCommentOwnerVideoReply | ||
28 | } from '../../typings/models/video' | ||
29 | import { MUserAccountId } from '@server/typings/models' | ||
34 | 30 | ||
35 | enum ScopeNames { | 31 | enum ScopeNames { |
36 | WITH_ACCOUNT = 'WITH_ACCOUNT', | 32 | WITH_ACCOUNT = 'WITH_ACCOUNT', |
@@ -68,22 +64,7 @@ enum ScopeNames { | |||
68 | [ScopeNames.WITH_ACCOUNT]: { | 64 | [ScopeNames.WITH_ACCOUNT]: { |
69 | include: [ | 65 | include: [ |
70 | { | 66 | { |
71 | model: AccountModel, | 67 | model: AccountModel |
72 | include: [ | ||
73 | { | ||
74 | model: ActorModel, | ||
75 | include: [ | ||
76 | { | ||
77 | model: ServerModel, | ||
78 | required: false | ||
79 | }, | ||
80 | { | ||
81 | model: AvatarModel, | ||
82 | required: false | ||
83 | } | ||
84 | ] | ||
85 | } | ||
86 | ] | ||
87 | } | 68 | } |
88 | ] | 69 | ] |
89 | }, | 70 | }, |
@@ -102,22 +83,12 @@ enum ScopeNames { | |||
102 | required: true, | 83 | required: true, |
103 | include: [ | 84 | include: [ |
104 | { | 85 | { |
105 | model: VideoChannelModel.unscoped(), | 86 | model: VideoChannelModel, |
106 | required: true, | 87 | required: true, |
107 | include: [ | 88 | include: [ |
108 | { | 89 | { |
109 | model: ActorModel, | ||
110 | required: true | ||
111 | }, | ||
112 | { | ||
113 | model: AccountModel, | 90 | model: AccountModel, |
114 | required: true, | 91 | required: true |
115 | include: [ | ||
116 | { | ||
117 | model: ActorModel, | ||
118 | required: true | ||
119 | } | ||
120 | ] | ||
121 | } | 92 | } |
122 | ] | 93 | ] |
123 | } | 94 | } |
@@ -212,7 +183,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
212 | }) | 183 | }) |
213 | Account: AccountModel | 184 | Account: AccountModel |
214 | 185 | ||
215 | static loadById (id: number, t?: Transaction) { | 186 | static loadById (id: number, t?: Transaction): Bluebird<MComment> { |
216 | const query: FindOptions = { | 187 | const query: FindOptions = { |
217 | where: { | 188 | where: { |
218 | id | 189 | id |
@@ -224,7 +195,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
224 | return VideoCommentModel.findOne(query) | 195 | return VideoCommentModel.findOne(query) |
225 | } | 196 | } |
226 | 197 | ||
227 | static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction) { | 198 | static loadByIdAndPopulateVideoAndAccountAndReply (id: number, t?: Transaction): Bluebird<MCommentOwnerVideoReply> { |
228 | const query: FindOptions = { | 199 | const query: FindOptions = { |
229 | where: { | 200 | where: { |
230 | id | 201 | id |
@@ -238,7 +209,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
238 | .findOne(query) | 209 | .findOne(query) |
239 | } | 210 | } |
240 | 211 | ||
241 | static loadByUrlAndPopulateAccountAndVideo (url: string, t?: Transaction) { | 212 | static loadByUrlAndPopulateAccountAndVideo (url: string, t?: Transaction): Bluebird<MCommentOwnerVideo> { |
242 | const query: FindOptions = { | 213 | const query: FindOptions = { |
243 | where: { | 214 | where: { |
244 | url | 215 | url |
@@ -250,7 +221,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
250 | return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEO ]).findOne(query) | 221 | return VideoCommentModel.scope([ ScopeNames.WITH_ACCOUNT, ScopeNames.WITH_VIDEO ]).findOne(query) |
251 | } | 222 | } |
252 | 223 | ||
253 | static loadByUrlAndPopulateReplyAndVideoUrlAndAccount (url: string, t?: Transaction) { | 224 | static loadByUrlAndPopulateReplyAndVideoUrlAndAccount (url: string, t?: Transaction): Bluebird<MCommentOwnerReplyVideoLight> { |
254 | const query: FindOptions = { | 225 | const query: FindOptions = { |
255 | where: { | 226 | where: { |
256 | url | 227 | url |
@@ -273,7 +244,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
273 | start: number, | 244 | start: number, |
274 | count: number, | 245 | count: number, |
275 | sort: string, | 246 | sort: string, |
276 | user?: UserModel | 247 | user?: MUserAccountId |
277 | }) { | 248 | }) { |
278 | const { videoId, start, count, sort, user } = parameters | 249 | const { videoId, start, count, sort, user } = parameters |
279 | 250 | ||
@@ -314,7 +285,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
314 | static async listThreadCommentsForApi (parameters: { | 285 | static async listThreadCommentsForApi (parameters: { |
315 | videoId: number, | 286 | videoId: number, |
316 | threadId: number, | 287 | threadId: number, |
317 | user?: UserModel | 288 | user?: MUserAccountId |
318 | }) { | 289 | }) { |
319 | const { videoId, threadId, user } = parameters | 290 | const { videoId, threadId, user } = parameters |
320 | 291 | ||
@@ -353,7 +324,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
353 | }) | 324 | }) |
354 | } | 325 | } |
355 | 326 | ||
356 | static listThreadParentComments (comment: VideoCommentModel, t: Transaction, order: 'ASC' | 'DESC' = 'ASC') { | 327 | static listThreadParentComments (comment: MCommentId, t: Transaction, order: 'ASC' | 'DESC' = 'ASC'): Bluebird<MCommentOwner[]> { |
357 | const query = { | 328 | const query = { |
358 | order: [ [ 'createdAt', order ] ] as Order, | 329 | order: [ [ 'createdAt', order ] ] as Order, |
359 | where: { | 330 | where: { |
@@ -389,10 +360,10 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
389 | transaction: t | 360 | transaction: t |
390 | } | 361 | } |
391 | 362 | ||
392 | return VideoCommentModel.findAndCountAll(query) | 363 | return VideoCommentModel.findAndCountAll<MComment>(query) |
393 | } | 364 | } |
394 | 365 | ||
395 | static listForFeed (start: number, count: number, videoId?: number) { | 366 | static listForFeed (start: number, count: number, videoId?: number): Bluebird<MCommentOwnerVideoFeed[]> { |
396 | const query = { | 367 | const query = { |
397 | order: [ [ 'createdAt', 'DESC' ] ] as Order, | 368 | order: [ [ 'createdAt', 'DESC' ] ] as Order, |
398 | offset: start, | 369 | offset: start, |
@@ -506,7 +477,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
506 | return uniq(result) | 477 | return uniq(result) |
507 | } | 478 | } |
508 | 479 | ||
509 | toFormattedJSON () { | 480 | toFormattedJSON (this: MCommentFormattable) { |
510 | return { | 481 | return { |
511 | id: this.id, | 482 | id: this.id, |
512 | url: this.url, | 483 | url: this.url, |
@@ -521,7 +492,7 @@ export class VideoCommentModel extends Model<VideoCommentModel> { | |||
521 | } as VideoComment | 492 | } as VideoComment |
522 | } | 493 | } |
523 | 494 | ||
524 | toActivityPubObject (threadParentComments: VideoCommentModel[]): VideoCommentObject { | 495 | toActivityPubObject (this: MCommentAP, threadParentComments: MCommentOwner[]): VideoCommentObject { |
525 | let inReplyTo: string | 496 | let inReplyTo: string |
526 | // New thread, so in AS we reply to the video | 497 | // New thread, so in AS we reply to the video |
527 | if (this.inReplyToCommentId === null) { | 498 | if (this.inReplyToCommentId === null) { |
diff --git a/server/models/video/video-file.ts b/server/models/video/video-file.ts index 05c490759..6304f741c 100644 --- a/server/models/video/video-file.ts +++ b/server/models/video/video-file.ts | |||
@@ -25,6 +25,7 @@ import { VideoRedundancyModel } from '../redundancy/video-redundancy' | |||
25 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | 25 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' |
26 | import { FindOptions, QueryTypes, Transaction } from 'sequelize' | 26 | import { FindOptions, QueryTypes, Transaction } from 'sequelize' |
27 | import { MIMETYPES } from '../../initializers/constants' | 27 | import { MIMETYPES } from '../../initializers/constants' |
28 | import { MVideoFile } from '@server/typings/models' | ||
28 | 29 | ||
29 | @Table({ | 30 | @Table({ |
30 | tableName: 'videoFile', | 31 | tableName: 'videoFile', |
@@ -166,7 +167,7 @@ export class VideoFileModel extends Model<VideoFileModel> { | |||
166 | return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname] | 167 | return !!MIMETYPES.AUDIO.EXT_MIMETYPE[this.extname] |
167 | } | 168 | } |
168 | 169 | ||
169 | hasSameUniqueKeysThan (other: VideoFileModel) { | 170 | hasSameUniqueKeysThan (other: MVideoFile) { |
170 | return this.fps === other.fps && | 171 | return this.fps === other.fps && |
171 | this.resolution === other.resolution && | 172 | this.resolution === other.resolution && |
172 | this.videoId === other.videoId | 173 | this.videoId === other.videoId |
diff --git a/server/models/video/video-format-utils.ts b/server/models/video/video-format-utils.ts index 284539def..2987aa780 100644 --- a/server/models/video/video-format-utils.ts +++ b/server/models/video/video-format-utils.ts | |||
@@ -1,6 +1,5 @@ | |||
1 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' | 1 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' |
2 | import { VideoModel } from './video' | 2 | import { VideoModel } from './video' |
3 | import { VideoFileModel } from './video-file' | ||
4 | import { | 3 | import { |
5 | ActivityPlaylistInfohashesObject, | 4 | ActivityPlaylistInfohashesObject, |
6 | ActivityPlaylistSegmentHashesObject, | 5 | ActivityPlaylistSegmentHashesObject, |
@@ -17,7 +16,9 @@ import { | |||
17 | } from '../../lib/activitypub' | 16 | } from '../../lib/activitypub' |
18 | import { isArray } from '../../helpers/custom-validators/misc' | 17 | import { isArray } from '../../helpers/custom-validators/misc' |
19 | import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model' | 18 | import { VideoStreamingPlaylist } from '../../../shared/models/videos/video-streaming-playlist.model' |
20 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | 19 | import { MStreamingPlaylistRedundanciesOpt, MVideo, MVideoAP, MVideoFormattable, MVideoFormattableDetails } from '../../typings/models' |
20 | import { MStreamingPlaylistRedundancies } from '../../typings/models/video/video-streaming-playlist' | ||
21 | import { MVideoFileRedundanciesOpt } from '../../typings/models/video/video-file' | ||
21 | 22 | ||
22 | export type VideoFormattingJSONOptions = { | 23 | export type VideoFormattingJSONOptions = { |
23 | completeDescription?: boolean | 24 | completeDescription?: boolean |
@@ -28,7 +29,7 @@ export type VideoFormattingJSONOptions = { | |||
28 | blacklistInfo?: boolean | 29 | blacklistInfo?: boolean |
29 | } | 30 | } |
30 | } | 31 | } |
31 | function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormattingJSONOptions): Video { | 32 | function videoModelToFormattedJSON (video: MVideoFormattable, options?: VideoFormattingJSONOptions): Video { |
32 | const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined | 33 | const userHistory = isArray(video.UserVideoHistories) ? video.UserVideoHistories[0] : undefined |
33 | 34 | ||
34 | const videoObject: Video = { | 35 | const videoObject: Video = { |
@@ -102,7 +103,7 @@ function videoModelToFormattedJSON (video: VideoModel, options?: VideoFormatting | |||
102 | return videoObject | 103 | return videoObject |
103 | } | 104 | } |
104 | 105 | ||
105 | function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails { | 106 | function videoModelToFormattedDetailsJSON (video: MVideoFormattableDetails): VideoDetails { |
106 | const formattedJson = video.toFormattedJSON({ | 107 | const formattedJson = video.toFormattedJSON({ |
107 | additionalAttributes: { | 108 | additionalAttributes: { |
108 | scheduledUpdate: true, | 109 | scheduledUpdate: true, |
@@ -114,7 +115,7 @@ function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails { | |||
114 | 115 | ||
115 | const tags = video.Tags ? video.Tags.map(t => t.name) : [] | 116 | const tags = video.Tags ? video.Tags.map(t => t.name) : [] |
116 | 117 | ||
117 | const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video, video.VideoStreamingPlaylists) | 118 | const streamingPlaylists = streamingPlaylistsModelToFormattedJSON(video.VideoStreamingPlaylists) |
118 | 119 | ||
119 | const detailsJson = { | 120 | const detailsJson = { |
120 | support: video.support, | 121 | support: video.support, |
@@ -142,7 +143,7 @@ function videoModelToFormattedDetailsJSON (video: VideoModel): VideoDetails { | |||
142 | return Object.assign(formattedJson, detailsJson) | 143 | return Object.assign(formattedJson, detailsJson) |
143 | } | 144 | } |
144 | 145 | ||
145 | function streamingPlaylistsModelToFormattedJSON (video: VideoModel, playlists: VideoStreamingPlaylistModel[]): VideoStreamingPlaylist[] { | 146 | function streamingPlaylistsModelToFormattedJSON (playlists: MStreamingPlaylistRedundanciesOpt[]): VideoStreamingPlaylist[] { |
146 | if (isArray(playlists) === false) return [] | 147 | if (isArray(playlists) === false) return [] |
147 | 148 | ||
148 | return playlists | 149 | return playlists |
@@ -161,7 +162,7 @@ function streamingPlaylistsModelToFormattedJSON (video: VideoModel, playlists: V | |||
161 | }) | 162 | }) |
162 | } | 163 | } |
163 | 164 | ||
164 | function videoFilesModelToFormattedJSON (video: VideoModel, videoFiles: VideoFileModel[]): VideoFile[] { | 165 | function videoFilesModelToFormattedJSON (video: MVideo, videoFiles: MVideoFileRedundanciesOpt[]): VideoFile[] { |
165 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() | 166 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() |
166 | 167 | ||
167 | return videoFiles | 168 | return videoFiles |
@@ -189,7 +190,7 @@ function videoFilesModelToFormattedJSON (video: VideoModel, videoFiles: VideoFil | |||
189 | }) | 190 | }) |
190 | } | 191 | } |
191 | 192 | ||
192 | function videoModelToActivityPubObject (video: VideoModel): VideoTorrentObject { | 193 | function videoModelToActivityPubObject (video: MVideoAP): VideoTorrentObject { |
193 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() | 194 | const { baseUrlHttp, baseUrlWs } = video.getBaseUrls() |
194 | if (!video.Tags) video.Tags = [] | 195 | if (!video.Tags) video.Tags = [] |
195 | 196 | ||
diff --git a/server/models/video/video-import.ts b/server/models/video/video-import.ts index 480a671c8..af5314ce9 100644 --- a/server/models/video/video-import.ts +++ b/server/models/video/video-import.ts | |||
@@ -20,6 +20,8 @@ import { isVideoImportStateValid, isVideoImportTargetUrlValid } from '../../help | |||
20 | import { VideoImport, VideoImportState } from '../../../shared' | 20 | import { VideoImport, VideoImportState } from '../../../shared' |
21 | import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' | 21 | import { isVideoMagnetUriValid } from '../../helpers/custom-validators/videos' |
22 | import { UserModel } from '../account/user' | 22 | import { UserModel } from '../account/user' |
23 | import * as Bluebird from 'bluebird' | ||
24 | import { MVideoImportDefault, MVideoImportFormattable } from '@server/typings/models/video/video-import' | ||
23 | 25 | ||
24 | @DefaultScope(() => ({ | 26 | @DefaultScope(() => ({ |
25 | include: [ | 27 | include: [ |
@@ -28,7 +30,11 @@ import { UserModel } from '../account/user' | |||
28 | required: true | 30 | required: true |
29 | }, | 31 | }, |
30 | { | 32 | { |
31 | model: VideoModel.scope([ VideoModelScopeNames.WITH_ACCOUNT_DETAILS, VideoModelScopeNames.WITH_TAGS]), | 33 | model: VideoModel.scope([ |
34 | VideoModelScopeNames.WITH_ACCOUNT_DETAILS, | ||
35 | VideoModelScopeNames.WITH_TAGS, | ||
36 | VideoModelScopeNames.WITH_THUMBNAILS | ||
37 | ]), | ||
32 | required: false | 38 | required: false |
33 | } | 39 | } |
34 | ] | 40 | ] |
@@ -114,7 +120,7 @@ export class VideoImportModel extends Model<VideoImportModel> { | |||
114 | return undefined | 120 | return undefined |
115 | } | 121 | } |
116 | 122 | ||
117 | static loadAndPopulateVideo (id: number) { | 123 | static loadAndPopulateVideo (id: number): Bluebird<MVideoImportDefault> { |
118 | return VideoImportModel.findByPk(id) | 124 | return VideoImportModel.findByPk(id) |
119 | } | 125 | } |
120 | 126 | ||
@@ -135,7 +141,7 @@ export class VideoImportModel extends Model<VideoImportModel> { | |||
135 | } | 141 | } |
136 | } | 142 | } |
137 | 143 | ||
138 | return VideoImportModel.findAndCountAll(query) | 144 | return VideoImportModel.findAndCountAll<MVideoImportDefault>(query) |
139 | .then(({ rows, count }) => { | 145 | .then(({ rows, count }) => { |
140 | return { | 146 | return { |
141 | data: rows, | 147 | data: rows, |
@@ -148,7 +154,7 @@ export class VideoImportModel extends Model<VideoImportModel> { | |||
148 | return this.targetUrl || this.magnetUri || this.torrentName | 154 | return this.targetUrl || this.magnetUri || this.torrentName |
149 | } | 155 | } |
150 | 156 | ||
151 | toFormattedJSON (): VideoImport { | 157 | toFormattedJSON (this: MVideoImportFormattable): VideoImport { |
152 | const videoFormatOptions = { | 158 | const videoFormatOptions = { |
153 | completeDescription: true, | 159 | completeDescription: true, |
154 | additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true } | 160 | additionalAttributes: { state: true, waitTranscoding: true, scheduledUpdate: true } |
diff --git a/server/models/video/video-playlist-element.ts b/server/models/video/video-playlist-element.ts index dd7653533..a28021313 100644 --- a/server/models/video/video-playlist-element.ts +++ b/server/models/video/video-playlist-element.ts | |||
@@ -21,10 +21,18 @@ import { CONSTRAINTS_FIELDS } from '../../initializers/constants' | |||
21 | import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' | 21 | import { PlaylistElementObject } from '../../../shared/models/activitypub/objects/playlist-element-object' |
22 | import * as validator from 'validator' | 22 | import * as validator from 'validator' |
23 | import { AggregateOptions, Op, ScopeOptions, Sequelize, Transaction } from 'sequelize' | 23 | import { AggregateOptions, Op, ScopeOptions, Sequelize, Transaction } from 'sequelize' |
24 | import { UserModel } from '../account/user' | ||
25 | import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../shared/models/videos/playlist/video-playlist-element.model' | 24 | import { VideoPlaylistElement, VideoPlaylistElementType } from '../../../shared/models/videos/playlist/video-playlist-element.model' |
26 | import { AccountModel } from '../account/account' | 25 | import { AccountModel } from '../account/account' |
27 | import { VideoPrivacy } from '../../../shared/models/videos' | 26 | import { VideoPrivacy } from '../../../shared/models/videos' |
27 | import * as Bluebird from 'bluebird' | ||
28 | import { | ||
29 | MVideoPlaylistElement, | ||
30 | MVideoPlaylistElementAP, | ||
31 | MVideoPlaylistElementFormattable, | ||
32 | MVideoPlaylistElementVideoUrlPlaylistPrivacy, | ||
33 | MVideoPlaylistVideoThumbnail | ||
34 | } from '@server/typings/models/video/video-playlist-element' | ||
35 | import { MUserAccountId } from '@server/typings/models' | ||
28 | 36 | ||
29 | @Table({ | 37 | @Table({ |
30 | tableName: 'videoPlaylistElement', | 38 | tableName: 'videoPlaylistElement', |
@@ -116,7 +124,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
116 | count: number, | 124 | count: number, |
117 | videoPlaylistId: number, | 125 | videoPlaylistId: number, |
118 | serverAccount: AccountModel, | 126 | serverAccount: AccountModel, |
119 | user?: UserModel | 127 | user?: MUserAccountId |
120 | }) { | 128 | }) { |
121 | const accountIds = [ options.serverAccount.id ] | 129 | const accountIds = [ options.serverAccount.id ] |
122 | const videoScope: (ScopeOptions | string)[] = [ | 130 | const videoScope: (ScopeOptions | string)[] = [ |
@@ -162,7 +170,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
162 | ]).then(([ total, data ]) => ({ total, data })) | 170 | ]).then(([ total, data ]) => ({ total, data })) |
163 | } | 171 | } |
164 | 172 | ||
165 | static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number) { | 173 | static loadByPlaylistAndVideo (videoPlaylistId: number, videoId: number): Bluebird<MVideoPlaylistElement> { |
166 | const query = { | 174 | const query = { |
167 | where: { | 175 | where: { |
168 | videoPlaylistId, | 176 | videoPlaylistId, |
@@ -173,11 +181,14 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
173 | return VideoPlaylistElementModel.findOne(query) | 181 | return VideoPlaylistElementModel.findOne(query) |
174 | } | 182 | } |
175 | 183 | ||
176 | static loadById (playlistElementId: number) { | 184 | static loadById (playlistElementId: number): Bluebird<MVideoPlaylistElement> { |
177 | return VideoPlaylistElementModel.findByPk(playlistElementId) | 185 | return VideoPlaylistElementModel.findByPk(playlistElementId) |
178 | } | 186 | } |
179 | 187 | ||
180 | static loadByPlaylistAndVideoForAP (playlistId: number | string, videoId: number | string) { | 188 | static loadByPlaylistAndVideoForAP ( |
189 | playlistId: number | string, | ||
190 | videoId: number | string | ||
191 | ): Bluebird<MVideoPlaylistElementVideoUrlPlaylistPrivacy> { | ||
181 | const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } | 192 | const playlistWhere = validator.isUUID('' + playlistId) ? { uuid: playlistId } : { id: playlistId } |
182 | const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId } | 193 | const videoWhere = validator.isUUID('' + videoId) ? { uuid: videoId } : { id: videoId } |
183 | 194 | ||
@@ -218,7 +229,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
218 | }) | 229 | }) |
219 | } | 230 | } |
220 | 231 | ||
221 | static loadFirstElementWithVideoThumbnail (videoPlaylistId: number) { | 232 | static loadFirstElementWithVideoThumbnail (videoPlaylistId: number): Bluebird<MVideoPlaylistVideoThumbnail> { |
222 | const query = { | 233 | const query = { |
223 | order: getSort('position'), | 234 | order: getSort('position'), |
224 | where: { | 235 | where: { |
@@ -290,7 +301,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
290 | return VideoPlaylistElementModel.increment({ position: by }, query) | 301 | return VideoPlaylistElementModel.increment({ position: by }, query) |
291 | } | 302 | } |
292 | 303 | ||
293 | getType (displayNSFW?: boolean, accountId?: number) { | 304 | getType (this: MVideoPlaylistElementFormattable, displayNSFW?: boolean, accountId?: number) { |
294 | const video = this.Video | 305 | const video = this.Video |
295 | 306 | ||
296 | if (!video) return VideoPlaylistElementType.DELETED | 307 | if (!video) return VideoPlaylistElementType.DELETED |
@@ -306,14 +317,17 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
306 | return VideoPlaylistElementType.REGULAR | 317 | return VideoPlaylistElementType.REGULAR |
307 | } | 318 | } |
308 | 319 | ||
309 | getVideoElement (displayNSFW?: boolean, accountId?: number) { | 320 | getVideoElement (this: MVideoPlaylistElementFormattable, displayNSFW?: boolean, accountId?: number) { |
310 | if (!this.Video) return null | 321 | if (!this.Video) return null |
311 | if (this.getType(displayNSFW, accountId) !== VideoPlaylistElementType.REGULAR) return null | 322 | if (this.getType(displayNSFW, accountId) !== VideoPlaylistElementType.REGULAR) return null |
312 | 323 | ||
313 | return this.Video.toFormattedJSON() | 324 | return this.Video.toFormattedJSON() |
314 | } | 325 | } |
315 | 326 | ||
316 | toFormattedJSON (options: { displayNSFW?: boolean, accountId?: number } = {}): VideoPlaylistElement { | 327 | toFormattedJSON ( |
328 | this: MVideoPlaylistElementFormattable, | ||
329 | options: { displayNSFW?: boolean, accountId?: number } = {} | ||
330 | ): VideoPlaylistElement { | ||
317 | return { | 331 | return { |
318 | id: this.id, | 332 | id: this.id, |
319 | position: this.position, | 333 | position: this.position, |
@@ -326,7 +340,7 @@ export class VideoPlaylistElementModel extends Model<VideoPlaylistElementModel> | |||
326 | } | 340 | } |
327 | } | 341 | } |
328 | 342 | ||
329 | toActivityPubObject (): PlaylistElementObject { | 343 | toActivityPubObject (this: MVideoPlaylistElementAP): PlaylistElementObject { |
330 | const base: PlaylistElementObject = { | 344 | const base: PlaylistElementObject = { |
331 | id: this.url, | 345 | id: this.url, |
332 | type: 'PlaylistElement', | 346 | type: 'PlaylistElement', |
diff --git a/server/models/video/video-playlist.ts b/server/models/video/video-playlist.ts index c8e97c491..278d80ac0 100644 --- a/server/models/video/video-playlist.ts +++ b/server/models/video/video-playlist.ts | |||
@@ -43,6 +43,15 @@ import { VideoPlaylistType } from '../../../shared/models/videos/playlist/video- | |||
43 | import { ThumbnailModel } from './thumbnail' | 43 | import { ThumbnailModel } from './thumbnail' |
44 | import { ActivityIconObject } from '../../../shared/models/activitypub/objects' | 44 | import { ActivityIconObject } from '../../../shared/models/activitypub/objects' |
45 | import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize' | 45 | import { FindOptions, literal, Op, ScopeOptions, Transaction, WhereOptions } from 'sequelize' |
46 | import * as Bluebird from 'bluebird' | ||
47 | import { | ||
48 | MVideoPlaylistAccountThumbnail, MVideoPlaylistAP, | ||
49 | MVideoPlaylistFormattable, | ||
50 | MVideoPlaylistFull, | ||
51 | MVideoPlaylistFullSummary, | ||
52 | MVideoPlaylistIdWithElements | ||
53 | } from '../../typings/models/video/video-playlist' | ||
54 | import { MThumbnail } from '../../typings/models/video/thumbnail' | ||
46 | 55 | ||
47 | enum ScopeNames { | 56 | enum ScopeNames { |
48 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', | 57 | AVAILABLE_FOR_LIST = 'AVAILABLE_FOR_LIST', |
@@ -332,7 +341,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
332 | }) | 341 | }) |
333 | } | 342 | } |
334 | 343 | ||
335 | static listPlaylistIdsOf (accountId: number, videoIds: number[]) { | 344 | static listPlaylistIdsOf (accountId: number, videoIds: number[]): Bluebird<MVideoPlaylistIdWithElements[]> { |
336 | const query = { | 345 | const query = { |
337 | attributes: [ 'id' ], | 346 | attributes: [ 'id' ], |
338 | where: { | 347 | where: { |
@@ -368,7 +377,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
368 | .then(e => !!e) | 377 | .then(e => !!e) |
369 | } | 378 | } |
370 | 379 | ||
371 | static loadWithAccountAndChannelSummary (id: number | string, transaction: Transaction) { | 380 | static loadWithAccountAndChannelSummary (id: number | string, transaction: Transaction): Bluebird<MVideoPlaylistFullSummary> { |
372 | const where = buildWhereIdOrUUID(id) | 381 | const where = buildWhereIdOrUUID(id) |
373 | 382 | ||
374 | const query = { | 383 | const query = { |
@@ -381,7 +390,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
381 | .findOne(query) | 390 | .findOne(query) |
382 | } | 391 | } |
383 | 392 | ||
384 | static loadWithAccountAndChannel (id: number | string, transaction: Transaction) { | 393 | static loadWithAccountAndChannel (id: number | string, transaction: Transaction): Bluebird<MVideoPlaylistFull> { |
385 | const where = buildWhereIdOrUUID(id) | 394 | const where = buildWhereIdOrUUID(id) |
386 | 395 | ||
387 | const query = { | 396 | const query = { |
@@ -394,7 +403,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
394 | .findOne(query) | 403 | .findOne(query) |
395 | } | 404 | } |
396 | 405 | ||
397 | static loadByUrlAndPopulateAccount (url: string) { | 406 | static loadByUrlAndPopulateAccount (url: string): Bluebird<MVideoPlaylistAccountThumbnail> { |
398 | const query = { | 407 | const query = { |
399 | where: { | 408 | where: { |
400 | url | 409 | url |
@@ -423,7 +432,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
423 | return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query) | 432 | return VideoPlaylistModel.update({ privacy: VideoPlaylistPrivacy.PRIVATE, videoChannelId: null }, query) |
424 | } | 433 | } |
425 | 434 | ||
426 | async setAndSaveThumbnail (thumbnail: ThumbnailModel, t: Transaction) { | 435 | async setAndSaveThumbnail (thumbnail: MThumbnail, t: Transaction) { |
427 | thumbnail.videoPlaylistId = this.id | 436 | thumbnail.videoPlaylistId = this.id |
428 | 437 | ||
429 | this.Thumbnail = await thumbnail.save({ transaction: t }) | 438 | this.Thumbnail = await thumbnail.save({ transaction: t }) |
@@ -471,7 +480,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
471 | return isOutdated(this, ACTIVITY_PUB.VIDEO_PLAYLIST_REFRESH_INTERVAL) | 480 | return isOutdated(this, ACTIVITY_PUB.VIDEO_PLAYLIST_REFRESH_INTERVAL) |
472 | } | 481 | } |
473 | 482 | ||
474 | toFormattedJSON (): VideoPlaylist { | 483 | toFormattedJSON (this: MVideoPlaylistFormattable): VideoPlaylist { |
475 | return { | 484 | return { |
476 | id: this.id, | 485 | id: this.id, |
477 | uuid: this.uuid, | 486 | uuid: this.uuid, |
@@ -501,7 +510,7 @@ export class VideoPlaylistModel extends Model<VideoPlaylistModel> { | |||
501 | } | 510 | } |
502 | } | 511 | } |
503 | 512 | ||
504 | toActivityPubObject (page: number, t: Transaction): Promise<PlaylistObject> { | 513 | toActivityPubObject (this: MVideoPlaylistAP, page: number, t: Transaction): Promise<PlaylistObject> { |
505 | const handler = (start: number, count: number) => { | 514 | const handler = (start: number, count: number) => { |
506 | return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count, t) | 515 | return VideoPlaylistElementModel.listUrlsOfForAP(this.id, start, count, t) |
507 | } | 516 | } |
diff --git a/server/models/video/video-share.ts b/server/models/video/video-share.ts index d8ed64557..9019b401a 100644 --- a/server/models/video/video-share.ts +++ b/server/models/video/video-share.ts | |||
@@ -8,6 +8,8 @@ import { buildLocalActorIdsIn, throwIfNotValid } from '../utils' | |||
8 | import { VideoModel } from './video' | 8 | import { VideoModel } from './video' |
9 | import { VideoChannelModel } from './video-channel' | 9 | import { VideoChannelModel } from './video-channel' |
10 | import { Op, Transaction } from 'sequelize' | 10 | import { Op, Transaction } from 'sequelize' |
11 | import { MVideoShareActor, MVideoShareFull } from '../../typings/models/video' | ||
12 | import { MActorDefault } from '../../typings/models' | ||
11 | 13 | ||
12 | enum ScopeNames { | 14 | enum ScopeNames { |
13 | FULL = 'FULL', | 15 | FULL = 'FULL', |
@@ -88,7 +90,7 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
88 | }) | 90 | }) |
89 | Video: VideoModel | 91 | Video: VideoModel |
90 | 92 | ||
91 | static load (actorId: number, videoId: number, t?: Transaction) { | 93 | static load (actorId: number, videoId: number, t?: Transaction): Bluebird<MVideoShareActor> { |
92 | return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({ | 94 | return VideoShareModel.scope(ScopeNames.WITH_ACTOR).findOne({ |
93 | where: { | 95 | where: { |
94 | actorId, | 96 | actorId, |
@@ -98,7 +100,7 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
98 | }) | 100 | }) |
99 | } | 101 | } |
100 | 102 | ||
101 | static loadByUrl (url: string, t: Transaction) { | 103 | static loadByUrl (url: string, t: Transaction): Bluebird<MVideoShareFull> { |
102 | return VideoShareModel.scope(ScopeNames.FULL).findOne({ | 104 | return VideoShareModel.scope(ScopeNames.FULL).findOne({ |
103 | where: { | 105 | where: { |
104 | url | 106 | url |
@@ -107,7 +109,7 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
107 | }) | 109 | }) |
108 | } | 110 | } |
109 | 111 | ||
110 | static loadActorsByShare (videoId: number, t: Transaction) { | 112 | static loadActorsByShare (videoId: number, t: Transaction): Bluebird<MActorDefault[]> { |
111 | const query = { | 113 | const query = { |
112 | where: { | 114 | where: { |
113 | videoId | 115 | videoId |
@@ -122,10 +124,10 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
122 | } | 124 | } |
123 | 125 | ||
124 | return VideoShareModel.scope(ScopeNames.FULL).findAll(query) | 126 | return VideoShareModel.scope(ScopeNames.FULL).findAll(query) |
125 | .then(res => res.map(r => r.Actor)) | 127 | .then((res: MVideoShareFull[]) => res.map(r => r.Actor)) |
126 | } | 128 | } |
127 | 129 | ||
128 | static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Bluebird<ActorModel[]> { | 130 | static loadActorsWhoSharedVideosOf (actorOwnerId: number, t: Transaction): Bluebird<MActorDefault[]> { |
129 | const query = { | 131 | const query = { |
130 | attributes: [], | 132 | attributes: [], |
131 | include: [ | 133 | include: [ |
@@ -163,7 +165,7 @@ export class VideoShareModel extends Model<VideoShareModel> { | |||
163 | .then(res => res.map(r => r.Actor)) | 165 | .then(res => res.map(r => r.Actor)) |
164 | } | 166 | } |
165 | 167 | ||
166 | static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Bluebird<ActorModel[]> { | 168 | static loadActorsByVideoChannel (videoChannelId: number, t: Transaction): Bluebird<MActorDefault[]> { |
167 | const query = { | 169 | const query = { |
168 | attributes: [], | 170 | attributes: [], |
169 | include: [ | 171 | include: [ |
diff --git a/server/models/video/video-streaming-playlist.ts b/server/models/video/video-streaming-playlist.ts index 31dc82c54..0ea90d28c 100644 --- a/server/models/video/video-streaming-playlist.ts +++ b/server/models/video/video-streaming-playlist.ts | |||
@@ -1,16 +1,16 @@ | |||
1 | import { AllowNull, BelongsTo, Column, CreatedAt, ForeignKey, HasMany, Is, Model, Table, UpdatedAt, DataType } from 'sequelize-typescript' | 1 | import { AllowNull, BelongsTo, Column, CreatedAt, DataType, ForeignKey, HasMany, Is, Model, Table, UpdatedAt } from 'sequelize-typescript' |
2 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' | 2 | import { isVideoFileInfoHashValid } from '../../helpers/custom-validators/videos' |
3 | import { throwIfNotValid } from '../utils' | 3 | import { throwIfNotValid } from '../utils' |
4 | import { VideoModel } from './video' | 4 | import { VideoModel } from './video' |
5 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' | 5 | import { VideoRedundancyModel } from '../redundancy/video-redundancy' |
6 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | 6 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' |
7 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' | 7 | import { isActivityPubUrlValid } from '../../helpers/custom-validators/activitypub/misc' |
8 | import { CONSTRAINTS_FIELDS, STATIC_PATHS, P2P_MEDIA_LOADER_PEER_VERSION } from '../../initializers/constants' | 8 | import { CONSTRAINTS_FIELDS, P2P_MEDIA_LOADER_PEER_VERSION, STATIC_PATHS } from '../../initializers/constants' |
9 | import { VideoFileModel } from './video-file' | ||
10 | import { join } from 'path' | 9 | import { join } from 'path' |
11 | import { sha1 } from '../../helpers/core-utils' | 10 | import { sha1 } from '../../helpers/core-utils' |
12 | import { isArrayOf } from '../../helpers/custom-validators/misc' | 11 | import { isArrayOf } from '../../helpers/custom-validators/misc' |
13 | import { QueryTypes, Op } from 'sequelize' | 12 | import { Op, QueryTypes } from 'sequelize' |
13 | import { MStreamingPlaylist, MVideoFile } from '@server/typings/models' | ||
14 | 14 | ||
15 | @Table({ | 15 | @Table({ |
16 | tableName: 'videoStreamingPlaylist', | 16 | tableName: 'videoStreamingPlaylist', |
@@ -91,7 +91,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod | |||
91 | .then(results => results.length === 1) | 91 | .then(results => results.length === 1) |
92 | } | 92 | } |
93 | 93 | ||
94 | static buildP2PMediaLoaderInfoHashes (playlistUrl: string, videoFiles: VideoFileModel[]) { | 94 | static buildP2PMediaLoaderInfoHashes (playlistUrl: string, videoFiles: MVideoFile[]) { |
95 | const hashes: string[] = [] | 95 | const hashes: string[] = [] |
96 | 96 | ||
97 | // https://github.com/Novage/p2p-media-loader/blob/master/p2p-media-loader-core/lib/p2p-media-manager.ts#L115 | 97 | // https://github.com/Novage/p2p-media-loader/blob/master/p2p-media-loader-core/lib/p2p-media-manager.ts#L115 |
@@ -165,7 +165,7 @@ export class VideoStreamingPlaylistModel extends Model<VideoStreamingPlaylistMod | |||
165 | return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getStringType() + '/' + this.Video.uuid | 165 | return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getStringType() + '/' + this.Video.uuid |
166 | } | 166 | } |
167 | 167 | ||
168 | hasSameUniqueKeysThan (other: VideoStreamingPlaylistModel) { | 168 | hasSameUniqueKeysThan (other: MStreamingPlaylist) { |
169 | return this.type === other.type && | 169 | return this.type === other.type && |
170 | this.videoId === other.videoId | 170 | this.videoId === other.videoId |
171 | } | 171 | } |
diff --git a/server/models/video/video.ts b/server/models/video/video.ts index b59df397d..6856dcd9f 100644 --- a/server/models/video/video.ts +++ b/server/models/video/video.ts | |||
@@ -36,7 +36,7 @@ import { | |||
36 | Table, | 36 | Table, |
37 | UpdatedAt | 37 | UpdatedAt |
38 | } from 'sequelize-typescript' | 38 | } from 'sequelize-typescript' |
39 | import { UserRight, VideoPrivacy, VideoResolution, VideoState } from '../../../shared' | 39 | import { UserRight, VideoPrivacy, VideoState } from '../../../shared' |
40 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' | 40 | import { VideoTorrentObject } from '../../../shared/models/activitypub/objects' |
41 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' | 41 | import { Video, VideoDetails, VideoFile } from '../../../shared/models/videos' |
42 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' | 42 | import { VideoFilter } from '../../../shared/models/videos/video-query.type' |
@@ -111,7 +111,6 @@ import { | |||
111 | videoModelToFormattedJSON | 111 | videoModelToFormattedJSON |
112 | } from './video-format-utils' | 112 | } from './video-format-utils' |
113 | import { UserVideoHistoryModel } from '../account/user-video-history' | 113 | import { UserVideoHistoryModel } from '../account/user-video-history' |
114 | import { UserModel } from '../account/user' | ||
115 | import { VideoImportModel } from './video-import' | 114 | import { VideoImportModel } from './video-import' |
116 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' | 115 | import { VideoStreamingPlaylistModel } from './video-streaming-playlist' |
117 | import { VideoPlaylistElementModel } from './video-playlist-element' | 116 | import { VideoPlaylistElementModel } from './video-playlist-element' |
@@ -120,6 +119,29 @@ import { ThumbnailModel } from './thumbnail' | |||
120 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' | 119 | import { ThumbnailType } from '../../../shared/models/videos/thumbnail.type' |
121 | import { createTorrentPromise } from '../../helpers/webtorrent' | 120 | import { createTorrentPromise } from '../../helpers/webtorrent' |
122 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' | 121 | import { VideoStreamingPlaylistType } from '../../../shared/models/videos/video-streaming-playlist.type' |
122 | import { | ||
123 | MChannel, | ||
124 | MChannelAccountDefault, | ||
125 | MChannelId, | ||
126 | MUserAccountId, | ||
127 | MUserId, | ||
128 | MVideoAccountLight, | ||
129 | MVideoAccountLightBlacklistAllFiles, | ||
130 | MVideoAP, | ||
131 | MVideoDetails, | ||
132 | MVideoFormattable, | ||
133 | MVideoFormattableDetails, | ||
134 | MVideoForUser, | ||
135 | MVideoFullLight, | ||
136 | MVideoIdThumbnail, | ||
137 | MVideoThumbnail, | ||
138 | MVideoThumbnailBlacklist, | ||
139 | MVideoWithAllFiles, | ||
140 | MVideoWithFile, | ||
141 | MVideoWithRights | ||
142 | } from '../../typings/models' | ||
143 | import { MVideoFile, MVideoFileRedundanciesOpt } from '../../typings/models/video/video-file' | ||
144 | import { MThumbnail } from '../../typings/models/video/thumbnail' | ||
123 | 145 | ||
124 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation | 146 | // FIXME: Define indexes here because there is an issue with TS and Sequelize.literal when called directly in the annotation |
125 | const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [ | 147 | const indexes: (ModelIndexesOptions & { where?: WhereOptions })[] = [ |
@@ -232,8 +254,8 @@ export type AvailableForListIDsOptions = { | |||
232 | videoPlaylistId?: number | 254 | videoPlaylistId?: number |
233 | 255 | ||
234 | trendingDays?: number | 256 | trendingDays?: number |
235 | user?: UserModel, | 257 | user?: MUserAccountId |
236 | historyOfUser?: UserModel | 258 | historyOfUser?: MUserId |
237 | 259 | ||
238 | baseWhere?: WhereOptions[] | 260 | baseWhere?: WhereOptions[] |
239 | } | 261 | } |
@@ -446,13 +468,15 @@ export type AvailableForListIDsOptions = { | |||
446 | // FIXME: issues with sequelize count when making a join on n:m relation, so we just make a IN() | 468 | // FIXME: issues with sequelize count when making a join on n:m relation, so we just make a IN() |
447 | if (options.tagsAllOf || options.tagsOneOf) { | 469 | if (options.tagsAllOf || options.tagsOneOf) { |
448 | if (options.tagsOneOf) { | 470 | if (options.tagsOneOf) { |
471 | const tagsOneOfLower = options.tagsOneOf.map(t => t.toLowerCase()) | ||
472 | |||
449 | whereAnd.push({ | 473 | whereAnd.push({ |
450 | id: { | 474 | id: { |
451 | [ Op.in ]: Sequelize.literal( | 475 | [ Op.in ]: Sequelize.literal( |
452 | '(' + | 476 | '(' + |
453 | 'SELECT "videoId" FROM "videoTag" ' + | 477 | 'SELECT "videoId" FROM "videoTag" ' + |
454 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | 478 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + |
455 | 'WHERE "tag"."name" IN (' + createSafeIn(VideoModel, options.tagsOneOf) + ')' + | 479 | 'WHERE lower("tag"."name") IN (' + createSafeIn(VideoModel, tagsOneOfLower) + ')' + |
456 | ')' | 480 | ')' |
457 | ) | 481 | ) |
458 | } | 482 | } |
@@ -460,14 +484,16 @@ export type AvailableForListIDsOptions = { | |||
460 | } | 484 | } |
461 | 485 | ||
462 | if (options.tagsAllOf) { | 486 | if (options.tagsAllOf) { |
487 | const tagsAllOfLower = options.tagsAllOf.map(t => t.toLowerCase()) | ||
488 | |||
463 | whereAnd.push({ | 489 | whereAnd.push({ |
464 | id: { | 490 | id: { |
465 | [ Op.in ]: Sequelize.literal( | 491 | [ Op.in ]: Sequelize.literal( |
466 | '(' + | 492 | '(' + |
467 | 'SELECT "videoId" FROM "videoTag" ' + | 493 | 'SELECT "videoId" FROM "videoTag" ' + |
468 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + | 494 | 'INNER JOIN "tag" ON "tag"."id" = "videoTag"."tagId" ' + |
469 | 'WHERE "tag"."name" IN (' + createSafeIn(VideoModel, options.tagsAllOf) + ')' + | 495 | 'WHERE lower("tag"."name") IN (' + createSafeIn(VideoModel, tagsAllOfLower) + ')' + |
470 | 'GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + options.tagsAllOf.length + | 496 | 'GROUP BY "videoTag"."videoId" HAVING COUNT(*) = ' + tagsAllOfLower.length + |
471 | ')' | 497 | ')' |
472 | ) | 498 | ) |
473 | } | 499 | } |
@@ -634,7 +660,7 @@ export type AvailableForListIDsOptions = { | |||
634 | [ ScopeNames.WITH_BLACKLISTED ]: { | 660 | [ ScopeNames.WITH_BLACKLISTED ]: { |
635 | include: [ | 661 | include: [ |
636 | { | 662 | { |
637 | attributes: [ 'id', 'reason' ], | 663 | attributes: [ 'id', 'reason', 'unfederated' ], |
638 | model: VideoBlacklistModel, | 664 | model: VideoBlacklistModel, |
639 | required: false | 665 | required: false |
640 | } | 666 | } |
@@ -989,18 +1015,16 @@ export class VideoModel extends Model<VideoModel> { | |||
989 | VideoCaptions: VideoCaptionModel[] | 1015 | VideoCaptions: VideoCaptionModel[] |
990 | 1016 | ||
991 | @BeforeDestroy | 1017 | @BeforeDestroy |
992 | static async sendDelete (instance: VideoModel, options) { | 1018 | static async sendDelete (instance: MVideoAccountLight, options) { |
993 | if (instance.isOwned()) { | 1019 | if (instance.isOwned()) { |
994 | if (!instance.VideoChannel) { | 1020 | if (!instance.VideoChannel) { |
995 | instance.VideoChannel = await instance.$get('VideoChannel', { | 1021 | instance.VideoChannel = await instance.$get('VideoChannel', { |
996 | include: [ | 1022 | include: [ |
997 | { | 1023 | ActorModel, |
998 | model: AccountModel, | 1024 | AccountModel |
999 | include: [ ActorModel ] | ||
1000 | } | ||
1001 | ], | 1025 | ], |
1002 | transaction: options.transaction | 1026 | transaction: options.transaction |
1003 | }) as VideoChannelModel | 1027 | }) as MChannelAccountDefault |
1004 | } | 1028 | } |
1005 | 1029 | ||
1006 | return sendDeleteVideo(instance, options.transaction) | 1030 | return sendDeleteVideo(instance, options.transaction) |
@@ -1039,7 +1063,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1039 | return undefined | 1063 | return undefined |
1040 | } | 1064 | } |
1041 | 1065 | ||
1042 | static listLocal () { | 1066 | static listLocal (): Bluebird<MVideoWithAllFiles[]> { |
1043 | const query = { | 1067 | const query = { |
1044 | where: { | 1068 | where: { |
1045 | remote: false | 1069 | remote: false |
@@ -1159,7 +1183,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1159 | }) | 1183 | }) |
1160 | } | 1184 | } |
1161 | 1185 | ||
1162 | static listUserVideosForApi (accountId: number, start: number, count: number, sort: string, withFiles = false) { | 1186 | static listUserVideosForApi (accountId: number, start: number, count: number, sort: string) { |
1163 | function buildBaseQuery (): FindOptions { | 1187 | function buildBaseQuery (): FindOptions { |
1164 | return { | 1188 | return { |
1165 | offset: start, | 1189 | offset: start, |
@@ -1192,16 +1216,9 @@ export class VideoModel extends Model<VideoModel> { | |||
1192 | ScopeNames.WITH_THUMBNAILS | 1216 | ScopeNames.WITH_THUMBNAILS |
1193 | ] | 1217 | ] |
1194 | 1218 | ||
1195 | if (withFiles === true) { | ||
1196 | findQuery.include.push({ | ||
1197 | model: VideoFileModel.unscoped(), | ||
1198 | required: true | ||
1199 | }) | ||
1200 | } | ||
1201 | |||
1202 | return Promise.all([ | 1219 | return Promise.all([ |
1203 | VideoModel.count(countQuery), | 1220 | VideoModel.count(countQuery), |
1204 | VideoModel.scope(findScopes).findAll(findQuery) | 1221 | VideoModel.scope(findScopes).findAll<MVideoForUser>(findQuery) |
1205 | ]).then(([ count, rows ]) => { | 1222 | ]).then(([ count, rows ]) => { |
1206 | return { | 1223 | return { |
1207 | data: rows, | 1224 | data: rows, |
@@ -1228,8 +1245,8 @@ export class VideoModel extends Model<VideoModel> { | |||
1228 | followerActorId?: number | 1245 | followerActorId?: number |
1229 | videoPlaylistId?: number, | 1246 | videoPlaylistId?: number, |
1230 | trendingDays?: number, | 1247 | trendingDays?: number, |
1231 | user?: UserModel, | 1248 | user?: MUserAccountId, |
1232 | historyOfUser?: UserModel | 1249 | historyOfUser?: MUserId |
1233 | }, countVideos = true) { | 1250 | }, countVideos = true) { |
1234 | if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { | 1251 | if (options.filter && options.filter === 'all-local' && !options.user.hasRight(UserRight.SEE_ALL_VIDEOS)) { |
1235 | throw new Error('Try to filter all-local but no user has not the see all videos right') | 1252 | throw new Error('Try to filter all-local but no user has not the see all videos right') |
@@ -1294,7 +1311,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1294 | tagsAllOf?: string[] | 1311 | tagsAllOf?: string[] |
1295 | durationMin?: number // seconds | 1312 | durationMin?: number // seconds |
1296 | durationMax?: number // seconds | 1313 | durationMax?: number // seconds |
1297 | user?: UserModel, | 1314 | user?: MUserAccountId, |
1298 | filter?: VideoFilter | 1315 | filter?: VideoFilter |
1299 | }) { | 1316 | }) { |
1300 | const whereAnd = [] | 1317 | const whereAnd = [] |
@@ -1387,7 +1404,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1387 | return VideoModel.getAvailableForApi(query, queryOptions) | 1404 | return VideoModel.getAvailableForApi(query, queryOptions) |
1388 | } | 1405 | } |
1389 | 1406 | ||
1390 | static load (id: number | string, t?: Transaction) { | 1407 | static load (id: number | string, t?: Transaction): Bluebird<MVideoThumbnail> { |
1391 | const where = buildWhereIdOrUUID(id) | 1408 | const where = buildWhereIdOrUUID(id) |
1392 | const options = { | 1409 | const options = { |
1393 | where, | 1410 | where, |
@@ -1397,7 +1414,20 @@ export class VideoModel extends Model<VideoModel> { | |||
1397 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) | 1414 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) |
1398 | } | 1415 | } |
1399 | 1416 | ||
1400 | static loadWithRights (id: number | string, t?: Transaction) { | 1417 | static loadWithBlacklist (id: number | string, t?: Transaction): Bluebird<MVideoThumbnailBlacklist> { |
1418 | const where = buildWhereIdOrUUID(id) | ||
1419 | const options = { | ||
1420 | where, | ||
1421 | transaction: t | ||
1422 | } | ||
1423 | |||
1424 | return VideoModel.scope([ | ||
1425 | ScopeNames.WITH_THUMBNAILS, | ||
1426 | ScopeNames.WITH_BLACKLISTED | ||
1427 | ]).findOne(options) | ||
1428 | } | ||
1429 | |||
1430 | static loadWithRights (id: number | string, t?: Transaction): Bluebird<MVideoWithRights> { | ||
1401 | const where = buildWhereIdOrUUID(id) | 1431 | const where = buildWhereIdOrUUID(id) |
1402 | const options = { | 1432 | const options = { |
1403 | where, | 1433 | where, |
@@ -1411,7 +1441,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1411 | ]).findOne(options) | 1441 | ]).findOne(options) |
1412 | } | 1442 | } |
1413 | 1443 | ||
1414 | static loadOnlyId (id: number | string, t?: Transaction) { | 1444 | static loadOnlyId (id: number | string, t?: Transaction): Bluebird<MVideoIdThumbnail> { |
1415 | const where = buildWhereIdOrUUID(id) | 1445 | const where = buildWhereIdOrUUID(id) |
1416 | 1446 | ||
1417 | const options = { | 1447 | const options = { |
@@ -1423,7 +1453,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1423 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) | 1453 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) |
1424 | } | 1454 | } |
1425 | 1455 | ||
1426 | static loadWithFiles (id: number | string, t?: Transaction, logging?: boolean) { | 1456 | static loadWithFiles (id: number | string, t?: Transaction, logging?: boolean): Bluebird<MVideoWithAllFiles> { |
1427 | const where = buildWhereIdOrUUID(id) | 1457 | const where = buildWhereIdOrUUID(id) |
1428 | 1458 | ||
1429 | const query = { | 1459 | const query = { |
@@ -1439,7 +1469,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1439 | ]).findOne(query) | 1469 | ]).findOne(query) |
1440 | } | 1470 | } |
1441 | 1471 | ||
1442 | static loadByUUID (uuid: string) { | 1472 | static loadByUUID (uuid: string): Bluebird<MVideoThumbnail> { |
1443 | const options = { | 1473 | const options = { |
1444 | where: { | 1474 | where: { |
1445 | uuid | 1475 | uuid |
@@ -1449,7 +1479,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1449 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) | 1479 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(options) |
1450 | } | 1480 | } |
1451 | 1481 | ||
1452 | static loadByUrl (url: string, transaction?: Transaction) { | 1482 | static loadByUrl (url: string, transaction?: Transaction): Bluebird<MVideoThumbnail> { |
1453 | const query: FindOptions = { | 1483 | const query: FindOptions = { |
1454 | where: { | 1484 | where: { |
1455 | url | 1485 | url |
@@ -1460,7 +1490,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1460 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) | 1490 | return VideoModel.scope(ScopeNames.WITH_THUMBNAILS).findOne(query) |
1461 | } | 1491 | } |
1462 | 1492 | ||
1463 | static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction) { | 1493 | static loadByUrlAndPopulateAccount (url: string, transaction?: Transaction): Bluebird<MVideoAccountLightBlacklistAllFiles> { |
1464 | const query: FindOptions = { | 1494 | const query: FindOptions = { |
1465 | where: { | 1495 | where: { |
1466 | url | 1496 | url |
@@ -1472,11 +1502,12 @@ export class VideoModel extends Model<VideoModel> { | |||
1472 | ScopeNames.WITH_ACCOUNT_DETAILS, | 1502 | ScopeNames.WITH_ACCOUNT_DETAILS, |
1473 | ScopeNames.WITH_FILES, | 1503 | ScopeNames.WITH_FILES, |
1474 | ScopeNames.WITH_STREAMING_PLAYLISTS, | 1504 | ScopeNames.WITH_STREAMING_PLAYLISTS, |
1475 | ScopeNames.WITH_THUMBNAILS | 1505 | ScopeNames.WITH_THUMBNAILS, |
1506 | ScopeNames.WITH_BLACKLISTED | ||
1476 | ]).findOne(query) | 1507 | ]).findOne(query) |
1477 | } | 1508 | } |
1478 | 1509 | ||
1479 | static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number) { | 1510 | static loadAndPopulateAccountAndServerAndTags (id: number | string, t?: Transaction, userId?: number): Bluebird<MVideoFullLight> { |
1480 | const where = buildWhereIdOrUUID(id) | 1511 | const where = buildWhereIdOrUUID(id) |
1481 | 1512 | ||
1482 | const options = { | 1513 | const options = { |
@@ -1508,7 +1539,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1508 | id: number | string, | 1539 | id: number | string, |
1509 | t?: Transaction, | 1540 | t?: Transaction, |
1510 | userId?: number | 1541 | userId?: number |
1511 | }) { | 1542 | }): Bluebird<MVideoDetails> { |
1512 | const { id, t, userId } = parameters | 1543 | const { id, t, userId } = parameters |
1513 | const where = buildWhereIdOrUUID(id) | 1544 | const where = buildWhereIdOrUUID(id) |
1514 | 1545 | ||
@@ -1586,7 +1617,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1586 | .then(results => results.length === 1) | 1617 | .then(results => results.length === 1) |
1587 | } | 1618 | } |
1588 | 1619 | ||
1589 | static bulkUpdateSupportField (videoChannel: VideoChannelModel, t: Transaction) { | 1620 | static bulkUpdateSupportField (videoChannel: MChannel, t: Transaction) { |
1590 | const options = { | 1621 | const options = { |
1591 | where: { | 1622 | where: { |
1592 | channelId: videoChannel.id | 1623 | channelId: videoChannel.id |
@@ -1597,7 +1628,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1597 | return VideoModel.update({ support: videoChannel.support }, options) | 1628 | return VideoModel.update({ support: videoChannel.support }, options) |
1598 | } | 1629 | } |
1599 | 1630 | ||
1600 | static getAllIdsFromChannel (videoChannel: VideoChannelModel) { | 1631 | static getAllIdsFromChannel (videoChannel: MChannelId): Bluebird<number[]> { |
1601 | const query = { | 1632 | const query = { |
1602 | attributes: [ 'id' ], | 1633 | attributes: [ 'id' ], |
1603 | where: { | 1634 | where: { |
@@ -1756,20 +1787,20 @@ export class VideoModel extends Model<VideoModel> { | |||
1756 | this.VideoChannel.Account.isBlocked() | 1787 | this.VideoChannel.Account.isBlocked() |
1757 | } | 1788 | } |
1758 | 1789 | ||
1759 | getOriginalFile () { | 1790 | getOriginalFile <T extends MVideoWithFile> (this: T) { |
1760 | if (Array.isArray(this.VideoFiles) === false) return undefined | 1791 | if (Array.isArray(this.VideoFiles) === false) return undefined |
1761 | 1792 | ||
1762 | // The original file is the file that have the higher resolution | 1793 | // The original file is the file that have the higher resolution |
1763 | return maxBy(this.VideoFiles, file => file.resolution) | 1794 | return maxBy(this.VideoFiles, file => file.resolution) |
1764 | } | 1795 | } |
1765 | 1796 | ||
1766 | getFile (resolution: number) { | 1797 | getFile <T extends MVideoWithFile> (this: T, resolution: number) { |
1767 | if (Array.isArray(this.VideoFiles) === false) return undefined | 1798 | if (Array.isArray(this.VideoFiles) === false) return undefined |
1768 | 1799 | ||
1769 | return this.VideoFiles.find(f => f.resolution === resolution) | 1800 | return this.VideoFiles.find(f => f.resolution === resolution) |
1770 | } | 1801 | } |
1771 | 1802 | ||
1772 | async addAndSaveThumbnail (thumbnail: ThumbnailModel, transaction: Transaction) { | 1803 | async addAndSaveThumbnail (thumbnail: MThumbnail, transaction: Transaction) { |
1773 | thumbnail.videoId = this.id | 1804 | thumbnail.videoId = this.id |
1774 | 1805 | ||
1775 | const savedThumbnail = await thumbnail.save({ transaction }) | 1806 | const savedThumbnail = await thumbnail.save({ transaction }) |
@@ -1782,7 +1813,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1782 | this.Thumbnails.push(savedThumbnail) | 1813 | this.Thumbnails.push(savedThumbnail) |
1783 | } | 1814 | } |
1784 | 1815 | ||
1785 | getVideoFilename (videoFile: VideoFileModel) { | 1816 | getVideoFilename (videoFile: MVideoFile) { |
1786 | return this.uuid + '-' + videoFile.resolution + videoFile.extname | 1817 | return this.uuid + '-' + videoFile.resolution + videoFile.extname |
1787 | } | 1818 | } |
1788 | 1819 | ||
@@ -1806,7 +1837,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1806 | return this.Thumbnails.find(t => t.type === ThumbnailType.PREVIEW) | 1837 | return this.Thumbnails.find(t => t.type === ThumbnailType.PREVIEW) |
1807 | } | 1838 | } |
1808 | 1839 | ||
1809 | getTorrentFileName (videoFile: VideoFileModel) { | 1840 | getTorrentFileName (videoFile: MVideoFile) { |
1810 | const extension = '.torrent' | 1841 | const extension = '.torrent' |
1811 | return this.uuid + '-' + videoFile.resolution + extension | 1842 | return this.uuid + '-' + videoFile.resolution + extension |
1812 | } | 1843 | } |
@@ -1815,15 +1846,15 @@ export class VideoModel extends Model<VideoModel> { | |||
1815 | return this.remote === false | 1846 | return this.remote === false |
1816 | } | 1847 | } |
1817 | 1848 | ||
1818 | getTorrentFilePath (videoFile: VideoFileModel) { | 1849 | getTorrentFilePath (videoFile: MVideoFile) { |
1819 | return join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) | 1850 | return join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) |
1820 | } | 1851 | } |
1821 | 1852 | ||
1822 | getVideoFilePath (videoFile: VideoFileModel) { | 1853 | getVideoFilePath (videoFile: MVideoFile) { |
1823 | return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) | 1854 | return join(CONFIG.STORAGE.VIDEOS_DIR, this.getVideoFilename(videoFile)) |
1824 | } | 1855 | } |
1825 | 1856 | ||
1826 | async createTorrentAndSetInfoHash (videoFile: VideoFileModel) { | 1857 | async createTorrentAndSetInfoHash (videoFile: MVideoFile) { |
1827 | const options = { | 1858 | const options = { |
1828 | // Keep the extname, it's used by the client to stream the file inside a web browser | 1859 | // Keep the extname, it's used by the client to stream the file inside a web browser |
1829 | name: `${this.name} ${videoFile.resolution}p${videoFile.extname}`, | 1860 | name: `${this.name} ${videoFile.resolution}p${videoFile.extname}`, |
@@ -1869,11 +1900,11 @@ export class VideoModel extends Model<VideoModel> { | |||
1869 | return join(LAZY_STATIC_PATHS.PREVIEWS, preview.filename) | 1900 | return join(LAZY_STATIC_PATHS.PREVIEWS, preview.filename) |
1870 | } | 1901 | } |
1871 | 1902 | ||
1872 | toFormattedJSON (options?: VideoFormattingJSONOptions): Video { | 1903 | toFormattedJSON (this: MVideoFormattable, options?: VideoFormattingJSONOptions): Video { |
1873 | return videoModelToFormattedJSON(this, options) | 1904 | return videoModelToFormattedJSON(this, options) |
1874 | } | 1905 | } |
1875 | 1906 | ||
1876 | toFormattedDetailsJSON (): VideoDetails { | 1907 | toFormattedDetailsJSON (this: MVideoFormattableDetails): VideoDetails { |
1877 | return videoModelToFormattedDetailsJSON(this) | 1908 | return videoModelToFormattedDetailsJSON(this) |
1878 | } | 1909 | } |
1879 | 1910 | ||
@@ -1881,7 +1912,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1881 | return videoFilesModelToFormattedJSON(this, this.VideoFiles) | 1912 | return videoFilesModelToFormattedJSON(this, this.VideoFiles) |
1882 | } | 1913 | } |
1883 | 1914 | ||
1884 | toActivityPubObject (): VideoTorrentObject { | 1915 | toActivityPubObject (this: MVideoAP): VideoTorrentObject { |
1885 | return videoModelToActivityPubObject(this) | 1916 | return videoModelToActivityPubObject(this) |
1886 | } | 1917 | } |
1887 | 1918 | ||
@@ -1908,7 +1939,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1908 | return this.VideoStreamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) | 1939 | return this.VideoStreamingPlaylists.find(p => p.type === VideoStreamingPlaylistType.HLS) |
1909 | } | 1940 | } |
1910 | 1941 | ||
1911 | removeFile (videoFile: VideoFileModel, isRedundancy = false) { | 1942 | removeFile (videoFile: MVideoFile, isRedundancy = false) { |
1912 | const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR | 1943 | const baseDir = isRedundancy ? CONFIG.STORAGE.REDUNDANCY_DIR : CONFIG.STORAGE.VIDEOS_DIR |
1913 | 1944 | ||
1914 | const filePath = join(baseDir, this.getVideoFilename(videoFile)) | 1945 | const filePath = join(baseDir, this.getVideoFilename(videoFile)) |
@@ -1916,7 +1947,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1916 | .catch(err => logger.warn('Cannot delete file %s.', filePath, { err })) | 1947 | .catch(err => logger.warn('Cannot delete file %s.', filePath, { err })) |
1917 | } | 1948 | } |
1918 | 1949 | ||
1919 | removeTorrent (videoFile: VideoFileModel) { | 1950 | removeTorrent (videoFile: MVideoFile) { |
1920 | const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) | 1951 | const torrentPath = join(CONFIG.STORAGE.TORRENTS_DIR, this.getTorrentFileName(videoFile)) |
1921 | return remove(torrentPath) | 1952 | return remove(torrentPath) |
1922 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) | 1953 | .catch(err => logger.warn('Cannot delete torrent %s.', torrentPath, { err })) |
@@ -1957,7 +1988,7 @@ export class VideoModel extends Model<VideoModel> { | |||
1957 | return { baseUrlHttp, baseUrlWs } | 1988 | return { baseUrlHttp, baseUrlWs } |
1958 | } | 1989 | } |
1959 | 1990 | ||
1960 | generateMagnetUri (videoFile: VideoFileModel, baseUrlHttp: string, baseUrlWs: string) { | 1991 | generateMagnetUri (videoFile: MVideoFileRedundanciesOpt, baseUrlHttp: string, baseUrlWs: string) { |
1961 | const xs = this.getTorrentUrl(videoFile, baseUrlHttp) | 1992 | const xs = this.getTorrentUrl(videoFile, baseUrlHttp) |
1962 | const announce = this.getTrackerUrls(baseUrlHttp, baseUrlWs) | 1993 | const announce = this.getTrackerUrls(baseUrlHttp, baseUrlWs) |
1963 | let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ] | 1994 | let urlList = [ this.getVideoFileUrl(videoFile, baseUrlHttp) ] |
@@ -1980,27 +2011,27 @@ export class VideoModel extends Model<VideoModel> { | |||
1980 | return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] | 2011 | return [ baseUrlWs + '/tracker/socket', baseUrlHttp + '/tracker/announce' ] |
1981 | } | 2012 | } |
1982 | 2013 | ||
1983 | getTorrentUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 2014 | getTorrentUrl (videoFile: MVideoFile, baseUrlHttp: string) { |
1984 | return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile) | 2015 | return baseUrlHttp + STATIC_PATHS.TORRENTS + this.getTorrentFileName(videoFile) |
1985 | } | 2016 | } |
1986 | 2017 | ||
1987 | getTorrentDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 2018 | getTorrentDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) { |
1988 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + this.getTorrentFileName(videoFile) | 2019 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.TORRENTS + this.getTorrentFileName(videoFile) |
1989 | } | 2020 | } |
1990 | 2021 | ||
1991 | getVideoFileUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 2022 | getVideoFileUrl (videoFile: MVideoFile, baseUrlHttp: string) { |
1992 | return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) | 2023 | return baseUrlHttp + STATIC_PATHS.WEBSEED + this.getVideoFilename(videoFile) |
1993 | } | 2024 | } |
1994 | 2025 | ||
1995 | getVideoRedundancyUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 2026 | getVideoRedundancyUrl (videoFile: MVideoFile, baseUrlHttp: string) { |
1996 | return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getVideoFilename(videoFile) | 2027 | return baseUrlHttp + STATIC_PATHS.REDUNDANCY + this.getVideoFilename(videoFile) |
1997 | } | 2028 | } |
1998 | 2029 | ||
1999 | getVideoFileDownloadUrl (videoFile: VideoFileModel, baseUrlHttp: string) { | 2030 | getVideoFileDownloadUrl (videoFile: MVideoFile, baseUrlHttp: string) { |
2000 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile) | 2031 | return baseUrlHttp + STATIC_DOWNLOAD_PATHS.VIDEOS + this.getVideoFilename(videoFile) |
2001 | } | 2032 | } |
2002 | 2033 | ||
2003 | getBandwidthBits (videoFile: VideoFileModel) { | 2034 | getBandwidthBits (videoFile: MVideoFile) { |
2004 | return Math.ceil((videoFile.size * 8) / this.duration) | 2035 | return Math.ceil((videoFile.size * 8) / this.duration) |
2005 | } | 2036 | } |
2006 | } | 2037 | } |
diff --git a/server/tests/api/activitypub/helpers.ts b/server/tests/api/activitypub/helpers.ts index 365d0e1ae..0d1f154fe 100644 --- a/server/tests/api/activitypub/helpers.ts +++ b/server/tests/api/activitypub/helpers.ts | |||
@@ -53,19 +53,6 @@ describe('Test activity pub helpers', function () { | |||
53 | expect(result).to.be.false | 53 | expect(result).to.be.false |
54 | }) | 54 | }) |
55 | 55 | ||
56 | it('Should fail with an invalid PeerTube URL', async function () { | ||
57 | const keys = require('./json/peertube/keys.json') | ||
58 | const body = require('./json/peertube/announce-without-context.json') | ||
59 | |||
60 | const actorSignature = { url: 'http://localhost:9002/accounts/peertube', privateKey: keys.privateKey } | ||
61 | const signedBody = await buildSignedActivity(actorSignature as any, body) | ||
62 | |||
63 | const fromActor = { publicKey: keys.publicKey, url: 'http://localhost:9003/accounts/peertube' } | ||
64 | const result = await isJsonLDSignatureVerified(fromActor as any, signedBody) | ||
65 | |||
66 | expect(result).to.be.false | ||
67 | }) | ||
68 | |||
69 | it('Should succeed with a valid PeerTube signature', async function () { | 56 | it('Should succeed with a valid PeerTube signature', async function () { |
70 | const keys = require('./json/peertube/keys.json') | 57 | const keys = require('./json/peertube/keys.json') |
71 | const body = require('./json/peertube/announce-without-context.json') | 58 | const body = require('./json/peertube/announce-without-context.json') |
diff --git a/server/tests/api/check-params/config.ts b/server/tests/api/check-params/config.ts index 7773ae1e7..9435bb1e8 100644 --- a/server/tests/api/check-params/config.ts +++ b/server/tests/api/check-params/config.ts | |||
@@ -5,8 +5,16 @@ import 'mocha' | |||
5 | import { CustomConfig } from '../../../../shared/models/server/custom-config.model' | 5 | import { CustomConfig } from '../../../../shared/models/server/custom-config.model' |
6 | 6 | ||
7 | import { | 7 | import { |
8 | createUser, flushTests, killallServers, makeDeleteRequest, makeGetRequest, makePutBodyRequest, flushAndRunServer, ServerInfo, | 8 | cleanupTests, |
9 | setAccessTokensToServers, userLogin, immutableAssign, cleanupTests | 9 | createUser, |
10 | flushAndRunServer, | ||
11 | immutableAssign, | ||
12 | makeDeleteRequest, | ||
13 | makeGetRequest, | ||
14 | makePutBodyRequest, | ||
15 | ServerInfo, | ||
16 | setAccessTokensToServers, | ||
17 | userLogin | ||
10 | } from '../../../../shared/extra-utils' | 18 | } from '../../../../shared/extra-utils' |
11 | 19 | ||
12 | describe('Test config API validators', function () { | 20 | describe('Test config API validators', function () { |
@@ -19,6 +27,18 @@ describe('Test config API validators', function () { | |||
19 | shortDescription: 'my short description', | 27 | shortDescription: 'my short description', |
20 | description: 'my super description', | 28 | description: 'my super description', |
21 | terms: 'my super terms', | 29 | terms: 'my super terms', |
30 | codeOfConduct: 'my super coc', | ||
31 | |||
32 | creationReason: 'my super reason', | ||
33 | moderationInformation: 'my super moderation information', | ||
34 | administrator: 'Kuja', | ||
35 | maintenanceLifetime: 'forever', | ||
36 | businessModel: 'my super business model', | ||
37 | hardwareInformation: '2vCore 3GB RAM', | ||
38 | |||
39 | languages: [ 'en', 'es' ], | ||
40 | categories: [ 1, 2 ], | ||
41 | |||
22 | isNSFW: true, | 42 | isNSFW: true, |
23 | defaultClientRoute: '/videos/recently-added', | 43 | defaultClientRoute: '/videos/recently-added', |
24 | defaultNSFWPolicy: 'blur', | 44 | defaultNSFWPolicy: 'blur', |
@@ -98,6 +118,17 @@ describe('Test config API validators', function () { | |||
98 | enabled: false, | 118 | enabled: false, |
99 | manualApproval: true | 119 | manualApproval: true |
100 | } | 120 | } |
121 | }, | ||
122 | followings: { | ||
123 | instance: { | ||
124 | autoFollowBack: { | ||
125 | enabled: true | ||
126 | }, | ||
127 | autoFollowIndex: { | ||
128 | enabled: true, | ||
129 | indexUrl: 'https://index.example.com' | ||
130 | } | ||
131 | } | ||
101 | } | 132 | } |
102 | } | 133 | } |
103 | 134 | ||
diff --git a/server/tests/api/check-params/user-notifications.ts b/server/tests/api/check-params/user-notifications.ts index 14ee20d45..3b06be7ef 100644 --- a/server/tests/api/check-params/user-notifications.ts +++ b/server/tests/api/check-params/user-notifications.ts | |||
@@ -172,7 +172,8 @@ describe('Test user notifications API validators', function () { | |||
172 | commentMention: UserNotificationSettingValue.WEB, | 172 | commentMention: UserNotificationSettingValue.WEB, |
173 | newFollow: UserNotificationSettingValue.WEB, | 173 | newFollow: UserNotificationSettingValue.WEB, |
174 | newUserRegistration: UserNotificationSettingValue.WEB, | 174 | newUserRegistration: UserNotificationSettingValue.WEB, |
175 | newInstanceFollower: UserNotificationSettingValue.WEB | 175 | newInstanceFollower: UserNotificationSettingValue.WEB, |
176 | autoInstanceFollowing: UserNotificationSettingValue.WEB | ||
176 | } | 177 | } |
177 | 178 | ||
178 | it('Should fail with missing fields', async function () { | 179 | it('Should fail with missing fields', async function () { |
diff --git a/server/tests/api/check-params/users.ts b/server/tests/api/check-params/users.ts index 939b919ed..55094795c 100644 --- a/server/tests/api/check-params/users.ts +++ b/server/tests/api/check-params/users.ts | |||
@@ -476,6 +476,22 @@ describe('Test users API validators', function () { | |||
476 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) | 476 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) |
477 | }) | 477 | }) |
478 | 478 | ||
479 | it('Should fail with an invalid noInstanceConfigWarningModal attribute', async function () { | ||
480 | const fields = { | ||
481 | noInstanceConfigWarningModal: -1 | ||
482 | } | ||
483 | |||
484 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) | ||
485 | }) | ||
486 | |||
487 | it('Should fail with an invalid noWelcomeModal attribute', async function () { | ||
488 | const fields = { | ||
489 | noWelcomeModal: -1 | ||
490 | } | ||
491 | |||
492 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields }) | ||
493 | }) | ||
494 | |||
479 | it('Should succeed to change password with the correct params', async function () { | 495 | it('Should succeed to change password with the correct params', async function () { |
480 | const fields = { | 496 | const fields = { |
481 | currentPassword: 'my super password', | 497 | currentPassword: 'my super password', |
@@ -483,7 +499,9 @@ describe('Test users API validators', function () { | |||
483 | nsfwPolicy: 'blur', | 499 | nsfwPolicy: 'blur', |
484 | autoPlayVideo: false, | 500 | autoPlayVideo: false, |
485 | email: 'super_email@example.com', | 501 | email: 'super_email@example.com', |
486 | theme: 'default' | 502 | theme: 'default', |
503 | noInstanceConfigWarningModal: true, | ||
504 | noWelcomeModal: true | ||
487 | } | 505 | } |
488 | 506 | ||
489 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 }) | 507 | await makePutBodyRequest({ url: server.url, path: path + 'me', token: userAccessToken, fields, statusCodeExpected: 204 }) |
diff --git a/server/tests/api/notifications/user-notifications.ts b/server/tests/api/notifications/user-notifications.ts index 6fa630562..15a34f5aa 100644 --- a/server/tests/api/notifications/user-notifications.ts +++ b/server/tests/api/notifications/user-notifications.ts | |||
@@ -14,10 +14,13 @@ import { | |||
14 | getVideoCommentThreads, | 14 | getVideoCommentThreads, |
15 | getVideoThreadComments, | 15 | getVideoThreadComments, |
16 | immutableAssign, | 16 | immutableAssign, |
17 | MockInstancesIndex, | ||
17 | registerUser, | 18 | registerUser, |
18 | removeVideoFromBlacklist, | 19 | removeVideoFromBlacklist, |
19 | reportVideoAbuse, | 20 | reportVideoAbuse, |
21 | unfollow, | ||
20 | updateCustomConfig, | 22 | updateCustomConfig, |
23 | updateCustomSubConfig, | ||
21 | updateMyUser, | 24 | updateMyUser, |
22 | updateVideo, | 25 | updateVideo, |
23 | updateVideoChannel, | 26 | updateVideoChannel, |
@@ -29,6 +32,7 @@ import { setAccessTokensToServers } from '../../../../shared/extra-utils/users/l | |||
29 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 32 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
30 | import { getUserNotificationSocket } from '../../../../shared/extra-utils/socket/socket-io' | 33 | import { getUserNotificationSocket } from '../../../../shared/extra-utils/socket/socket-io' |
31 | import { | 34 | import { |
35 | checkAutoInstanceFollowing, | ||
32 | checkCommentMention, | 36 | checkCommentMention, |
33 | CheckerBaseParams, | 37 | CheckerBaseParams, |
34 | checkMyVideoImportIsFinished, | 38 | checkMyVideoImportIsFinished, |
@@ -108,7 +112,8 @@ describe('Test users notifications', function () { | |||
108 | commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 112 | commentMention: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
109 | newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 113 | newFollow: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
110 | newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, | 114 | newUserRegistration: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
111 | newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL | 115 | newInstanceFollower: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL, |
116 | autoInstanceFollowing: UserNotificationSettingValue.WEB | UserNotificationSettingValue.EMAIL | ||
112 | } | 117 | } |
113 | 118 | ||
114 | before(async function () { | 119 | before(async function () { |
@@ -873,7 +878,18 @@ describe('Test users notifications', function () { | |||
873 | }) | 878 | }) |
874 | }) | 879 | }) |
875 | 880 | ||
876 | describe('New instance follower', function () { | 881 | describe('New instance follows', function () { |
882 | const instanceIndexServer = new MockInstancesIndex() | ||
883 | const config = { | ||
884 | followings: { | ||
885 | instance: { | ||
886 | autoFollowIndex: { | ||
887 | indexUrl: 'http://localhost:42100', | ||
888 | enabled: true | ||
889 | } | ||
890 | } | ||
891 | } | ||
892 | } | ||
877 | let baseParams: CheckerBaseParams | 893 | let baseParams: CheckerBaseParams |
878 | 894 | ||
879 | before(async () => { | 895 | before(async () => { |
@@ -883,6 +899,9 @@ describe('Test users notifications', function () { | |||
883 | socketNotifications: adminNotifications, | 899 | socketNotifications: adminNotifications, |
884 | token: servers[0].accessToken | 900 | token: servers[0].accessToken |
885 | } | 901 | } |
902 | |||
903 | await instanceIndexServer.initialize() | ||
904 | instanceIndexServer.addInstance(servers[1].host) | ||
886 | }) | 905 | }) |
887 | 906 | ||
888 | it('Should send a notification only to admin when there is a new instance follower', async function () { | 907 | it('Should send a notification only to admin when there is a new instance follower', async function () { |
@@ -897,6 +916,56 @@ describe('Test users notifications', function () { | |||
897 | const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } } | 916 | const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } } |
898 | await checkNewInstanceFollower(immutableAssign(baseParams, userOverride), 'localhost:' + servers[2].port, 'absence') | 917 | await checkNewInstanceFollower(immutableAssign(baseParams, userOverride), 'localhost:' + servers[2].port, 'absence') |
899 | }) | 918 | }) |
919 | |||
920 | it('Should send a notification on auto follow back', async function () { | ||
921 | this.timeout(40000) | ||
922 | |||
923 | await unfollow(servers[2].url, servers[2].accessToken, servers[0]) | ||
924 | await waitJobs(servers) | ||
925 | |||
926 | const config = { | ||
927 | followings: { | ||
928 | instance: { | ||
929 | autoFollowBack: { enabled: true } | ||
930 | } | ||
931 | } | ||
932 | } | ||
933 | await updateCustomSubConfig(servers[0].url, servers[0].accessToken, config) | ||
934 | |||
935 | await follow(servers[2].url, [ servers[0].url ], servers[2].accessToken) | ||
936 | |||
937 | await waitJobs(servers) | ||
938 | |||
939 | const followerHost = servers[0].host | ||
940 | const followingHost = servers[2].host | ||
941 | await checkAutoInstanceFollowing(baseParams, followerHost, followingHost, 'presence') | ||
942 | |||
943 | const userOverride = { socketNotifications: userNotifications, token: userAccessToken, check: { web: true, mail: false } } | ||
944 | await checkAutoInstanceFollowing(immutableAssign(baseParams, userOverride), followerHost, followingHost, 'absence') | ||
945 | |||
946 | config.followings.instance.autoFollowBack.enabled = false | ||
947 | await updateCustomSubConfig(servers[0].url, servers[0].accessToken, config) | ||
948 | await unfollow(servers[0].url, servers[0].accessToken, servers[2]) | ||
949 | await unfollow(servers[2].url, servers[2].accessToken, servers[0]) | ||
950 | }) | ||
951 | |||
952 | it('Should send a notification on auto instances index follow', async function () { | ||
953 | this.timeout(30000) | ||
954 | await unfollow(servers[0].url, servers[0].accessToken, servers[1]) | ||
955 | |||
956 | await updateCustomSubConfig(servers[0].url, servers[0].accessToken, config) | ||
957 | |||
958 | await wait(5000) | ||
959 | await waitJobs(servers) | ||
960 | |||
961 | const followerHost = servers[0].host | ||
962 | const followingHost = servers[1].host | ||
963 | await checkAutoInstanceFollowing(baseParams, followerHost, followingHost, 'presence') | ||
964 | |||
965 | config.followings.instance.autoFollowIndex.enabled = false | ||
966 | await updateCustomSubConfig(servers[0].url, servers[0].accessToken, config) | ||
967 | await unfollow(servers[0].url, servers[0].accessToken, servers[1]) | ||
968 | }) | ||
900 | }) | 969 | }) |
901 | 970 | ||
902 | describe('New actor follow', function () { | 971 | describe('New actor follow', function () { |
diff --git a/server/tests/api/search/search-videos.ts b/server/tests/api/search/search-videos.ts index c06200ffe..a3e05156b 100644 --- a/server/tests/api/search/search-videos.ts +++ b/server/tests/api/search/search-videos.ts | |||
@@ -206,7 +206,7 @@ describe('Test videos search', function () { | |||
206 | const query = { | 206 | const query = { |
207 | search: '9999', | 207 | search: '9999', |
208 | categoryOneOf: [ 1 ], | 208 | categoryOneOf: [ 1 ], |
209 | tagsOneOf: [ 'aaaa', 'ffff' ] | 209 | tagsOneOf: [ 'aAaa', 'ffff' ] |
210 | } | 210 | } |
211 | const res1 = await advancedVideosSearch(server.url, query) | 211 | const res1 = await advancedVideosSearch(server.url, query) |
212 | expect(res1.body.total).to.equal(2) | 212 | expect(res1.body.total).to.equal(2) |
@@ -219,15 +219,15 @@ describe('Test videos search', function () { | |||
219 | const query = { | 219 | const query = { |
220 | search: '9999', | 220 | search: '9999', |
221 | categoryOneOf: [ 1 ], | 221 | categoryOneOf: [ 1 ], |
222 | tagsAllOf: [ 'cccc' ] | 222 | tagsAllOf: [ 'CCcc' ] |
223 | } | 223 | } |
224 | const res1 = await advancedVideosSearch(server.url, query) | 224 | const res1 = await advancedVideosSearch(server.url, query) |
225 | expect(res1.body.total).to.equal(2) | 225 | expect(res1.body.total).to.equal(2) |
226 | 226 | ||
227 | const res2 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'blabla' ] })) | 227 | const res2 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'blAbla' ] })) |
228 | expect(res2.body.total).to.equal(0) | 228 | expect(res2.body.total).to.equal(0) |
229 | 229 | ||
230 | const res3 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'bbbb', 'cccc' ] })) | 230 | const res3 = await advancedVideosSearch(server.url, immutableAssign(query, { tagsAllOf: [ 'bbbb', 'CCCC' ] })) |
231 | expect(res3.body.total).to.equal(1) | 231 | expect(res3.body.total).to.equal(1) |
232 | }) | 232 | }) |
233 | 233 | ||
diff --git a/server/tests/api/server/auto-follows.ts b/server/tests/api/server/auto-follows.ts new file mode 100644 index 000000000..df468034c --- /dev/null +++ b/server/tests/api/server/auto-follows.ts | |||
@@ -0,0 +1,211 @@ | |||
1 | /* tslint:disable:no-unused-expression */ | ||
2 | |||
3 | import * as chai from 'chai' | ||
4 | import 'mocha' | ||
5 | import { | ||
6 | acceptFollower, | ||
7 | cleanupTests, | ||
8 | flushAndRunMultipleServers, | ||
9 | MockInstancesIndex, | ||
10 | ServerInfo, | ||
11 | setAccessTokensToServers, | ||
12 | unfollow, | ||
13 | updateCustomSubConfig, | ||
14 | wait | ||
15 | } from '../../../../shared/extra-utils/index' | ||
16 | import { follow, getFollowersListPaginationAndSort, getFollowingListPaginationAndSort } from '../../../../shared/extra-utils/server/follows' | ||
17 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | ||
18 | import { ActorFollow } from '../../../../shared/models/actors' | ||
19 | |||
20 | const expect = chai.expect | ||
21 | |||
22 | async function checkFollow (follower: ServerInfo, following: ServerInfo, exists: boolean) { | ||
23 | { | ||
24 | const res = await getFollowersListPaginationAndSort(following.url, 0, 5, '-createdAt') | ||
25 | const follows = res.body.data as ActorFollow[] | ||
26 | |||
27 | const follow = follows.find(f => { | ||
28 | return f.follower.host === follower.host && f.state === 'accepted' | ||
29 | }) | ||
30 | |||
31 | if (exists === true) { | ||
32 | expect(follow).to.exist | ||
33 | } else { | ||
34 | expect(follow).to.be.undefined | ||
35 | } | ||
36 | } | ||
37 | |||
38 | { | ||
39 | const res = await getFollowingListPaginationAndSort(follower.url, 0, 5, '-createdAt') | ||
40 | const follows = res.body.data as ActorFollow[] | ||
41 | |||
42 | const follow = follows.find(f => { | ||
43 | return f.following.host === following.host && f.state === 'accepted' | ||
44 | }) | ||
45 | |||
46 | if (exists === true) { | ||
47 | expect(follow).to.exist | ||
48 | } else { | ||
49 | expect(follow).to.be.undefined | ||
50 | } | ||
51 | } | ||
52 | } | ||
53 | |||
54 | async function server1Follows2 (servers: ServerInfo[]) { | ||
55 | await follow(servers[0].url, [ servers[1].host ], servers[0].accessToken) | ||
56 | |||
57 | await waitJobs(servers) | ||
58 | } | ||
59 | |||
60 | async function resetFollows (servers: ServerInfo[]) { | ||
61 | try { | ||
62 | await unfollow(servers[ 0 ].url, servers[ 0 ].accessToken, servers[ 1 ]) | ||
63 | await unfollow(servers[ 1 ].url, servers[ 1 ].accessToken, servers[ 0 ]) | ||
64 | } catch { /* empty */ } | ||
65 | |||
66 | await waitJobs(servers) | ||
67 | |||
68 | await checkFollow(servers[0], servers[1], false) | ||
69 | await checkFollow(servers[1], servers[0], false) | ||
70 | } | ||
71 | |||
72 | describe('Test auto follows', function () { | ||
73 | let servers: ServerInfo[] = [] | ||
74 | |||
75 | before(async function () { | ||
76 | this.timeout(30000) | ||
77 | |||
78 | servers = await flushAndRunMultipleServers(3) | ||
79 | |||
80 | // Get the access tokens | ||
81 | await setAccessTokensToServers(servers) | ||
82 | }) | ||
83 | |||
84 | describe('Auto follow back', function () { | ||
85 | |||
86 | it('Should not auto follow back if the option is not enabled', async function () { | ||
87 | this.timeout(15000) | ||
88 | |||
89 | await server1Follows2(servers) | ||
90 | |||
91 | await checkFollow(servers[0], servers[1], true) | ||
92 | await checkFollow(servers[1], servers[0], false) | ||
93 | |||
94 | await resetFollows(servers) | ||
95 | }) | ||
96 | |||
97 | it('Should auto follow back on auto accept if the option is enabled', async function () { | ||
98 | this.timeout(15000) | ||
99 | |||
100 | const config = { | ||
101 | followings: { | ||
102 | instance: { | ||
103 | autoFollowBack: { enabled: true } | ||
104 | } | ||
105 | } | ||
106 | } | ||
107 | await updateCustomSubConfig(servers[1].url, servers[1].accessToken, config) | ||
108 | |||
109 | await server1Follows2(servers) | ||
110 | |||
111 | await checkFollow(servers[0], servers[1], true) | ||
112 | await checkFollow(servers[1], servers[0], true) | ||
113 | |||
114 | await resetFollows(servers) | ||
115 | }) | ||
116 | |||
117 | it('Should wait the acceptation before auto follow back', async function () { | ||
118 | this.timeout(30000) | ||
119 | |||
120 | const config = { | ||
121 | followings: { | ||
122 | instance: { | ||
123 | autoFollowBack: { enabled: true } | ||
124 | } | ||
125 | }, | ||
126 | followers: { | ||
127 | instance: { | ||
128 | manualApproval: true | ||
129 | } | ||
130 | } | ||
131 | } | ||
132 | await updateCustomSubConfig(servers[1].url, servers[1].accessToken, config) | ||
133 | |||
134 | await server1Follows2(servers) | ||
135 | |||
136 | await checkFollow(servers[0], servers[1], false) | ||
137 | await checkFollow(servers[1], servers[0], false) | ||
138 | |||
139 | await acceptFollower(servers[1].url, servers[1].accessToken, 'peertube@' + servers[0].host) | ||
140 | await waitJobs(servers) | ||
141 | |||
142 | await checkFollow(servers[0], servers[1], true) | ||
143 | await checkFollow(servers[1], servers[0], true) | ||
144 | |||
145 | await resetFollows(servers) | ||
146 | |||
147 | config.followings.instance.autoFollowBack.enabled = false | ||
148 | config.followers.instance.manualApproval = false | ||
149 | await updateCustomSubConfig(servers[1].url, servers[1].accessToken, config) | ||
150 | }) | ||
151 | }) | ||
152 | |||
153 | describe('Auto follow index', function () { | ||
154 | const instanceIndexServer = new MockInstancesIndex() | ||
155 | |||
156 | before(async () => { | ||
157 | await instanceIndexServer.initialize() | ||
158 | }) | ||
159 | |||
160 | it('Should not auto follow index if the option is not enabled', async function () { | ||
161 | this.timeout(30000) | ||
162 | |||
163 | await wait(5000) | ||
164 | await waitJobs(servers) | ||
165 | |||
166 | await checkFollow(servers[ 0 ], servers[ 1 ], false) | ||
167 | await checkFollow(servers[ 1 ], servers[ 0 ], false) | ||
168 | }) | ||
169 | |||
170 | it('Should auto follow the index', async function () { | ||
171 | this.timeout(30000) | ||
172 | |||
173 | instanceIndexServer.addInstance(servers[1].host) | ||
174 | |||
175 | const config = { | ||
176 | followings: { | ||
177 | instance: { | ||
178 | autoFollowIndex: { | ||
179 | indexUrl: 'http://localhost:42100', | ||
180 | enabled: true | ||
181 | } | ||
182 | } | ||
183 | } | ||
184 | } | ||
185 | await updateCustomSubConfig(servers[0].url, servers[0].accessToken, config) | ||
186 | |||
187 | await wait(5000) | ||
188 | await waitJobs(servers) | ||
189 | |||
190 | await checkFollow(servers[ 0 ], servers[ 1 ], true) | ||
191 | |||
192 | await resetFollows(servers) | ||
193 | }) | ||
194 | |||
195 | it('Should follow new added instances in the index but not old ones', async function () { | ||
196 | this.timeout(30000) | ||
197 | |||
198 | instanceIndexServer.addInstance(servers[2].host) | ||
199 | |||
200 | await wait(5000) | ||
201 | await waitJobs(servers) | ||
202 | |||
203 | await checkFollow(servers[ 0 ], servers[ 1 ], false) | ||
204 | await checkFollow(servers[ 0 ], servers[ 2 ], true) | ||
205 | }) | ||
206 | }) | ||
207 | |||
208 | after(async function () { | ||
209 | await cleanupTests(servers) | ||
210 | }) | ||
211 | }) | ||
diff --git a/server/tests/api/server/config.ts b/server/tests/api/server/config.ts index 78fdc9cc0..97cc99eea 100644 --- a/server/tests/api/server/config.ts +++ b/server/tests/api/server/config.ts | |||
@@ -28,7 +28,19 @@ function checkInitialConfig (server: ServerInfo, data: CustomConfig) { | |||
28 | 'with WebTorrent and Angular.' | 28 | 'with WebTorrent and Angular.' |
29 | ) | 29 | ) |
30 | expect(data.instance.description).to.equal('Welcome to this PeerTube instance!') | 30 | expect(data.instance.description).to.equal('Welcome to this PeerTube instance!') |
31 | |||
31 | expect(data.instance.terms).to.equal('No terms for now.') | 32 | expect(data.instance.terms).to.equal('No terms for now.') |
33 | expect(data.instance.creationReason).to.be.empty | ||
34 | expect(data.instance.codeOfConduct).to.be.empty | ||
35 | expect(data.instance.moderationInformation).to.be.empty | ||
36 | expect(data.instance.administrator).to.be.empty | ||
37 | expect(data.instance.maintenanceLifetime).to.be.empty | ||
38 | expect(data.instance.businessModel).to.be.empty | ||
39 | expect(data.instance.hardwareInformation).to.be.empty | ||
40 | |||
41 | expect(data.instance.languages).to.have.lengthOf(0) | ||
42 | expect(data.instance.categories).to.have.lengthOf(0) | ||
43 | |||
32 | expect(data.instance.defaultClientRoute).to.equal('/videos/trending') | 44 | expect(data.instance.defaultClientRoute).to.equal('/videos/trending') |
33 | expect(data.instance.isNSFW).to.be.false | 45 | expect(data.instance.isNSFW).to.be.false |
34 | expect(data.instance.defaultNSFWPolicy).to.equal('display') | 46 | expect(data.instance.defaultNSFWPolicy).to.equal('display') |
@@ -68,13 +80,29 @@ function checkInitialConfig (server: ServerInfo, data: CustomConfig) { | |||
68 | 80 | ||
69 | expect(data.followers.instance.enabled).to.be.true | 81 | expect(data.followers.instance.enabled).to.be.true |
70 | expect(data.followers.instance.manualApproval).to.be.false | 82 | expect(data.followers.instance.manualApproval).to.be.false |
83 | |||
84 | expect(data.followings.instance.autoFollowBack.enabled).to.be.false | ||
85 | expect(data.followings.instance.autoFollowIndex.enabled).to.be.false | ||
86 | expect(data.followings.instance.autoFollowIndex.indexUrl).to.equal('https://instances.joinpeertube.org') | ||
71 | } | 87 | } |
72 | 88 | ||
73 | function checkUpdatedConfig (data: CustomConfig) { | 89 | function checkUpdatedConfig (data: CustomConfig) { |
74 | expect(data.instance.name).to.equal('PeerTube updated') | 90 | expect(data.instance.name).to.equal('PeerTube updated') |
75 | expect(data.instance.shortDescription).to.equal('my short description') | 91 | expect(data.instance.shortDescription).to.equal('my short description') |
76 | expect(data.instance.description).to.equal('my super description') | 92 | expect(data.instance.description).to.equal('my super description') |
93 | |||
77 | expect(data.instance.terms).to.equal('my super terms') | 94 | expect(data.instance.terms).to.equal('my super terms') |
95 | expect(data.instance.creationReason).to.equal('my super creation reason') | ||
96 | expect(data.instance.codeOfConduct).to.equal('my super coc') | ||
97 | expect(data.instance.moderationInformation).to.equal('my super moderation information') | ||
98 | expect(data.instance.administrator).to.equal('Kuja') | ||
99 | expect(data.instance.maintenanceLifetime).to.equal('forever') | ||
100 | expect(data.instance.businessModel).to.equal('my super business model') | ||
101 | expect(data.instance.hardwareInformation).to.equal('2vCore 3GB RAM') | ||
102 | |||
103 | expect(data.instance.languages).to.deep.equal([ 'en', 'es' ]) | ||
104 | expect(data.instance.categories).to.deep.equal([ 1, 2 ]) | ||
105 | |||
78 | expect(data.instance.defaultClientRoute).to.equal('/videos/recently-added') | 106 | expect(data.instance.defaultClientRoute).to.equal('/videos/recently-added') |
79 | expect(data.instance.isNSFW).to.be.true | 107 | expect(data.instance.isNSFW).to.be.true |
80 | expect(data.instance.defaultNSFWPolicy).to.equal('blur') | 108 | expect(data.instance.defaultNSFWPolicy).to.equal('blur') |
@@ -119,6 +147,10 @@ function checkUpdatedConfig (data: CustomConfig) { | |||
119 | 147 | ||
120 | expect(data.followers.instance.enabled).to.be.false | 148 | expect(data.followers.instance.enabled).to.be.false |
121 | expect(data.followers.instance.manualApproval).to.be.true | 149 | expect(data.followers.instance.manualApproval).to.be.true |
150 | |||
151 | expect(data.followings.instance.autoFollowBack.enabled).to.be.true | ||
152 | expect(data.followings.instance.autoFollowIndex.enabled).to.be.true | ||
153 | expect(data.followings.instance.autoFollowIndex.indexUrl).to.equal('https://updated.example.com') | ||
122 | } | 154 | } |
123 | 155 | ||
124 | describe('Test config', function () { | 156 | describe('Test config', function () { |
@@ -182,6 +214,18 @@ describe('Test config', function () { | |||
182 | shortDescription: 'my short description', | 214 | shortDescription: 'my short description', |
183 | description: 'my super description', | 215 | description: 'my super description', |
184 | terms: 'my super terms', | 216 | terms: 'my super terms', |
217 | codeOfConduct: 'my super coc', | ||
218 | |||
219 | creationReason: 'my super creation reason', | ||
220 | moderationInformation: 'my super moderation information', | ||
221 | administrator: 'Kuja', | ||
222 | maintenanceLifetime: 'forever', | ||
223 | businessModel: 'my super business model', | ||
224 | hardwareInformation: '2vCore 3GB RAM', | ||
225 | |||
226 | languages: [ 'en', 'es' ], | ||
227 | categories: [ 1, 2 ], | ||
228 | |||
185 | defaultClientRoute: '/videos/recently-added', | 229 | defaultClientRoute: '/videos/recently-added', |
186 | isNSFW: true, | 230 | isNSFW: true, |
187 | defaultNSFWPolicy: 'blur' as 'blur', | 231 | defaultNSFWPolicy: 'blur' as 'blur', |
@@ -261,6 +305,17 @@ describe('Test config', function () { | |||
261 | enabled: false, | 305 | enabled: false, |
262 | manualApproval: true | 306 | manualApproval: true |
263 | } | 307 | } |
308 | }, | ||
309 | followings: { | ||
310 | instance: { | ||
311 | autoFollowBack: { | ||
312 | enabled: true | ||
313 | }, | ||
314 | autoFollowIndex: { | ||
315 | enabled: true, | ||
316 | indexUrl: 'https://updated.example.com' | ||
317 | } | ||
318 | } | ||
264 | } | 319 | } |
265 | } | 320 | } |
266 | await updateCustomConfig(server.url, server.accessToken, newCustomConfig) | 321 | await updateCustomConfig(server.url, server.accessToken, newCustomConfig) |
@@ -310,6 +365,17 @@ describe('Test config', function () { | |||
310 | expect(data.instance.shortDescription).to.equal('my short description') | 365 | expect(data.instance.shortDescription).to.equal('my short description') |
311 | expect(data.instance.description).to.equal('my super description') | 366 | expect(data.instance.description).to.equal('my super description') |
312 | expect(data.instance.terms).to.equal('my super terms') | 367 | expect(data.instance.terms).to.equal('my super terms') |
368 | expect(data.instance.codeOfConduct).to.equal('my super coc') | ||
369 | |||
370 | expect(data.instance.creationReason).to.equal('my super creation reason') | ||
371 | expect(data.instance.moderationInformation).to.equal('my super moderation information') | ||
372 | expect(data.instance.administrator).to.equal('Kuja') | ||
373 | expect(data.instance.maintenanceLifetime).to.equal('forever') | ||
374 | expect(data.instance.businessModel).to.equal('my super business model') | ||
375 | expect(data.instance.hardwareInformation).to.equal('2vCore 3GB RAM') | ||
376 | |||
377 | expect(data.instance.languages).to.deep.equal([ 'en', 'es' ]) | ||
378 | expect(data.instance.categories).to.deep.equal([ 1, 2 ]) | ||
313 | }) | 379 | }) |
314 | 380 | ||
315 | it('Should remove the custom configuration', async function () { | 381 | it('Should remove the custom configuration', async function () { |
diff --git a/server/tests/api/server/index.ts b/server/tests/api/server/index.ts index 3daeeb49a..08205b2c8 100644 --- a/server/tests/api/server/index.ts +++ b/server/tests/api/server/index.ts | |||
@@ -1,3 +1,4 @@ | |||
1 | import './auto-follows' | ||
1 | import './config' | 2 | import './config' |
2 | import './contact-form' | 3 | import './contact-form' |
3 | import './email' | 4 | import './email' |
diff --git a/server/tests/api/users/users.ts b/server/tests/api/users/users.ts index 3a3fabb4c..95b1bb626 100644 --- a/server/tests/api/users/users.ts +++ b/server/tests/api/users/users.ts | |||
@@ -442,7 +442,7 @@ describe('Test users', function () { | |||
442 | url: server.url, | 442 | url: server.url, |
443 | accessToken: accessTokenUser, | 443 | accessToken: accessTokenUser, |
444 | currentPassword: 'super password', | 444 | currentPassword: 'super password', |
445 | newPassword: 'new password' | 445 | password: 'new password' |
446 | }) | 446 | }) |
447 | user.password = 'new password' | 447 | user.password = 'new password' |
448 | 448 | ||
@@ -543,7 +543,7 @@ describe('Test users', function () { | |||
543 | }) | 543 | }) |
544 | 544 | ||
545 | const res = await getMyUserInformation(server.url, accessTokenUser) | 545 | const res = await getMyUserInformation(server.url, accessTokenUser) |
546 | const user = res.body | 546 | const user: User = res.body |
547 | 547 | ||
548 | expect(user.username).to.equal('user_1') | 548 | expect(user.username).to.equal('user_1') |
549 | expect(user.email).to.equal('updated@example.com') | 549 | expect(user.email).to.equal('updated@example.com') |
@@ -552,6 +552,8 @@ describe('Test users', function () { | |||
552 | expect(user.id).to.be.a('number') | 552 | expect(user.id).to.be.a('number') |
553 | expect(user.account.displayName).to.equal('new display name') | 553 | expect(user.account.displayName).to.equal('new display name') |
554 | expect(user.account.description).to.equal('my super description updated') | 554 | expect(user.account.description).to.equal('my super description updated') |
555 | expect(user.noWelcomeModal).to.be.false | ||
556 | expect(user.noInstanceConfigWarningModal).to.be.false | ||
555 | }) | 557 | }) |
556 | 558 | ||
557 | it('Should be able to update my theme', async function () { | 559 | it('Should be able to update my theme', async function () { |
@@ -568,6 +570,21 @@ describe('Test users', function () { | |||
568 | expect(body.theme).to.equal(theme) | 570 | expect(body.theme).to.equal(theme) |
569 | } | 571 | } |
570 | }) | 572 | }) |
573 | |||
574 | it('Should be able to update my modal preferences', async function () { | ||
575 | await updateMyUser({ | ||
576 | url: server.url, | ||
577 | accessToken: accessTokenUser, | ||
578 | noInstanceConfigWarningModal: true, | ||
579 | noWelcomeModal: true | ||
580 | }) | ||
581 | |||
582 | const res = await getMyUserInformation(server.url, accessTokenUser) | ||
583 | const user: User = res.body | ||
584 | |||
585 | expect(user.noWelcomeModal).to.be.true | ||
586 | expect(user.noInstanceConfigWarningModal).to.be.true | ||
587 | }) | ||
571 | }) | 588 | }) |
572 | 589 | ||
573 | describe('Updating another user', function () { | 590 | describe('Updating another user', function () { |
diff --git a/server/tests/api/videos/video-abuse.ts b/server/tests/api/videos/video-abuse.ts index a2f3ee161..0cd6f22c7 100644 --- a/server/tests/api/videos/video-abuse.ts +++ b/server/tests/api/videos/video-abuse.ts | |||
@@ -17,6 +17,12 @@ import { | |||
17 | } from '../../../../shared/extra-utils/index' | 17 | } from '../../../../shared/extra-utils/index' |
18 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' | 18 | import { doubleFollow } from '../../../../shared/extra-utils/server/follows' |
19 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' | 19 | import { waitJobs } from '../../../../shared/extra-utils/server/jobs' |
20 | import { | ||
21 | addAccountToServerBlocklist, | ||
22 | addServerToServerBlocklist, | ||
23 | removeAccountFromServerBlocklist, | ||
24 | removeServerFromServerBlocklist | ||
25 | } from '../../../../shared/extra-utils/users/blocklist' | ||
20 | 26 | ||
21 | const expect = chai.expect | 27 | const expect = chai.expect |
22 | 28 | ||
@@ -163,13 +169,76 @@ describe('Test video abuses', function () { | |||
163 | expect(res.body.data[0].moderationComment).to.equal('It is valid') | 169 | expect(res.body.data[0].moderationComment).to.equal('It is valid') |
164 | }) | 170 | }) |
165 | 171 | ||
172 | it('Should hide video abuses from blocked accounts', async function () { | ||
173 | this.timeout(10000) | ||
174 | |||
175 | { | ||
176 | await reportVideoAbuse(servers[1].url, servers[1].accessToken, servers[0].video.uuid, 'will mute this') | ||
177 | await waitJobs(servers) | ||
178 | |||
179 | const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken) | ||
180 | expect(res.body.total).to.equal(3) | ||
181 | } | ||
182 | |||
183 | const accountToBlock = 'root@localhost:' + servers[1].port | ||
184 | |||
185 | { | ||
186 | await addAccountToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, accountToBlock) | ||
187 | |||
188 | const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) | ||
189 | expect(res.body.total).to.equal(2) | ||
190 | |||
191 | const abuse = res.body.data.find(a => a.reason === 'will mute this') | ||
192 | expect(abuse).to.be.undefined | ||
193 | } | ||
194 | |||
195 | { | ||
196 | await removeAccountFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, accountToBlock) | ||
197 | |||
198 | const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) | ||
199 | expect(res.body.total).to.equal(3) | ||
200 | } | ||
201 | }) | ||
202 | |||
203 | it('Should hide video abuses from blocked servers', async function () { | ||
204 | const serverToBlock = servers[1].host | ||
205 | |||
206 | { | ||
207 | await addServerToServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, servers[1].host) | ||
208 | |||
209 | const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) | ||
210 | expect(res.body.total).to.equal(2) | ||
211 | |||
212 | const abuse = res.body.data.find(a => a.reason === 'will mute this') | ||
213 | expect(abuse).to.be.undefined | ||
214 | } | ||
215 | |||
216 | { | ||
217 | await removeServerFromServerBlocklist(servers[ 0 ].url, servers[ 0 ].accessToken, serverToBlock) | ||
218 | |||
219 | const res = await getVideoAbusesList(servers[ 0 ].url, servers[ 0 ].accessToken) | ||
220 | expect(res.body.total).to.equal(3) | ||
221 | } | ||
222 | }) | ||
223 | |||
166 | it('Should delete the video abuse', async function () { | 224 | it('Should delete the video abuse', async function () { |
225 | this.timeout(10000) | ||
226 | |||
167 | await deleteVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id) | 227 | await deleteVideoAbuse(servers[1].url, servers[1].accessToken, abuseServer2.video.uuid, abuseServer2.id) |
168 | 228 | ||
169 | const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken) | 229 | await waitJobs(servers) |
170 | expect(res.body.total).to.equal(0) | 230 | |
171 | expect(res.body.data).to.be.an('array') | 231 | { |
172 | expect(res.body.data.length).to.equal(0) | 232 | const res = await getVideoAbusesList(servers[1].url, servers[1].accessToken) |
233 | expect(res.body.total).to.equal(1) | ||
234 | expect(res.body.data.length).to.equal(1) | ||
235 | expect(res.body.data[0].id).to.not.equal(abuseServer2.id) | ||
236 | } | ||
237 | |||
238 | { | ||
239 | const res = await getVideoAbusesList(servers[0].url, servers[0].accessToken) | ||
240 | expect(res.body.total).to.equal(3) | ||
241 | } | ||
173 | }) | 242 | }) |
174 | 243 | ||
175 | after(async function () { | 244 | after(async function () { |
diff --git a/server/tests/api/videos/video-change-ownership.ts b/server/tests/api/videos/video-change-ownership.ts index 3a3add71b..64ee2355a 100644 --- a/server/tests/api/videos/video-change-ownership.ts +++ b/server/tests/api/videos/video-change-ownership.ts | |||
@@ -191,7 +191,7 @@ describe('Test video change ownership - nominal', function () { | |||
191 | await waitJobs(servers) | 191 | await waitJobs(servers) |
192 | }) | 192 | }) |
193 | 193 | ||
194 | it('Should have video channel updated', async function () { | 194 | it('Should have the channel of the video updated', async function () { |
195 | for (const server of servers) { | 195 | for (const server of servers) { |
196 | const res = await getVideo(server.url, servers[0].video.uuid) | 196 | const res = await getVideo(server.url, servers[0].video.uuid) |
197 | 197 | ||
diff --git a/server/tools/cli.ts b/server/tools/cli.ts index 8599a270f..58e2445ac 100644 --- a/server/tools/cli.ts +++ b/server/tools/cli.ts | |||
@@ -5,6 +5,7 @@ import { root } from '../../shared/extra-utils/miscs/miscs' | |||
5 | import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels' | 5 | import { getVideoChannel } from '../../shared/extra-utils/videos/video-channels' |
6 | import { Command } from 'commander' | 6 | import { Command } from 'commander' |
7 | import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' | 7 | import { VideoChannel, VideoPrivacy } from '../../shared/models/videos' |
8 | import { createLogger, format, transports } from 'winston' | ||
8 | 9 | ||
9 | let configName = 'PeerTube/CLI' | 10 | let configName = 'PeerTube/CLI' |
10 | if (isTestInstance()) configName += `-${getAppNumber()}` | 11 | if (isTestInstance()) configName += `-${getAppNumber()}` |
@@ -119,6 +120,7 @@ function buildCommonVideoOptions (command: Command) { | |||
119 | .option('-m, --comments-enabled', 'Enable comments') | 120 | .option('-m, --comments-enabled', 'Enable comments') |
120 | .option('-s, --support <support>', 'Video support text') | 121 | .option('-s, --support <support>', 'Video support text') |
121 | .option('-w, --wait-transcoding', 'Wait transcoding before publishing the video') | 122 | .option('-w, --wait-transcoding', 'Wait transcoding before publishing the video') |
123 | .option('-v, --verbose <verbose>', 'Verbosity, from 0/\'error\' to 4/\'debug\'', 'info') | ||
122 | } | 124 | } |
123 | 125 | ||
124 | async function buildVideoAttributesFromCommander (url: string, command: Command, defaultAttributes: any = {}) { | 126 | async function buildVideoAttributesFromCommander (url: string, command: Command, defaultAttributes: any = {}) { |
@@ -175,11 +177,42 @@ function getServerCredentials (program: any) { | |||
175 | }) | 177 | }) |
176 | } | 178 | } |
177 | 179 | ||
180 | function getLogger (logLevel = 'info') { | ||
181 | const logLevels = { | ||
182 | 0: 0, | ||
183 | error: 0, | ||
184 | 1: 1, | ||
185 | warn: 1, | ||
186 | 2: 2, | ||
187 | info: 2, | ||
188 | 3: 3, | ||
189 | verbose: 3, | ||
190 | 4: 4, | ||
191 | debug: 4 | ||
192 | } | ||
193 | |||
194 | const logger = createLogger({ | ||
195 | levels: logLevels, | ||
196 | format: format.combine( | ||
197 | format.splat(), | ||
198 | format.simple() | ||
199 | ), | ||
200 | transports: [ | ||
201 | new (transports.Console)({ | ||
202 | level: logLevel | ||
203 | }) | ||
204 | ] | ||
205 | }) | ||
206 | |||
207 | return logger | ||
208 | } | ||
209 | |||
178 | // --------------------------------------------------------------------------- | 210 | // --------------------------------------------------------------------------- |
179 | 211 | ||
180 | export { | 212 | export { |
181 | version, | 213 | version, |
182 | config, | 214 | config, |
215 | getLogger, | ||
183 | getSettings, | 216 | getSettings, |
184 | getNetrc, | 217 | getNetrc, |
185 | getRemoteObjectOrDie, | 218 | getRemoteObjectOrDie, |
diff --git a/server/tools/peertube-import-videos.ts b/server/tools/peertube-import-videos.ts index 0ebfa7442..fcb90cca3 100644 --- a/server/tools/peertube-import-videos.ts +++ b/server/tools/peertube-import-videos.ts | |||
@@ -8,10 +8,11 @@ import { CONSTRAINTS_FIELDS } from '../initializers/constants' | |||
8 | import { getClient, getVideoCategories, login, searchVideoWithSort, uploadVideo } from '../../shared/extra-utils/index' | 8 | import { getClient, getVideoCategories, login, searchVideoWithSort, uploadVideo } from '../../shared/extra-utils/index' |
9 | import { truncate } from 'lodash' | 9 | import { truncate } from 'lodash' |
10 | import * as prompt from 'prompt' | 10 | import * as prompt from 'prompt' |
11 | import { accessSync, constants } from 'fs' | ||
11 | import { remove } from 'fs-extra' | 12 | import { remove } from 'fs-extra' |
12 | import { sha256 } from '../helpers/core-utils' | 13 | import { sha256 } from '../helpers/core-utils' |
13 | import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' | 14 | import { buildOriginallyPublishedAt, safeGetYoutubeDL } from '../helpers/youtube-dl' |
14 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials } from './cli' | 15 | import { buildCommonVideoOptions, buildVideoAttributesFromCommander, getServerCredentials, getLogger } from './cli' |
15 | 16 | ||
16 | type UserInfo = { | 17 | type UserInfo = { |
17 | username: string | 18 | username: string |
@@ -19,7 +20,6 @@ type UserInfo = { | |||
19 | } | 20 | } |
20 | 21 | ||
21 | const processOptions = { | 22 | const processOptions = { |
22 | cwd: __dirname, | ||
23 | maxBuffer: Infinity | 23 | maxBuffer: Infinity |
24 | } | 24 | } |
25 | 25 | ||
@@ -35,15 +35,23 @@ command | |||
35 | .option('--target-url <targetUrl>', 'Video target URL') | 35 | .option('--target-url <targetUrl>', 'Video target URL') |
36 | .option('--since <since>', 'Publication date (inclusive) since which the videos can be imported (YYYY-MM-DD)', parseDate) | 36 | .option('--since <since>', 'Publication date (inclusive) since which the videos can be imported (YYYY-MM-DD)', parseDate) |
37 | .option('--until <until>', 'Publication date (inclusive) until which the videos can be imported (YYYY-MM-DD)', parseDate) | 37 | .option('--until <until>', 'Publication date (inclusive) until which the videos can be imported (YYYY-MM-DD)', parseDate) |
38 | .option('-v, --verbose', 'Verbose mode') | 38 | .option('--first <first>', 'Process first n elements of returned playlist') |
39 | .option('--last <last>', 'Process last n elements of returned playlist') | ||
40 | .option('-T, --tmpdir <tmpdir>', 'Working directory', __dirname) | ||
39 | .parse(process.argv) | 41 | .parse(process.argv) |
40 | 42 | ||
43 | let log = getLogger(program[ 'verbose' ]) | ||
44 | |||
41 | getServerCredentials(command) | 45 | getServerCredentials(command) |
42 | .then(({ url, username, password }) => { | 46 | .then(({ url, username, password }) => { |
43 | if (!program[ 'targetUrl' ]) { | 47 | if (!program[ 'targetUrl' ]) { |
44 | console.error('--targetUrl field is required.') | 48 | exitError('--target-url field is required.') |
49 | } | ||
45 | 50 | ||
46 | process.exit(-1) | 51 | try { |
52 | accessSync(program[ 'tmpdir' ], constants.R_OK | constants.W_OK) | ||
53 | } catch (e) { | ||
54 | exitError('--tmpdir %s: directory does not exist or is not accessible', program[ 'tmpdir' ]) | ||
47 | } | 55 | } |
48 | 56 | ||
49 | removeEndSlashes(url) | 57 | removeEndSlashes(url) |
@@ -53,8 +61,7 @@ getServerCredentials(command) | |||
53 | 61 | ||
54 | run(url, user) | 62 | run(url, user) |
55 | .catch(err => { | 63 | .catch(err => { |
56 | console.error(err) | 64 | exitError(err) |
57 | process.exit(-1) | ||
58 | }) | 65 | }) |
59 | }) | 66 | }) |
60 | 67 | ||
@@ -68,30 +75,32 @@ async function run (url: string, user: UserInfo) { | |||
68 | const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] | 75 | const options = [ '-j', '--flat-playlist', '--playlist-reverse' ] |
69 | youtubeDL.getInfo(program[ 'targetUrl' ], options, processOptions, async (err, info) => { | 76 | youtubeDL.getInfo(program[ 'targetUrl' ], options, processOptions, async (err, info) => { |
70 | if (err) { | 77 | if (err) { |
71 | console.log(err.message) | 78 | exitError(err.message) |
72 | process.exit(1) | ||
73 | } | 79 | } |
74 | 80 | ||
75 | let infoArray: any[] | 81 | let infoArray: any[] |
76 | 82 | ||
77 | // Normalize utf8 fields | 83 | // Normalize utf8 fields |
78 | if (Array.isArray(info) === true) { | 84 | infoArray = [].concat(info); |
79 | infoArray = info.map(i => normalizeObject(i)) | 85 | if (program[ 'first' ]) { |
80 | } else { | 86 | infoArray = infoArray.slice(0, program[ 'first' ]) |
81 | infoArray = [ normalizeObject(info) ] | 87 | } else if (program[ 'last' ]) { |
88 | infoArray = infoArray.slice(- program[ 'last' ]) | ||
82 | } | 89 | } |
83 | console.log('Will download and upload %d videos.\n', infoArray.length) | 90 | infoArray = infoArray.map(i => normalizeObject(i)) |
91 | |||
92 | log.info('Will download and upload %d videos.\n', infoArray.length) | ||
84 | 93 | ||
85 | for (const info of infoArray) { | 94 | for (const info of infoArray) { |
86 | await processVideo({ | 95 | await processVideo({ |
87 | cwd: processOptions.cwd, | 96 | cwd: program[ 'tmpdir' ], |
88 | url, | 97 | url, |
89 | user, | 98 | user, |
90 | youtubeInfo: info | 99 | youtubeInfo: info |
91 | }) | 100 | }) |
92 | } | 101 | } |
93 | 102 | ||
94 | console.log('Video/s for user %s imported: %s', program[ 'username' ], program[ 'targetUrl' ]) | 103 | log.info('Video/s for user %s imported: %s', user.username, program[ 'targetUrl' ]) |
95 | process.exit(0) | 104 | process.exit(0) |
96 | }) | 105 | }) |
97 | } | 106 | } |
@@ -105,21 +114,21 @@ function processVideo (parameters: { | |||
105 | const { youtubeInfo, cwd, url, user } = parameters | 114 | const { youtubeInfo, cwd, url, user } = parameters |
106 | 115 | ||
107 | return new Promise(async res => { | 116 | return new Promise(async res => { |
108 | if (program[ 'verbose' ]) console.log('Fetching object.', youtubeInfo) | 117 | log.debug('Fetching object.', youtubeInfo) |
109 | 118 | ||
110 | const videoInfo = await fetchObject(youtubeInfo) | 119 | const videoInfo = await fetchObject(youtubeInfo) |
111 | if (program[ 'verbose' ]) console.log('Fetched object.', videoInfo) | 120 | log.debug('Fetched object.', videoInfo) |
112 | 121 | ||
113 | if (program[ 'since' ]) { | 122 | if (program[ 'since' ]) { |
114 | if (buildOriginallyPublishedAt(videoInfo).getTime() < program[ 'since' ].getTime()) { | 123 | if (buildOriginallyPublishedAt(videoInfo).getTime() < program[ 'since' ].getTime()) { |
115 | console.log('Video "%s" has been published before "%s", don\'t upload it.\n', | 124 | log.info('Video "%s" has been published before "%s", don\'t upload it.\n', |
116 | videoInfo.title, formatDate(program[ 'since' ])); | 125 | videoInfo.title, formatDate(program[ 'since' ])); |
117 | return res(); | 126 | return res(); |
118 | } | 127 | } |
119 | } | 128 | } |
120 | if (program[ 'until' ]) { | 129 | if (program[ 'until' ]) { |
121 | if (buildOriginallyPublishedAt(videoInfo).getTime() > program[ 'until' ].getTime()) { | 130 | if (buildOriginallyPublishedAt(videoInfo).getTime() > program[ 'until' ].getTime()) { |
122 | console.log('Video "%s" has been published after "%s", don\'t upload it.\n', | 131 | log.info('Video "%s" has been published after "%s", don\'t upload it.\n', |
123 | videoInfo.title, formatDate(program[ 'until' ])); | 132 | videoInfo.title, formatDate(program[ 'until' ])); |
124 | return res(); | 133 | return res(); |
125 | } | 134 | } |
@@ -127,27 +136,27 @@ function processVideo (parameters: { | |||
127 | 136 | ||
128 | const result = await searchVideoWithSort(url, videoInfo.title, '-match') | 137 | const result = await searchVideoWithSort(url, videoInfo.title, '-match') |
129 | 138 | ||
130 | console.log('############################################################\n') | 139 | log.info('############################################################\n') |
131 | 140 | ||
132 | if (result.body.data.find(v => v.name === videoInfo.title)) { | 141 | if (result.body.data.find(v => v.name === videoInfo.title)) { |
133 | console.log('Video "%s" already exists, don\'t reupload it.\n', videoInfo.title) | 142 | log.info('Video "%s" already exists, don\'t reupload it.\n', videoInfo.title) |
134 | return res() | 143 | return res() |
135 | } | 144 | } |
136 | 145 | ||
137 | const path = join(cwd, sha256(videoInfo.url) + '.mp4') | 146 | const path = join(cwd, sha256(videoInfo.url) + '.mp4') |
138 | 147 | ||
139 | console.log('Downloading video "%s"...', videoInfo.title) | 148 | log.info('Downloading video "%s"...', videoInfo.title) |
140 | 149 | ||
141 | const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ] | 150 | const options = [ '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best', '-o', path ] |
142 | try { | 151 | try { |
143 | const youtubeDL = await safeGetYoutubeDL() | 152 | const youtubeDL = await safeGetYoutubeDL() |
144 | youtubeDL.exec(videoInfo.url, options, processOptions, async (err, output) => { | 153 | youtubeDL.exec(videoInfo.url, options, processOptions, async (err, output) => { |
145 | if (err) { | 154 | if (err) { |
146 | console.error(err) | 155 | log.error(err) |
147 | return res() | 156 | return res() |
148 | } | 157 | } |
149 | 158 | ||
150 | console.log(output.join('\n')) | 159 | log.info(output.join('\n')) |
151 | await uploadVideoOnPeerTube({ | 160 | await uploadVideoOnPeerTube({ |
152 | cwd, | 161 | cwd, |
153 | url, | 162 | url, |
@@ -158,7 +167,7 @@ function processVideo (parameters: { | |||
158 | return res() | 167 | return res() |
159 | }) | 168 | }) |
160 | } catch (err) { | 169 | } catch (err) { |
161 | console.log(err.message) | 170 | log.error(err.message) |
162 | return res() | 171 | return res() |
163 | } | 172 | } |
164 | }) | 173 | }) |
@@ -217,7 +226,7 @@ async function uploadVideoOnPeerTube (parameters: { | |||
217 | fixture: videoPath | 226 | fixture: videoPath |
218 | }) | 227 | }) |
219 | 228 | ||
220 | console.log('\nUploading on PeerTube video "%s".', videoAttributes.name) | 229 | log.info('\nUploading on PeerTube video "%s".', videoAttributes.name) |
221 | 230 | ||
222 | let accessToken = await getAccessTokenOrDie(url, user) | 231 | let accessToken = await getAccessTokenOrDie(url, user) |
223 | 232 | ||
@@ -225,21 +234,20 @@ async function uploadVideoOnPeerTube (parameters: { | |||
225 | await uploadVideo(url, accessToken, videoAttributes) | 234 | await uploadVideo(url, accessToken, videoAttributes) |
226 | } catch (err) { | 235 | } catch (err) { |
227 | if (err.message.indexOf('401') !== -1) { | 236 | if (err.message.indexOf('401') !== -1) { |
228 | console.log('Got 401 Unauthorized, token may have expired, renewing token and retry.') | 237 | log.info('Got 401 Unauthorized, token may have expired, renewing token and retry.') |
229 | 238 | ||
230 | accessToken = await getAccessTokenOrDie(url, user) | 239 | accessToken = await getAccessTokenOrDie(url, user) |
231 | 240 | ||
232 | await uploadVideo(url, accessToken, videoAttributes) | 241 | await uploadVideo(url, accessToken, videoAttributes) |
233 | } else { | 242 | } else { |
234 | console.log(err.message) | 243 | exitError(err.message) |
235 | process.exit(1) | ||
236 | } | 244 | } |
237 | } | 245 | } |
238 | 246 | ||
239 | await remove(videoPath) | 247 | await remove(videoPath) |
240 | if (thumbnailfile) await remove(thumbnailfile) | 248 | if (thumbnailfile) await remove(thumbnailfile) |
241 | 249 | ||
242 | console.log('Uploaded video "%s"!\n', videoAttributes.name) | 250 | log.warn('Uploaded video "%s"!\n', videoAttributes.name) |
243 | } | 251 | } |
244 | 252 | ||
245 | /* ---------------------------------------------------------- */ | 253 | /* ---------------------------------------------------------- */ |
@@ -355,20 +363,17 @@ async function getAccessTokenOrDie (url: string, user: UserInfo) { | |||
355 | const res = await login(url, client, user) | 363 | const res = await login(url, client, user) |
356 | return res.body.access_token | 364 | return res.body.access_token |
357 | } catch (err) { | 365 | } catch (err) { |
358 | console.error('Cannot authenticate. Please check your username/password.') | 366 | exitError('Cannot authenticate. Please check your username/password.') |
359 | process.exit(-1) | ||
360 | } | 367 | } |
361 | } | 368 | } |
362 | 369 | ||
363 | function parseDate (dateAsStr: string): Date { | 370 | function parseDate (dateAsStr: string): Date { |
364 | if (!/\d{4}-\d{2}-\d{2}/.test(dateAsStr)) { | 371 | if (!/\d{4}-\d{2}-\d{2}/.test(dateAsStr)) { |
365 | console.error(`Invalid date passed: ${dateAsStr}. Expected format: YYYY-MM-DD. See help for usage.`); | 372 | exitError(`Invalid date passed: ${dateAsStr}. Expected format: YYYY-MM-DD. See help for usage.`); |
366 | process.exit(-1); | ||
367 | } | 373 | } |
368 | const date = new Date(dateAsStr); | 374 | const date = new Date(dateAsStr); |
369 | if (isNaN(date.getTime())) { | 375 | if (isNaN(date.getTime())) { |
370 | console.error(`Invalid date passed: ${dateAsStr}. See help for usage.`); | 376 | exitError(`Invalid date passed: ${dateAsStr}. See help for usage.`); |
371 | process.exit(-1); | ||
372 | } | 377 | } |
373 | return date; | 378 | return date; |
374 | } | 379 | } |
@@ -376,3 +381,9 @@ function parseDate (dateAsStr: string): Date { | |||
376 | function formatDate (date: Date): string { | 381 | function formatDate (date: Date): string { |
377 | return date.toISOString().split('T')[0]; | 382 | return date.toISOString().split('T')[0]; |
378 | } | 383 | } |
384 | |||
385 | function exitError (message:string, ...meta: any[]) { | ||
386 | // use console.error instead of log.error here | ||
387 | console.error(message, ...meta) | ||
388 | process.exit(-1) | ||
389 | } | ||
diff --git a/server/typings/activitypub-processor.model.ts b/server/typings/activitypub-processor.model.ts index 37b2859de..7ed3a65b1 100644 --- a/server/typings/activitypub-processor.model.ts +++ b/server/typings/activitypub-processor.model.ts | |||
@@ -1,10 +1,9 @@ | |||
1 | import { Activity } from '../../shared/models/activitypub' | 1 | import { Activity } from '../../shared/models/activitypub' |
2 | import { ActorModel } from '../models/activitypub/actor' | 2 | import { MActorDefault, MActorSignature } from './models' |
3 | import { SignatureActorModel } from './models' | ||
4 | 3 | ||
5 | export type APProcessorOptions<T extends Activity> = { | 4 | export type APProcessorOptions<T extends Activity> = { |
6 | activity: T | 5 | activity: T |
7 | byActor: SignatureActorModel | 6 | byActor: MActorSignature |
8 | inboxActor?: ActorModel | 7 | inboxActor?: MActorDefault |
9 | fromFetch?: boolean | 8 | fromFetch?: boolean |
10 | } | 9 | } |
diff --git a/server/typings/express.ts b/server/typings/express.ts index f7da55ab0..3cc7c7632 100644 --- a/server/typings/express.ts +++ b/server/typings/express.ts | |||
@@ -1,89 +1,103 @@ | |||
1 | import { VideoChannelModel } from '../models/video/video-channel' | ||
2 | import { VideoPlaylistModel } from '../models/video/video-playlist' | ||
3 | import { VideoPlaylistElementModel } from '../models/video/video-playlist-element' | ||
4 | import { UserModel } from '../models/account/user' | ||
5 | import { VideoModel } from '../models/video/video' | ||
6 | import { AccountModel } from '../models/account/account' | ||
7 | import { VideoChangeOwnershipModel } from '../models/video/video-change-ownership' | ||
8 | import { ActorModel } from '../models/activitypub/actor' | ||
9 | import { VideoCommentModel } from '../models/video/video-comment' | ||
10 | import { VideoShareModel } from '../models/video/video-share' | ||
11 | import { AccountVideoRateModel } from '../models/account/account-video-rate' | ||
12 | import { ActorFollowModel } from '../models/activitypub/actor-follow' | ||
13 | import { ServerModel } from '../models/server/server' | ||
14 | import { VideoFileModel } from '../models/video/video-file' | ||
15 | import { VideoRedundancyModel } from '../models/redundancy/video-redundancy' | ||
16 | import { ServerBlocklistModel } from '../models/server/server-blocklist' | ||
17 | import { AccountBlocklistModel } from '../models/account/account-blocklist' | ||
18 | import { VideoImportModel } from '../models/video/video-import' | ||
19 | import { VideoAbuseModel } from '../models/video/video-abuse' | ||
20 | import { VideoBlacklistModel } from '../models/video/video-blacklist' | ||
21 | import { VideoCaptionModel } from '../models/video/video-caption' | ||
22 | import { VideoStreamingPlaylistModel } from '../models/video/video-streaming-playlist' | ||
23 | import { RegisteredPlugin } from '../lib/plugins/plugin-manager' | 1 | import { RegisteredPlugin } from '../lib/plugins/plugin-manager' |
24 | import { PluginModel } from '../models/server/plugin' | 2 | import { |
25 | import { SignatureActorModel } from './models' | 3 | MAccountDefault, |
4 | MActorAccountChannelId, | ||
5 | MActorFollowActorsDefault, | ||
6 | MActorFollowActorsDefaultSubscription, | ||
7 | MActorFull, | ||
8 | MChannelAccountDefault, | ||
9 | MComment, | ||
10 | MCommentOwnerVideoReply, | ||
11 | MUserDefault, | ||
12 | MVideoAbuse, | ||
13 | MVideoBlacklist, | ||
14 | MVideoCaptionVideo, | ||
15 | MVideoFullLight, | ||
16 | MVideoIdThumbnail, | ||
17 | MVideoRedundancyVideo, | ||
18 | MVideoShareActor, | ||
19 | MVideoThumbnail, | ||
20 | MVideoWithRights | ||
21 | } from './models' | ||
22 | import { MVideoPlaylistFull, MVideoPlaylistFullSummary } from './models/video/video-playlist' | ||
23 | import { MVideoImportDefault } from '@server/typings/models/video/video-import' | ||
24 | import { MAccountBlocklist, MStreamingPlaylist, MVideoFile } from '@server/typings/models' | ||
25 | import { MVideoPlaylistElement, MVideoPlaylistElementVideoUrlPlaylistPrivacy } from '@server/typings/models/video/video-playlist-element' | ||
26 | import { MAccountVideoRateAccountVideo } from '@server/typings/models/video/video-rate' | ||
27 | import { MVideoChangeOwnershipFull } from './models/video/video-change-ownership' | ||
28 | import { MPlugin, MServer } from '@server/typings/models/server' | ||
29 | import { MServerBlocklist } from './models/server/server-blocklist' | ||
30 | import { MOAuthTokenUser } from '@server/typings/models/oauth/oauth-token' | ||
26 | 31 | ||
27 | declare module 'express' { | 32 | declare module 'express' { |
28 | 33 | ||
29 | interface Response { | 34 | interface Response { |
35 | |||
30 | locals: { | 36 | locals: { |
31 | video?: VideoModel | 37 | videoAll?: MVideoFullLight |
32 | videoShare?: VideoShareModel | 38 | onlyVideo?: MVideoThumbnail |
33 | videoFile?: VideoFileModel | 39 | onlyVideoWithRights?: MVideoWithRights |
40 | videoId?: MVideoIdThumbnail | ||
41 | |||
42 | videoShare?: MVideoShareActor | ||
43 | |||
44 | videoFile?: MVideoFile | ||
45 | |||
46 | videoImport?: MVideoImportDefault | ||
47 | |||
48 | videoBlacklist?: MVideoBlacklist | ||
49 | |||
50 | videoCaption?: MVideoCaptionVideo | ||
51 | |||
52 | videoAbuse?: MVideoAbuse | ||
34 | 53 | ||
35 | videoImport?: VideoImportModel | 54 | videoStreamingPlaylist?: MStreamingPlaylist |
36 | 55 | ||
37 | videoBlacklist?: VideoBlacklistModel | 56 | videoChannel?: MChannelAccountDefault |
38 | 57 | ||
39 | videoCaption?: VideoCaptionModel | 58 | videoPlaylistFull?: MVideoPlaylistFull |
59 | videoPlaylistSummary?: MVideoPlaylistFullSummary | ||
40 | 60 | ||
41 | videoAbuse?: VideoAbuseModel | 61 | videoPlaylistElement?: MVideoPlaylistElement |
62 | videoPlaylistElementAP?: MVideoPlaylistElementVideoUrlPlaylistPrivacy | ||
42 | 63 | ||
43 | videoStreamingPlaylist?: VideoStreamingPlaylistModel | 64 | accountVideoRate?: MAccountVideoRateAccountVideo |
44 | 65 | ||
45 | videoChannel?: VideoChannelModel | 66 | videoCommentFull?: MCommentOwnerVideoReply |
67 | videoCommentThread?: MComment | ||
46 | 68 | ||
47 | videoPlaylist?: VideoPlaylistModel | 69 | follow?: MActorFollowActorsDefault |
48 | videoPlaylistElement?: VideoPlaylistElementModel | 70 | subscription?: MActorFollowActorsDefaultSubscription |
49 | 71 | ||
50 | accountVideoRate?: AccountVideoRateModel | 72 | nextOwner?: MAccountDefault |
73 | videoChangeOwnership?: MVideoChangeOwnershipFull | ||
51 | 74 | ||
52 | videoComment?: VideoCommentModel | 75 | account?: MAccountDefault |
53 | videoCommentThread?: VideoCommentModel | ||
54 | 76 | ||
55 | follow?: ActorFollowModel | 77 | actorFull?: MActorFull |
56 | subscription?: ActorFollowModel | ||
57 | 78 | ||
58 | nextOwner?: AccountModel | 79 | user?: MUserDefault |
59 | videoChangeOwnership?: VideoChangeOwnershipModel | ||
60 | account?: AccountModel | ||
61 | actor?: ActorModel | ||
62 | user?: UserModel | ||
63 | 80 | ||
64 | server?: ServerModel | 81 | server?: MServer |
65 | 82 | ||
66 | videoRedundancy?: VideoRedundancyModel | 83 | videoRedundancy?: MVideoRedundancyVideo |
67 | 84 | ||
68 | accountBlock?: AccountBlocklistModel | 85 | accountBlock?: MAccountBlocklist |
69 | serverBlock?: ServerBlocklistModel | 86 | serverBlock?: MServerBlocklist |
70 | 87 | ||
71 | oauth?: { | 88 | oauth?: { |
72 | token: { | 89 | token: MOAuthTokenUser |
73 | User: UserModel | ||
74 | user: UserModel | ||
75 | } | ||
76 | } | 90 | } |
77 | 91 | ||
78 | signature?: { | 92 | signature?: { |
79 | actor: SignatureActorModel | 93 | actor: MActorAccountChannelId |
80 | } | 94 | } |
81 | 95 | ||
82 | authenticated?: boolean | 96 | authenticated?: boolean |
83 | 97 | ||
84 | registeredPlugin?: RegisteredPlugin | 98 | registeredPlugin?: RegisteredPlugin |
85 | 99 | ||
86 | plugin?: PluginModel | 100 | plugin?: MPlugin |
87 | } | 101 | } |
88 | } | 102 | } |
89 | } | 103 | } |
diff --git a/server/typings/models/account/account-blocklist.ts b/server/typings/models/account/account-blocklist.ts new file mode 100644 index 000000000..c9cb55332 --- /dev/null +++ b/server/typings/models/account/account-blocklist.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | import { AccountBlocklistModel } from '../../../models/account/account-blocklist' | ||
2 | import { PickWith } from '../../utils' | ||
3 | import { MAccountDefault, MAccountFormattable } from './account' | ||
4 | |||
5 | type Use<K extends keyof AccountBlocklistModel, M> = PickWith<AccountBlocklistModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MAccountBlocklist = Omit<AccountBlocklistModel, 'ByAccount' | 'BlockedAccount'> | ||
10 | |||
11 | // ############################################################################ | ||
12 | |||
13 | export type MAccountBlocklistId = Pick<AccountBlocklistModel, 'id'> | ||
14 | |||
15 | export type MAccountBlocklistAccounts = MAccountBlocklist & | ||
16 | Use<'ByAccount', MAccountDefault> & | ||
17 | Use<'BlockedAccount', MAccountDefault> | ||
18 | |||
19 | // ############################################################################ | ||
20 | |||
21 | // Format for API or AP object | ||
22 | |||
23 | export type MAccountBlocklistFormattable = Pick<MAccountBlocklist, 'createdAt'> & | ||
24 | Use<'ByAccount', MAccountFormattable> & | ||
25 | Use<'BlockedAccount', MAccountFormattable> | ||
diff --git a/server/typings/models/account/account.ts b/server/typings/models/account/account.ts new file mode 100644 index 000000000..ec78fece8 --- /dev/null +++ b/server/typings/models/account/account.ts | |||
@@ -0,0 +1,95 @@ | |||
1 | import { AccountModel } from '../../../models/account/account' | ||
2 | import { | ||
3 | MActor, | ||
4 | MActorAP, | ||
5 | MActorAPI, | ||
6 | MActorAudience, | ||
7 | MActorDefault, | ||
8 | MActorDefaultLight, | ||
9 | MActorFormattable, | ||
10 | MActorId, | ||
11 | MActorServer, | ||
12 | MActorSummary, | ||
13 | MActorSummaryFormattable, | ||
14 | MActorUrl | ||
15 | } from './actor' | ||
16 | import { FunctionProperties, PickWith } from '../../utils' | ||
17 | import { MAccountBlocklistId } from './account-blocklist' | ||
18 | import { MChannelDefault } from '@server/typings/models' | ||
19 | |||
20 | type Use<K extends keyof AccountModel, M> = PickWith<AccountModel, K, M> | ||
21 | |||
22 | // ############################################################################ | ||
23 | |||
24 | export type MAccount = Omit<AccountModel, 'Actor' | 'User' | 'Application' | 'VideoChannels' | 'VideoPlaylists' | | ||
25 | 'VideoComments' | 'BlockedAccounts'> | ||
26 | |||
27 | // ############################################################################ | ||
28 | |||
29 | // Only some attributes | ||
30 | export type MAccountId = Pick<MAccount, 'id'> | ||
31 | export type MAccountUserId = Pick<MAccount, 'userId'> | ||
32 | |||
33 | // Only some Actor attributes | ||
34 | export type MAccountUrl = Use<'Actor', MActorUrl> | ||
35 | export type MAccountAudience = Use<'Actor', MActorAudience> | ||
36 | |||
37 | export type MAccountIdActor = MAccountId & | ||
38 | Use<'Actor', MActor> | ||
39 | |||
40 | export type MAccountIdActorId = MAccountId & | ||
41 | Use<'Actor', MActorId> | ||
42 | |||
43 | // ############################################################################ | ||
44 | |||
45 | // Default scope | ||
46 | export type MAccountDefault = MAccount & | ||
47 | Use<'Actor', MActorDefault> | ||
48 | |||
49 | // Default with default association scopes | ||
50 | export type MAccountDefaultChannelDefault = MAccount & | ||
51 | Use<'Actor', MActorDefault> & | ||
52 | Use<'VideoChannels', MChannelDefault[]> | ||
53 | |||
54 | // We don't need some actors attributes | ||
55 | export type MAccountLight = MAccount & | ||
56 | Use<'Actor', MActorDefaultLight> | ||
57 | |||
58 | // ############################################################################ | ||
59 | |||
60 | // Full actor | ||
61 | export type MAccountActor = MAccount & | ||
62 | Use<'Actor', MActor> | ||
63 | |||
64 | // Full actor with server | ||
65 | export type MAccountServer = MAccount & | ||
66 | Use<'Actor', MActorServer> | ||
67 | |||
68 | // ############################################################################ | ||
69 | |||
70 | // For API | ||
71 | |||
72 | export type MAccountSummary = FunctionProperties<MAccount> & | ||
73 | Pick<MAccount, 'id' | 'name'> & | ||
74 | Use<'Actor', MActorSummary> | ||
75 | |||
76 | export type MAccountSummaryBlocks = MAccountSummary & | ||
77 | Use<'BlockedAccounts', MAccountBlocklistId[]> | ||
78 | |||
79 | export type MAccountAPI = MAccount & | ||
80 | Use<'Actor', MActorAPI> | ||
81 | |||
82 | // ############################################################################ | ||
83 | |||
84 | // Format for API or AP object | ||
85 | |||
86 | export type MAccountSummaryFormattable = FunctionProperties<MAccount> & | ||
87 | Pick<MAccount, 'id' | 'name'> & | ||
88 | Use<'Actor', MActorSummaryFormattable> | ||
89 | |||
90 | export type MAccountFormattable = FunctionProperties<MAccount> & | ||
91 | Pick<MAccount, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'userId'> & | ||
92 | Use<'Actor', MActorFormattable> | ||
93 | |||
94 | export type MAccountAP = Pick<MAccount, 'name' | 'description'> & | ||
95 | Use<'Actor', MActorAP> | ||
diff --git a/server/typings/models/account/actor-follow.ts b/server/typings/models/account/actor-follow.ts new file mode 100644 index 000000000..1c66eb0a0 --- /dev/null +++ b/server/typings/models/account/actor-follow.ts | |||
@@ -0,0 +1,63 @@ | |||
1 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
2 | import { | ||
3 | MActor, | ||
4 | MActorAccount, | ||
5 | MActorDefaultAccountChannel, | ||
6 | MActorChannelAccountActor, | ||
7 | MActorDefault, | ||
8 | MActorFormattable, | ||
9 | MActorHost, | ||
10 | MActorUsername | ||
11 | } from './actor' | ||
12 | import { PickWith } from '../../utils' | ||
13 | import { ActorModel } from '@server/models/activitypub/actor' | ||
14 | import { MChannelDefault } from '@server/typings/models' | ||
15 | |||
16 | type Use<K extends keyof ActorFollowModel, M> = PickWith<ActorFollowModel, K, M> | ||
17 | |||
18 | // ############################################################################ | ||
19 | |||
20 | export type MActorFollow = Omit<ActorFollowModel, 'ActorFollower' | 'ActorFollowing'> | ||
21 | |||
22 | // ############################################################################ | ||
23 | |||
24 | export type MActorFollowFollowingHost = MActorFollow & | ||
25 | Use<'ActorFollowing', MActorUsername & MActorHost> | ||
26 | |||
27 | // ############################################################################ | ||
28 | |||
29 | // With actors or actors default | ||
30 | |||
31 | export type MActorFollowActors = MActorFollow & | ||
32 | Use<'ActorFollower', MActor> & | ||
33 | Use<'ActorFollowing', MActor> | ||
34 | |||
35 | export type MActorFollowActorsDefault = MActorFollow & | ||
36 | Use<'ActorFollower', MActorDefault> & | ||
37 | Use<'ActorFollowing', MActorDefault> | ||
38 | |||
39 | export type MActorFollowFull = MActorFollow & | ||
40 | Use<'ActorFollower', MActorDefaultAccountChannel> & | ||
41 | Use<'ActorFollowing', MActorDefaultAccountChannel> | ||
42 | |||
43 | // ############################################################################ | ||
44 | |||
45 | // For subscriptions | ||
46 | |||
47 | type SubscriptionFollowing = MActorDefault & | ||
48 | PickWith<ActorModel, 'VideoChannel', MChannelDefault> | ||
49 | |||
50 | export type MActorFollowActorsDefaultSubscription = MActorFollow & | ||
51 | Use<'ActorFollower', MActorDefault> & | ||
52 | Use<'ActorFollowing', SubscriptionFollowing> | ||
53 | |||
54 | export type MActorFollowSubscriptions = MActorFollow & | ||
55 | Use<'ActorFollowing', MActorChannelAccountActor> | ||
56 | |||
57 | // ############################################################################ | ||
58 | |||
59 | // Format for API or AP object | ||
60 | |||
61 | export type MActorFollowFormattable = Pick<MActorFollow, 'id' | 'score' | 'state' | 'createdAt' | 'updatedAt'> & | ||
62 | Use<'ActorFollower', MActorFormattable> & | ||
63 | Use<'ActorFollowing', MActorFormattable> | ||
diff --git a/server/typings/models/account/actor.ts b/server/typings/models/account/actor.ts new file mode 100644 index 000000000..bcacb8351 --- /dev/null +++ b/server/typings/models/account/actor.ts | |||
@@ -0,0 +1,121 @@ | |||
1 | import { ActorModel } from '../../../models/activitypub/actor' | ||
2 | import { FunctionProperties, PickWith, PickWithOpt } from '../../utils' | ||
3 | import { MAccount, MAccountDefault, MAccountId, MAccountIdActor } from './account' | ||
4 | import { MServer, MServerHost, MServerHostBlocks, MServerRedundancyAllowed } from '../server' | ||
5 | import { MAvatar, MAvatarFormattable } from './avatar' | ||
6 | import { MChannel, MChannelAccountActor, MChannelAccountDefault, MChannelId, MChannelIdActor } from '../video' | ||
7 | |||
8 | type Use<K extends keyof ActorModel, M> = PickWith<ActorModel, K, M> | ||
9 | |||
10 | // ############################################################################ | ||
11 | |||
12 | export type MActor = Omit<ActorModel, 'Account' | 'VideoChannel' | 'ActorFollowing' | 'Avatar' | 'ActorFollowers' | 'Server'> | ||
13 | |||
14 | // ############################################################################ | ||
15 | |||
16 | export type MActorUrl = Pick<MActor, 'url'> | ||
17 | export type MActorId = Pick<MActor, 'id'> | ||
18 | export type MActorUsername = Pick<MActor, 'preferredUsername'> | ||
19 | |||
20 | export type MActorFollowersUrl = Pick<MActor, 'followersUrl'> | ||
21 | export type MActorAudience = MActorUrl & MActorFollowersUrl | ||
22 | export type MActorFollowerException = Pick<ActorModel, 'sharedInboxUrl' | 'inboxUrl'> | ||
23 | export type MActorSignature = MActorAccountChannelId | ||
24 | |||
25 | export type MActorLight = Omit<MActor, 'privateKey' | 'privateKey'> | ||
26 | |||
27 | // ############################################################################ | ||
28 | |||
29 | // Some association attributes | ||
30 | |||
31 | export type MActorHost = Use<'Server', MServerHost> | ||
32 | export type MActorRedundancyAllowedOpt = PickWithOpt<ActorModel, 'Server', MServerRedundancyAllowed> | ||
33 | |||
34 | export type MActorDefaultLight = MActorLight & | ||
35 | Use<'Server', MServerHost> & | ||
36 | Use<'Avatar', MAvatar> | ||
37 | |||
38 | export type MActorAccountId = MActor & | ||
39 | Use<'Account', MAccountId> | ||
40 | export type MActorAccountIdActor = MActor & | ||
41 | Use<'Account', MAccountIdActor> | ||
42 | |||
43 | export type MActorChannelId = MActor & | ||
44 | Use<'VideoChannel', MChannelId> | ||
45 | export type MActorChannelIdActor = MActor & | ||
46 | Use<'VideoChannel', MChannelIdActor> | ||
47 | |||
48 | export type MActorAccountChannelId = MActorAccountId & MActorChannelId | ||
49 | export type MActorAccountChannelIdActor = MActorAccountIdActor & MActorChannelIdActor | ||
50 | |||
51 | // ############################################################################ | ||
52 | |||
53 | // Include raw account/channel/server | ||
54 | |||
55 | export type MActorAccount = MActor & | ||
56 | Use<'Account', MAccount> | ||
57 | |||
58 | export type MActorChannel = MActor & | ||
59 | Use<'VideoChannel', MChannel> | ||
60 | |||
61 | export type MActorDefaultAccountChannel = MActorDefault & MActorAccount & MActorChannel | ||
62 | |||
63 | export type MActorServer = MActor & | ||
64 | Use<'Server', MServer> | ||
65 | |||
66 | // ############################################################################ | ||
67 | |||
68 | // Complex actor associations | ||
69 | |||
70 | export type MActorDefault = MActor & | ||
71 | Use<'Server', MServer> & | ||
72 | Use<'Avatar', MAvatar> | ||
73 | |||
74 | // Actor with channel that is associated to an account and its actor | ||
75 | // Actor -> VideoChannel -> Account -> Actor | ||
76 | export type MActorChannelAccountActor = MActor & | ||
77 | Use<'VideoChannel', MChannelAccountActor> | ||
78 | |||
79 | export type MActorFull = MActor & | ||
80 | Use<'Server', MServer> & | ||
81 | Use<'Avatar', MAvatar> & | ||
82 | Use<'Account', MAccount> & | ||
83 | Use<'VideoChannel', MChannelAccountActor> | ||
84 | |||
85 | // Same than ActorFull, but the account and the channel have their actor | ||
86 | export type MActorFullActor = MActor & | ||
87 | Use<'Server', MServer> & | ||
88 | Use<'Avatar', MAvatar> & | ||
89 | Use<'Account', MAccountDefault> & | ||
90 | Use<'VideoChannel', MChannelAccountDefault> | ||
91 | |||
92 | // ############################################################################ | ||
93 | |||
94 | // API | ||
95 | |||
96 | export type MActorSummary = FunctionProperties<MActor> & | ||
97 | Pick<MActor, 'id' | 'preferredUsername' | 'url' | 'serverId' | 'avatarId'> & | ||
98 | Use<'Server', MServerHost> & | ||
99 | Use<'Avatar', MAvatar> | ||
100 | |||
101 | export type MActorSummaryBlocks = MActorSummary & | ||
102 | Use<'Server', MServerHostBlocks> | ||
103 | |||
104 | export type MActorAPI = Omit<MActorDefault, 'publicKey' | 'privateKey' | 'inboxUrl' | 'outboxUrl' | 'sharedInboxUrl' | | ||
105 | 'followersUrl' | 'followingUrl' | 'url' | 'createdAt' | 'updatedAt'> | ||
106 | |||
107 | // ############################################################################ | ||
108 | |||
109 | // Format for API or AP object | ||
110 | |||
111 | export type MActorSummaryFormattable = FunctionProperties<MActor> & | ||
112 | Pick<MActor, 'url' | 'preferredUsername'> & | ||
113 | Use<'Server', MServerHost> & | ||
114 | Use<'Avatar', MAvatarFormattable> | ||
115 | |||
116 | export type MActorFormattable = MActorSummaryFormattable & | ||
117 | Pick<MActor, 'id' | 'followingCount' | 'followersCount' | 'createdAt' | 'updatedAt'> & | ||
118 | Use<'Server', MServerHost & Partial<Pick<MServer, 'redundancyAllowed'>>> | ||
119 | |||
120 | export type MActorAP = MActor & | ||
121 | Use<'Avatar', MAvatar> | ||
diff --git a/server/typings/models/account/avatar.ts b/server/typings/models/account/avatar.ts new file mode 100644 index 000000000..8af6cc787 --- /dev/null +++ b/server/typings/models/account/avatar.ts | |||
@@ -0,0 +1,11 @@ | |||
1 | import { AvatarModel } from '../../../models/avatar/avatar' | ||
2 | import { FunctionProperties } from '@server/typings/utils' | ||
3 | |||
4 | export type MAvatar = AvatarModel | ||
5 | |||
6 | // ############################################################################ | ||
7 | |||
8 | // Format for API or AP object | ||
9 | |||
10 | export type MAvatarFormattable = FunctionProperties<MAvatar> & | ||
11 | Pick<MAvatar, 'filename' | 'createdAt' | 'updatedAt'> | ||
diff --git a/server/typings/models/account/index.d.ts b/server/typings/models/account/index.d.ts new file mode 100644 index 000000000..513c09c40 --- /dev/null +++ b/server/typings/models/account/index.d.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | export * from './account' | ||
2 | export * from './account-blocklist' | ||
3 | export * from './actor' | ||
4 | export * from './actor-follow' | ||
5 | export * from './avatar' | ||
diff --git a/server/typings/models/actor-follow.ts b/server/typings/models/actor-follow.ts deleted file mode 100644 index 952ef877b..000000000 --- a/server/typings/models/actor-follow.ts +++ /dev/null | |||
@@ -1,8 +0,0 @@ | |||
1 | import { ActorFollowModel } from '../../models/activitypub/actor-follow' | ||
2 | import { ActorModelOnly } from './actor' | ||
3 | |||
4 | export type ActorFollowModelOnly = Omit<ActorFollowModel, 'ActorFollower' | 'ActorFollowing'> | ||
5 | export type ActorFollowModelLight = ActorFollowModelOnly & { | ||
6 | ActorFollower: ActorModelOnly | ||
7 | ActorFollowing: ActorModelOnly | ||
8 | } | ||
diff --git a/server/typings/models/actor.ts b/server/typings/models/actor.ts deleted file mode 100644 index 2656c7b66..000000000 --- a/server/typings/models/actor.ts +++ /dev/null | |||
@@ -1,22 +0,0 @@ | |||
1 | import { ActorModel } from '../../models/activitypub/actor' | ||
2 | import { VideoChannelModel } from '../../models/video/video-channel' | ||
3 | import { AccountModel } from '../../models/account/account' | ||
4 | import { FunctionProperties } from '../utils' | ||
5 | |||
6 | export type VideoChannelModelId = FunctionProperties<VideoChannelModel> | ||
7 | export type AccountModelId = FunctionProperties<AccountModel> | Pick<AccountModel, 'id'> | ||
8 | |||
9 | export type VideoChannelModelIdActor = VideoChannelModelId & Pick<VideoChannelModel, 'Actor'> | ||
10 | export type AccountModelIdActor = AccountModelId & Pick<AccountModel, 'Actor'> | ||
11 | |||
12 | export type ActorModelUrl = Pick<ActorModel, 'url'> | ||
13 | export type ActorModelOnly = Omit<ActorModel, 'Account' | 'VideoChannel' | 'ActorFollowing' | 'Avatar' | 'ActorFollowers' | 'Server'> | ||
14 | export type ActorModelId = Pick<ActorModelOnly, 'id'> | ||
15 | |||
16 | export type SignatureActorModel = ActorModelOnly & { | ||
17 | VideoChannel: VideoChannelModelIdActor | ||
18 | |||
19 | Account: AccountModelIdActor | ||
20 | } | ||
21 | |||
22 | export type ActorFollowerException = Pick<ActorModel, 'sharedInboxUrl' | 'inboxUrl'> | ||
diff --git a/server/typings/models/index.d.ts b/server/typings/models/index.d.ts index c90656965..78b4948ce 100644 --- a/server/typings/models/index.d.ts +++ b/server/typings/models/index.d.ts | |||
@@ -1 +1,5 @@ | |||
1 | export * from './actor' | 1 | export * from './account' |
2 | export * from './oauth' | ||
3 | export * from './server' | ||
4 | export * from './user' | ||
5 | export * from './video' | ||
diff --git a/server/typings/models/oauth/index.d.ts b/server/typings/models/oauth/index.d.ts new file mode 100644 index 000000000..36b7ea8ca --- /dev/null +++ b/server/typings/models/oauth/index.d.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './oauth-client' | ||
2 | export * from './oauth-token' | ||
diff --git a/server/typings/models/oauth/oauth-client.ts b/server/typings/models/oauth/oauth-client.ts new file mode 100644 index 000000000..904a07863 --- /dev/null +++ b/server/typings/models/oauth/oauth-client.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | import { OAuthClientModel } from '@server/models/oauth/oauth-client' | ||
2 | |||
3 | export type MOAuthClient = Omit<OAuthClientModel, 'OAuthTokens'> | ||
diff --git a/server/typings/models/oauth/oauth-token.ts b/server/typings/models/oauth/oauth-token.ts new file mode 100644 index 000000000..af3412925 --- /dev/null +++ b/server/typings/models/oauth/oauth-token.ts | |||
@@ -0,0 +1,13 @@ | |||
1 | import { OAuthTokenModel } from '@server/models/oauth/oauth-token' | ||
2 | import { PickWith } from '@server/typings/utils' | ||
3 | import { MUserAccountUrl } from '@server/typings/models' | ||
4 | |||
5 | type Use<K extends keyof OAuthTokenModel, M> = PickWith<OAuthTokenModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MOAuthToken = Omit<OAuthTokenModel, 'User' | 'OAuthClients'> | ||
10 | |||
11 | export type MOAuthTokenUser = MOAuthToken & | ||
12 | Use<'User', MUserAccountUrl> & | ||
13 | { user?: MUserAccountUrl } | ||
diff --git a/server/typings/models/server/index.d.ts b/server/typings/models/server/index.d.ts new file mode 100644 index 000000000..c853795ad --- /dev/null +++ b/server/typings/models/server/index.d.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './plugin' | ||
2 | export * from './server' | ||
3 | export * from './server-blocklist' | ||
diff --git a/server/typings/models/server/plugin.ts b/server/typings/models/server/plugin.ts new file mode 100644 index 000000000..94674c318 --- /dev/null +++ b/server/typings/models/server/plugin.ts | |||
@@ -0,0 +1,10 @@ | |||
1 | import { PluginModel } from '@server/models/server/plugin' | ||
2 | |||
3 | export type MPlugin = PluginModel | ||
4 | |||
5 | // ############################################################################ | ||
6 | |||
7 | // Format for API or AP object | ||
8 | |||
9 | export type MPluginFormattable = Pick<MPlugin, 'name' | 'type' | 'version' | 'latestVersion' | 'enabled' | 'uninstalled' | ||
10 | | 'peertubeEngine' | 'description' | 'homepage' | 'settings' | 'createdAt' | 'updatedAt'> | ||
diff --git a/server/typings/models/server/server-blocklist.ts b/server/typings/models/server/server-blocklist.ts new file mode 100644 index 000000000..c81f604f5 --- /dev/null +++ b/server/typings/models/server/server-blocklist.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | import { ServerBlocklistModel } from '@server/models/server/server-blocklist' | ||
2 | import { PickWith } from '@server/typings/utils' | ||
3 | import { MAccountDefault, MAccountFormattable, MServer, MServerFormattable } from '@server/typings/models' | ||
4 | |||
5 | type Use<K extends keyof ServerBlocklistModel, M> = PickWith<ServerBlocklistModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MServerBlocklist = Omit<ServerBlocklistModel, 'ByAccount' | 'BlockedServer'> | ||
10 | |||
11 | // ############################################################################ | ||
12 | |||
13 | export type MServerBlocklistAccountServer = MServerBlocklist & | ||
14 | Use<'ByAccount', MAccountDefault> & | ||
15 | Use<'BlockedServer', MServer> | ||
16 | |||
17 | // ############################################################################ | ||
18 | |||
19 | // Format for API or AP object | ||
20 | |||
21 | export type MServerBlocklistFormattable = Pick<MServerBlocklist, 'createdAt'> & | ||
22 | Use<'ByAccount', MAccountFormattable> & | ||
23 | Use<'BlockedServer', MServerFormattable> | ||
diff --git a/server/typings/models/server/server.ts b/server/typings/models/server/server.ts new file mode 100644 index 000000000..190cc0c28 --- /dev/null +++ b/server/typings/models/server/server.ts | |||
@@ -0,0 +1,24 @@ | |||
1 | import { ServerModel } from '../../../models/server/server' | ||
2 | import { FunctionProperties, PickWith } from '../../utils' | ||
3 | import { MAccountBlocklistId } from '../account' | ||
4 | |||
5 | type Use<K extends keyof ServerModel, M> = PickWith<ServerModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MServer = Omit<ServerModel, 'Actors' | 'BlockedByAccounts'> | ||
10 | |||
11 | // ############################################################################ | ||
12 | |||
13 | export type MServerHost = Pick<MServer, 'host'> | ||
14 | export type MServerRedundancyAllowed = Pick<MServer, 'redundancyAllowed'> | ||
15 | |||
16 | export type MServerHostBlocks = MServerHost & | ||
17 | Use<'BlockedByAccounts', MAccountBlocklistId[]> | ||
18 | |||
19 | // ############################################################################ | ||
20 | |||
21 | // Format for API or AP object | ||
22 | |||
23 | export type MServerFormattable = FunctionProperties<MServer> & | ||
24 | Pick<MServer, 'host'> | ||
diff --git a/server/typings/models/user/index.d.ts b/server/typings/models/user/index.d.ts new file mode 100644 index 000000000..6657b2128 --- /dev/null +++ b/server/typings/models/user/index.d.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | export * from './user' | ||
2 | export * from './user-notification' | ||
3 | export * from './user-notification-setting' | ||
4 | export * from './user-video-history' | ||
diff --git a/server/typings/models/user/user-notification-setting.ts b/server/typings/models/user/user-notification-setting.ts new file mode 100644 index 000000000..c674add1b --- /dev/null +++ b/server/typings/models/user/user-notification-setting.ts | |||
@@ -0,0 +1,9 @@ | |||
1 | import { UserNotificationSettingModel } from '@server/models/account/user-notification-setting' | ||
2 | |||
3 | export type MNotificationSetting = Omit<UserNotificationSettingModel, 'User'> | ||
4 | |||
5 | // ############################################################################ | ||
6 | |||
7 | // Format for API or AP object | ||
8 | |||
9 | export type MNotificationSettingFormattable = MNotificationSetting | ||
diff --git a/server/typings/models/user/user-notification.ts b/server/typings/models/user/user-notification.ts new file mode 100644 index 000000000..1cdc691b0 --- /dev/null +++ b/server/typings/models/user/user-notification.ts | |||
@@ -0,0 +1,78 @@ | |||
1 | import { UserNotificationModel } from '../../../models/account/user-notification' | ||
2 | import { PickWith, PickWithOpt } from '../../utils' | ||
3 | import { VideoModel } from '../../../models/video/video' | ||
4 | import { ActorModel } from '../../../models/activitypub/actor' | ||
5 | import { ServerModel } from '../../../models/server/server' | ||
6 | import { AvatarModel } from '../../../models/avatar/avatar' | ||
7 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
8 | import { AccountModel } from '../../../models/account/account' | ||
9 | import { VideoCommentModel } from '../../../models/video/video-comment' | ||
10 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
11 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' | ||
12 | import { VideoImportModel } from '../../../models/video/video-import' | ||
13 | import { ActorFollowModel } from '../../../models/activitypub/actor-follow' | ||
14 | |||
15 | type Use<K extends keyof UserNotificationModel, M> = PickWith<UserNotificationModel, K, M> | ||
16 | |||
17 | // ############################################################################ | ||
18 | |||
19 | export namespace UserNotificationIncludes { | ||
20 | export type VideoInclude = Pick<VideoModel, 'id' | 'uuid' | 'name'> | ||
21 | export type VideoIncludeChannel = VideoInclude & | ||
22 | PickWith<VideoModel, 'VideoChannel', VideoChannelIncludeActor> | ||
23 | |||
24 | export type ActorInclude = Pick<ActorModel, 'preferredUsername' | 'getHost'> & | ||
25 | PickWith<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> & | ||
26 | PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> | ||
27 | |||
28 | export type VideoChannelInclude = Pick<VideoChannelModel, 'id' | 'name' | 'getDisplayName'> | ||
29 | export type VideoChannelIncludeActor = VideoChannelInclude & | ||
30 | PickWith<VideoChannelModel, 'Actor', ActorInclude> | ||
31 | |||
32 | export type AccountInclude = Pick<AccountModel, 'id' | 'name' | 'getDisplayName'> | ||
33 | export type AccountIncludeActor = AccountInclude & | ||
34 | PickWith<AccountModel, 'Actor', ActorInclude> | ||
35 | |||
36 | export type VideoCommentInclude = Pick<VideoCommentModel, 'id' | 'originCommentId' | 'getThreadId'> & | ||
37 | PickWith<VideoCommentModel, 'Account', AccountIncludeActor> & | ||
38 | PickWith<VideoCommentModel, 'Video', VideoInclude> | ||
39 | |||
40 | export type VideoAbuseInclude = Pick<VideoAbuseModel, 'id'> & | ||
41 | PickWith<VideoAbuseModel, 'Video', VideoInclude> | ||
42 | |||
43 | export type VideoBlacklistInclude = Pick<VideoBlacklistModel, 'id'> & | ||
44 | PickWith<VideoAbuseModel, 'Video', VideoInclude> | ||
45 | |||
46 | export type VideoImportInclude = Pick<VideoImportModel, 'id' | 'magnetUri' | 'targetUrl' | 'torrentName'> & | ||
47 | PickWith<VideoImportModel, 'Video', VideoInclude> | ||
48 | |||
49 | export type ActorFollower = Pick<ActorModel, 'preferredUsername' | 'getHost'> & | ||
50 | PickWith<ActorModel, 'Account', AccountInclude> & | ||
51 | PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> & | ||
52 | PickWithOpt<ActorModel, 'Avatar', Pick<AvatarModel, 'filename' | 'getStaticPath'>> | ||
53 | |||
54 | export type ActorFollowing = Pick<ActorModel, 'preferredUsername' | 'type' | 'getHost'> & | ||
55 | PickWith<ActorModel, 'VideoChannel', VideoChannelInclude> & | ||
56 | PickWith<ActorModel, 'Account', AccountInclude> & | ||
57 | PickWith<ActorModel, 'Server', Pick<ServerModel, 'host'>> | ||
58 | |||
59 | export type ActorFollowInclude = Pick<ActorFollowModel, 'id' | 'state'> & | ||
60 | PickWith<ActorFollowModel, 'ActorFollower', ActorFollower> & | ||
61 | PickWith<ActorFollowModel, 'ActorFollowing', ActorFollowing> | ||
62 | } | ||
63 | |||
64 | // ############################################################################ | ||
65 | |||
66 | export type MUserNotification = Omit<UserNotificationModel, 'User' | 'Video' | 'Comment' | 'VideoAbuse' | 'VideoBlacklist' | | ||
67 | 'VideoImport' | 'Account' | 'ActorFollow'> | ||
68 | |||
69 | // ############################################################################ | ||
70 | |||
71 | export type UserNotificationModelForApi = MUserNotification & | ||
72 | Use<'Video', UserNotificationIncludes.VideoIncludeChannel> & | ||
73 | Use<'Comment', UserNotificationIncludes.VideoCommentInclude> & | ||
74 | Use<'VideoAbuse', UserNotificationIncludes.VideoAbuseInclude> & | ||
75 | Use<'VideoBlacklist', UserNotificationIncludes.VideoBlacklistInclude> & | ||
76 | Use<'VideoImport', UserNotificationIncludes.VideoImportInclude> & | ||
77 | Use<'ActorFollow', UserNotificationIncludes.ActorFollowInclude> & | ||
78 | Use<'Account', UserNotificationIncludes.AccountIncludeActor> | ||
diff --git a/server/typings/models/user/user-video-history.ts b/server/typings/models/user/user-video-history.ts new file mode 100644 index 000000000..62673ab1b --- /dev/null +++ b/server/typings/models/user/user-video-history.ts | |||
@@ -0,0 +1,5 @@ | |||
1 | import { UserVideoHistoryModel } from '../../../models/account/user-video-history' | ||
2 | |||
3 | export type MUserVideoHistory = Omit<UserVideoHistoryModel, 'Video' | 'User'> | ||
4 | |||
5 | export type MUserVideoHistoryTime = Pick<MUserVideoHistory, 'currentTime'> | ||
diff --git a/server/typings/models/user/user.ts b/server/typings/models/user/user.ts new file mode 100644 index 000000000..52d6d4a05 --- /dev/null +++ b/server/typings/models/user/user.ts | |||
@@ -0,0 +1,70 @@ | |||
1 | import { UserModel } from '../../../models/account/user' | ||
2 | import { PickWith, PickWithOpt } from '../../utils' | ||
3 | import { | ||
4 | MAccount, | ||
5 | MAccountDefault, | ||
6 | MAccountDefaultChannelDefault, | ||
7 | MAccountFormattable, | ||
8 | MAccountId, | ||
9 | MAccountIdActorId, | ||
10 | MAccountUrl | ||
11 | } from '../account' | ||
12 | import { MNotificationSetting, MNotificationSettingFormattable } from './user-notification-setting' | ||
13 | import { AccountModel } from '@server/models/account/account' | ||
14 | import { MChannelFormattable } from '@server/typings/models' | ||
15 | |||
16 | type Use<K extends keyof UserModel, M> = PickWith<UserModel, K, M> | ||
17 | |||
18 | // ############################################################################ | ||
19 | |||
20 | export type MUser = Omit<UserModel, 'Account' | 'NotificationSetting' | 'VideoImports' | 'OAuthTokens'> | ||
21 | |||
22 | // ############################################################################ | ||
23 | |||
24 | export type MUserQuotaUsed = MUser & { videoQuotaUsed?: number, videoQuotaUsedDaily?: number } | ||
25 | export type MUserId = Pick<UserModel, 'id'> | ||
26 | |||
27 | // ############################################################################ | ||
28 | |||
29 | // With account | ||
30 | |||
31 | export type MUserAccountId = MUser & | ||
32 | Use<'Account', MAccountId> | ||
33 | |||
34 | export type MUserAccountUrl = MUser & | ||
35 | Use<'Account', MAccountUrl & MAccountIdActorId> | ||
36 | |||
37 | export type MUserAccount = MUser & | ||
38 | Use<'Account', MAccount> | ||
39 | |||
40 | export type MUserAccountDefault = MUser & | ||
41 | Use<'Account', MAccountDefault> | ||
42 | |||
43 | // With channel | ||
44 | |||
45 | export type MUserNotifSettingChannelDefault = MUser & | ||
46 | Use<'NotificationSetting', MNotificationSetting> & | ||
47 | Use<'Account', MAccountDefaultChannelDefault> | ||
48 | |||
49 | // With notification settings | ||
50 | |||
51 | export type MUserWithNotificationSetting = MUser & | ||
52 | Use<'NotificationSetting', MNotificationSetting> | ||
53 | |||
54 | export type MUserNotifSettingAccount = MUser & | ||
55 | Use<'NotificationSetting', MNotificationSetting> & | ||
56 | Use<'Account', MAccount> | ||
57 | |||
58 | // Default scope | ||
59 | |||
60 | export type MUserDefault = MUser & | ||
61 | Use<'NotificationSetting', MNotificationSetting> & | ||
62 | Use<'Account', MAccountDefault> | ||
63 | |||
64 | // ############################################################################ | ||
65 | |||
66 | // Format for API or AP object | ||
67 | |||
68 | export type MUserFormattable = MUserQuotaUsed & | ||
69 | Use<'Account', MAccountFormattable & PickWithOpt<AccountModel, 'VideoChannels', MChannelFormattable[]>> & | ||
70 | PickWithOpt<UserModel, 'NotificationSetting', MNotificationSettingFormattable> | ||
diff --git a/server/typings/models/video-share.ts b/server/typings/models/video-share.ts deleted file mode 100644 index 1406749d2..000000000 --- a/server/typings/models/video-share.ts +++ /dev/null | |||
@@ -1,3 +0,0 @@ | |||
1 | import { VideoShareModel } from '../../models/video/video-share' | ||
2 | |||
3 | export type VideoShareModelOnly = Omit<VideoShareModel, 'Actor' | 'Video'> | ||
diff --git a/server/typings/models/video/index.d.ts b/server/typings/models/video/index.d.ts new file mode 100644 index 000000000..bd69c8a4b --- /dev/null +++ b/server/typings/models/video/index.d.ts | |||
@@ -0,0 +1,18 @@ | |||
1 | export * from './schedule-video-update' | ||
2 | export * from './tag' | ||
3 | export * from './thumbnail' | ||
4 | export * from './video' | ||
5 | export * from './video-abuse' | ||
6 | export * from './video-blacklist' | ||
7 | export * from './video-caption' | ||
8 | export * from './video-change-ownership' | ||
9 | export * from './video-channels' | ||
10 | export * from './video-comment' | ||
11 | export * from './video-file' | ||
12 | export * from './video-import' | ||
13 | export * from './video-playlist' | ||
14 | export * from './video-playlist-element' | ||
15 | export * from './video-rate' | ||
16 | export * from './video-redundancy' | ||
17 | export * from './video-share' | ||
18 | export * from './video-streaming-playlist' | ||
diff --git a/server/typings/models/video/schedule-video-update.ts b/server/typings/models/video/schedule-video-update.ts new file mode 100644 index 000000000..ada9af06e --- /dev/null +++ b/server/typings/models/video/schedule-video-update.ts | |||
@@ -0,0 +1,9 @@ | |||
1 | import { ScheduleVideoUpdateModel } from '../../../models/video/schedule-video-update' | ||
2 | |||
3 | export type MScheduleVideoUpdate = Omit<ScheduleVideoUpdateModel, 'Video'> | ||
4 | |||
5 | // ############################################################################ | ||
6 | |||
7 | // Format for API or AP object | ||
8 | |||
9 | export type MScheduleVideoUpdateFormattable = Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'> | ||
diff --git a/server/typings/models/video/tag.ts b/server/typings/models/video/tag.ts new file mode 100644 index 000000000..64a68873e --- /dev/null +++ b/server/typings/models/video/tag.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | import { TagModel } from '../../../models/video/tag' | ||
2 | |||
3 | export type MTag = Omit<TagModel, 'Videos'> | ||
diff --git a/server/typings/models/video/thumbnail.ts b/server/typings/models/video/thumbnail.ts new file mode 100644 index 000000000..c03ba55ac --- /dev/null +++ b/server/typings/models/video/thumbnail.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | import { ThumbnailModel } from '../../../models/video/thumbnail' | ||
2 | |||
3 | export type MThumbnail = Omit<ThumbnailModel, 'Video' | 'VideoPlaylist'> | ||
diff --git a/server/typings/models/video/video-abuse.ts b/server/typings/models/video/video-abuse.ts new file mode 100644 index 000000000..e38c3f586 --- /dev/null +++ b/server/typings/models/video/video-abuse.ts | |||
@@ -0,0 +1,31 @@ | |||
1 | import { VideoAbuseModel } from '../../../models/video/video-abuse' | ||
2 | import { PickWith } from '../../utils' | ||
3 | import { MVideo } from './video' | ||
4 | import { MAccountDefault, MAccountFormattable } from '../account' | ||
5 | |||
6 | type Use<K extends keyof VideoAbuseModel, M> = PickWith<VideoAbuseModel, K, M> | ||
7 | |||
8 | // ############################################################################ | ||
9 | |||
10 | export type MVideoAbuse = Omit<VideoAbuseModel, 'Account' | 'Video' | 'toActivityPubObject'> | ||
11 | |||
12 | // ############################################################################ | ||
13 | |||
14 | export type MVideoAbuseId = Pick<VideoAbuseModel, 'id'> | ||
15 | |||
16 | export type MVideoAbuseVideo = MVideoAbuse & | ||
17 | Pick<VideoAbuseModel, 'toActivityPubObject'> & | ||
18 | Use<'Video', MVideo> | ||
19 | |||
20 | export type MVideoAbuseAccountVideo = MVideoAbuse & | ||
21 | Pick<VideoAbuseModel, 'toActivityPubObject'> & | ||
22 | Use<'Video', MVideo> & | ||
23 | Use<'Account', MAccountDefault> | ||
24 | |||
25 | // ############################################################################ | ||
26 | |||
27 | // Format for API or AP object | ||
28 | |||
29 | export type MVideoAbuseFormattable = MVideoAbuse & | ||
30 | Use<'Account', MAccountFormattable> & | ||
31 | Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'name'>> | ||
diff --git a/server/typings/models/video/video-blacklist.ts b/server/typings/models/video/video-blacklist.ts new file mode 100644 index 000000000..e12880454 --- /dev/null +++ b/server/typings/models/video/video-blacklist.ts | |||
@@ -0,0 +1,27 @@ | |||
1 | import { VideoBlacklistModel } from '../../../models/video/video-blacklist' | ||
2 | import { PickWith } from '@server/typings/utils' | ||
3 | import { MVideo, MVideoFormattable } from '@server/typings/models' | ||
4 | |||
5 | type Use<K extends keyof VideoBlacklistModel, M> = PickWith<VideoBlacklistModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MVideoBlacklist = Omit<VideoBlacklistModel, 'Video'> | ||
10 | |||
11 | export type MVideoBlacklistLight = Pick<MVideoBlacklist, 'id' | 'reason' | 'unfederated'> | ||
12 | export type MVideoBlacklistUnfederated = Pick<MVideoBlacklist, 'unfederated'> | ||
13 | |||
14 | // ############################################################################ | ||
15 | |||
16 | export type MVideoBlacklistLightVideo = MVideoBlacklistLight & | ||
17 | Use<'Video', MVideo> | ||
18 | |||
19 | export type MVideoBlacklistVideo = MVideoBlacklist & | ||
20 | Use<'Video', MVideo> | ||
21 | |||
22 | // ############################################################################ | ||
23 | |||
24 | // Format for API or AP object | ||
25 | |||
26 | export type MVideoBlacklistFormattable = MVideoBlacklist & | ||
27 | Use<'Video', MVideoFormattable> | ||
diff --git a/server/typings/models/video/video-caption.ts b/server/typings/models/video/video-caption.ts new file mode 100644 index 000000000..7cb2a2ad3 --- /dev/null +++ b/server/typings/models/video/video-caption.ts | |||
@@ -0,0 +1,24 @@ | |||
1 | import { VideoCaptionModel } from '../../../models/video/video-caption' | ||
2 | import { FunctionProperties, PickWith } from '@server/typings/utils' | ||
3 | import { MVideo, MVideoUUID } from '@server/typings/models' | ||
4 | |||
5 | type Use<K extends keyof VideoCaptionModel, M> = PickWith<VideoCaptionModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MVideoCaption = Omit<VideoCaptionModel, 'Video'> | ||
10 | |||
11 | // ############################################################################ | ||
12 | |||
13 | export type MVideoCaptionLanguage = Pick<MVideoCaption, 'language'> | ||
14 | |||
15 | export type MVideoCaptionVideo = MVideoCaption & | ||
16 | Use<'Video', Pick<MVideo, 'id' | 'remote' | 'uuid'>> | ||
17 | |||
18 | // ############################################################################ | ||
19 | |||
20 | // Format for API or AP object | ||
21 | |||
22 | export type MVideoCaptionFormattable = FunctionProperties<MVideoCaption> & | ||
23 | Pick<MVideoCaption, 'language'> & | ||
24 | Use<'Video', MVideoUUID> | ||
diff --git a/server/typings/models/video/video-change-ownership.ts b/server/typings/models/video/video-change-ownership.ts new file mode 100644 index 000000000..72634cdb2 --- /dev/null +++ b/server/typings/models/video/video-change-ownership.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | import { VideoChangeOwnershipModel } from '@server/models/video/video-change-ownership' | ||
2 | import { PickWith } from '@server/typings/utils' | ||
3 | import { MAccountDefault, MAccountFormattable, MVideo, MVideoWithFileThumbnail } from '@server/typings/models' | ||
4 | |||
5 | type Use<K extends keyof VideoChangeOwnershipModel, M> = PickWith<VideoChangeOwnershipModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MVideoChangeOwnership = Omit<VideoChangeOwnershipModel, 'Initiator' | 'NextOwner' | 'Video'> | ||
10 | |||
11 | export type MVideoChangeOwnershipFull = MVideoChangeOwnership & | ||
12 | Use<'Initiator', MAccountDefault> & | ||
13 | Use<'NextOwner', MAccountDefault> & | ||
14 | Use<'Video', MVideoWithFileThumbnail> | ||
15 | |||
16 | // ############################################################################ | ||
17 | |||
18 | // Format for API or AP object | ||
19 | |||
20 | export type MVideoChangeOwnershipFormattable = Pick<MVideoChangeOwnership, 'id' | 'status' | 'createdAt'> & | ||
21 | Use<'Initiator', MAccountFormattable> & | ||
22 | Use<'NextOwner', MAccountFormattable> & | ||
23 | Use<'Video', Pick<MVideo, 'id' | 'uuid' | 'url' | 'name'>> | ||
diff --git a/server/typings/models/video/video-channels.ts b/server/typings/models/video/video-channels.ts new file mode 100644 index 000000000..292d0ac95 --- /dev/null +++ b/server/typings/models/video/video-channels.ts | |||
@@ -0,0 +1,126 @@ | |||
1 | import { FunctionProperties, PickWith, PickWithOpt } from '../../utils' | ||
2 | import { VideoChannelModel } from '../../../models/video/video-channel' | ||
3 | import { | ||
4 | MAccountActor, | ||
5 | MAccountAPI, | ||
6 | MAccountDefault, | ||
7 | MAccountFormattable, | ||
8 | MAccountLight, | ||
9 | MAccountSummaryBlocks, | ||
10 | MAccountSummaryFormattable, | ||
11 | MAccountUrl, | ||
12 | MAccountUserId, | ||
13 | MActor, | ||
14 | MActorAccountChannelId, | ||
15 | MActorAP, | ||
16 | MActorAPI, | ||
17 | MActorDefault, | ||
18 | MActorDefaultLight, | ||
19 | MActorFormattable, | ||
20 | MActorLight, | ||
21 | MActorSummary, | ||
22 | MActorSummaryFormattable, MActorUrl | ||
23 | } from '../account' | ||
24 | import { MVideo } from './video' | ||
25 | |||
26 | type Use<K extends keyof VideoChannelModel, M> = PickWith<VideoChannelModel, K, M> | ||
27 | |||
28 | // ############################################################################ | ||
29 | |||
30 | export type MChannel = Omit<VideoChannelModel, 'Actor' | 'Account' | 'Videos' | 'VideoPlaylists'> | ||
31 | |||
32 | // ############################################################################ | ||
33 | |||
34 | export type MChannelId = Pick<MChannel, 'id'> | ||
35 | |||
36 | // ############################################################################ | ||
37 | |||
38 | export type MChannelIdActor = MChannelId & | ||
39 | Use<'Actor', MActorAccountChannelId> | ||
40 | |||
41 | export type MChannelUserId = Pick<MChannel, 'accountId'> & | ||
42 | Use<'Account', MAccountUserId> | ||
43 | |||
44 | export type MChannelActor = MChannel & | ||
45 | Use<'Actor', MActor> | ||
46 | |||
47 | export type MChannelUrl = Use<'Actor', MActorUrl> | ||
48 | |||
49 | // Default scope | ||
50 | export type MChannelDefault = MChannel & | ||
51 | Use<'Actor', MActorDefault> | ||
52 | |||
53 | // ############################################################################ | ||
54 | |||
55 | // Not all association attributes | ||
56 | |||
57 | export type MChannelLight = MChannel & | ||
58 | Use<'Actor', MActorDefaultLight> | ||
59 | |||
60 | export type MChannelActorLight = MChannel & | ||
61 | Use<'Actor', MActorLight> | ||
62 | |||
63 | export type MChannelAccountLight = MChannel & | ||
64 | Use<'Actor', MActorDefaultLight> & | ||
65 | Use<'Account', MAccountLight> | ||
66 | |||
67 | // ############################################################################ | ||
68 | |||
69 | // Account associations | ||
70 | |||
71 | export type MChannelAccountActor = MChannel & | ||
72 | Use<'Account', MAccountActor> | ||
73 | |||
74 | export type MChannelAccountDefault = MChannel & | ||
75 | Use<'Actor', MActorDefault> & | ||
76 | Use<'Account', MAccountDefault> | ||
77 | |||
78 | export type MChannelActorAccountActor = MChannel & | ||
79 | Use<'Account', MAccountActor> & | ||
80 | Use<'Actor', MActor> | ||
81 | |||
82 | // ############################################################################ | ||
83 | |||
84 | // Videos associations | ||
85 | export type MChannelVideos = MChannel & | ||
86 | Use<'Videos', MVideo[]> | ||
87 | |||
88 | export type MChannelActorAccountDefaultVideos = MChannel & | ||
89 | Use<'Actor', MActorDefault> & | ||
90 | Use<'Account', MAccountDefault> & | ||
91 | Use<'Videos', MVideo[]> | ||
92 | |||
93 | // ############################################################################ | ||
94 | |||
95 | // For API | ||
96 | |||
97 | export type MChannelSummary = FunctionProperties<MChannel> & | ||
98 | Pick<MChannel, 'id' | 'name' | 'description' | 'actorId'> & | ||
99 | Use<'Actor', MActorSummary> | ||
100 | |||
101 | export type MChannelSummaryAccount = MChannelSummary & | ||
102 | Use<'Account', MAccountSummaryBlocks> | ||
103 | |||
104 | export type MChannelAPI = MChannel & | ||
105 | Use<'Actor', MActorAPI> & | ||
106 | Use<'Account', MAccountAPI> | ||
107 | |||
108 | // ############################################################################ | ||
109 | |||
110 | // Format for API or AP object | ||
111 | |||
112 | export type MChannelSummaryFormattable = FunctionProperties<MChannel> & | ||
113 | Pick<MChannel, 'id' | 'name'> & | ||
114 | Use<'Actor', MActorSummaryFormattable> | ||
115 | |||
116 | export type MChannelAccountSummaryFormattable = MChannelSummaryFormattable & | ||
117 | Use<'Account', MAccountSummaryFormattable> | ||
118 | |||
119 | export type MChannelFormattable = FunctionProperties<MChannel> & | ||
120 | Pick<MChannel, 'id' | 'name' | 'description' | 'createdAt' | 'updatedAt' | 'support'> & | ||
121 | Use<'Actor', MActorFormattable> & | ||
122 | PickWithOpt<VideoChannelModel, 'Account', MAccountFormattable> | ||
123 | |||
124 | export type MChannelAP = Pick<MChannel, 'name' | 'description' | 'support'> & | ||
125 | Use<'Actor', MActorAP> & | ||
126 | Use<'Account', MAccountUrl> | ||
diff --git a/server/typings/models/video/video-comment.ts b/server/typings/models/video/video-comment.ts new file mode 100644 index 000000000..4fd1c29e8 --- /dev/null +++ b/server/typings/models/video/video-comment.ts | |||
@@ -0,0 +1,57 @@ | |||
1 | import { VideoCommentModel } from '../../../models/video/video-comment' | ||
2 | import { PickWith, PickWithOpt } from '../../utils' | ||
3 | import { MAccountDefault, MAccountFormattable, MAccountUrl, MActorUrl } from '../account' | ||
4 | import { MVideoAccountLight, MVideoFeed, MVideoIdUrl, MVideoUrl } from './video' | ||
5 | |||
6 | type Use<K extends keyof VideoCommentModel, M> = PickWith<VideoCommentModel, K, M> | ||
7 | |||
8 | // ############################################################################ | ||
9 | |||
10 | export type MComment = Omit<VideoCommentModel, 'OriginVideoComment' | 'InReplyToVideoComment' | 'Video' | 'Account'> | ||
11 | export type MCommentTotalReplies = MComment & { totalReplies?: number } | ||
12 | export type MCommentId = Pick<MComment, 'id'> | ||
13 | export type MCommentUrl = Pick<MComment, 'url'> | ||
14 | |||
15 | // ############################################################################ | ||
16 | |||
17 | export type MCommentOwner = MComment & | ||
18 | Use<'Account', MAccountDefault> | ||
19 | |||
20 | export type MCommentVideo = MComment & | ||
21 | Use<'Video', MVideoAccountLight> | ||
22 | |||
23 | export type MCommentReply = MComment & | ||
24 | Use<'InReplyToVideoComment', MComment> | ||
25 | |||
26 | export type MCommentOwnerVideo = MComment & | ||
27 | Use<'Account', MAccountDefault> & | ||
28 | Use<'Video', MVideoAccountLight> | ||
29 | |||
30 | export type MCommentOwnerVideoReply = MComment & | ||
31 | Use<'Account', MAccountDefault> & | ||
32 | Use<'Video', MVideoAccountLight> & | ||
33 | Use<'InReplyToVideoComment', MComment> | ||
34 | |||
35 | export type MCommentOwnerReplyVideoLight = MComment & | ||
36 | Use<'Account', MAccountDefault> & | ||
37 | Use<'InReplyToVideoComment', MComment> & | ||
38 | Use<'Video', MVideoIdUrl> | ||
39 | |||
40 | export type MCommentOwnerVideoFeed = MCommentOwner & | ||
41 | Use<'Video', MVideoFeed> | ||
42 | |||
43 | // ############################################################################ | ||
44 | |||
45 | export type MCommentAPI = MComment & { totalReplies: number } | ||
46 | |||
47 | // ############################################################################ | ||
48 | |||
49 | // Format for API or AP object | ||
50 | |||
51 | export type MCommentFormattable = MCommentTotalReplies & | ||
52 | Use<'Account', MAccountFormattable> | ||
53 | |||
54 | export type MCommentAP = MComment & | ||
55 | Use<'Account', MAccountUrl> & | ||
56 | PickWithOpt<VideoCommentModel, 'Video', MVideoUrl> & | ||
57 | PickWithOpt<VideoCommentModel, 'InReplyToVideoComment', MCommentUrl> | ||
diff --git a/server/typings/models/video/video-file.ts b/server/typings/models/video/video-file.ts new file mode 100644 index 000000000..484351a8d --- /dev/null +++ b/server/typings/models/video/video-file.ts | |||
@@ -0,0 +1,19 @@ | |||
1 | import { VideoFileModel } from '../../../models/video/video-file' | ||
2 | import { PickWith, PickWithOpt } from '../../utils' | ||
3 | import { MVideo, MVideoUUID } from './video' | ||
4 | import { MVideoRedundancyFileUrl } from './video-redundancy' | ||
5 | |||
6 | type Use<K extends keyof VideoFileModel, M> = PickWith<VideoFileModel, K, M> | ||
7 | |||
8 | // ############################################################################ | ||
9 | |||
10 | export type MVideoFile = Omit<VideoFileModel, 'Video' | 'RedundancyVideos'> | ||
11 | |||
12 | export type MVideoFileVideo = MVideoFile & | ||
13 | Use<'Video', MVideo> | ||
14 | |||
15 | export type MVideoFileVideoUUID = MVideoFile & | ||
16 | Use<'Video', MVideoUUID> | ||
17 | |||
18 | export type MVideoFileRedundanciesOpt = MVideoFile & | ||
19 | PickWithOpt<VideoFileModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]> | ||
diff --git a/server/typings/models/video/video-import.ts b/server/typings/models/video/video-import.ts new file mode 100644 index 000000000..c6a1c5b66 --- /dev/null +++ b/server/typings/models/video/video-import.ts | |||
@@ -0,0 +1,31 @@ | |||
1 | import { VideoImportModel } from '@server/models/video/video-import' | ||
2 | import { PickWith, PickWithOpt } from '@server/typings/utils' | ||
3 | import { MUser, MVideo, MVideoAccountLight, MVideoFormattable, MVideoTag, MVideoThumbnail, MVideoWithFile } from '@server/typings/models' | ||
4 | |||
5 | type Use<K extends keyof VideoImportModel, M> = PickWith<VideoImportModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MVideoImport = Omit<VideoImportModel, 'User' | 'Video'> | ||
10 | |||
11 | export type MVideoImportVideo = MVideoImport & | ||
12 | Use<'Video', MVideo> | ||
13 | |||
14 | // ############################################################################ | ||
15 | |||
16 | type VideoAssociation = MVideoTag & MVideoAccountLight & MVideoThumbnail | ||
17 | |||
18 | export type MVideoImportDefault = MVideoImport & | ||
19 | Use<'User', MUser> & | ||
20 | Use<'Video', VideoAssociation> | ||
21 | |||
22 | export type MVideoImportDefaultFiles = MVideoImport & | ||
23 | Use<'User', MUser> & | ||
24 | Use<'Video', VideoAssociation & MVideoWithFile> | ||
25 | |||
26 | // ############################################################################ | ||
27 | |||
28 | // Format for API or AP object | ||
29 | |||
30 | export type MVideoImportFormattable = MVideoImport & | ||
31 | PickWithOpt<VideoImportModel, 'Video', MVideoFormattable & MVideoTag> | ||
diff --git a/server/typings/models/video/video-playlist-element.ts b/server/typings/models/video/video-playlist-element.ts new file mode 100644 index 000000000..7b1b993ce --- /dev/null +++ b/server/typings/models/video/video-playlist-element.ts | |||
@@ -0,0 +1,34 @@ | |||
1 | import { VideoPlaylistElementModel } from '@server/models/video/video-playlist-element' | ||
2 | import { PickWith } from '@server/typings/utils' | ||
3 | import { MVideoFormattable, MVideoPlaylistPrivacy, MVideoThumbnail, MVideoUrl } from '@server/typings/models' | ||
4 | |||
5 | type Use<K extends keyof VideoPlaylistElementModel, M> = PickWith<VideoPlaylistElementModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MVideoPlaylistElement = Omit<VideoPlaylistElementModel, 'VideoPlaylist' | 'Video'> | ||
10 | |||
11 | // ############################################################################ | ||
12 | |||
13 | export type MVideoPlaylistElementId = Pick<MVideoPlaylistElement, 'id'> | ||
14 | |||
15 | export type MVideoPlaylistElementLight = Pick<MVideoPlaylistElement, 'id' | 'videoId' | 'startTimestamp' | 'stopTimestamp'> | ||
16 | |||
17 | // ############################################################################ | ||
18 | |||
19 | export type MVideoPlaylistVideoThumbnail = MVideoPlaylistElement & | ||
20 | Use<'Video', MVideoThumbnail> | ||
21 | |||
22 | export type MVideoPlaylistElementVideoUrlPlaylistPrivacy = MVideoPlaylistElement & | ||
23 | Use<'Video', MVideoUrl> & | ||
24 | Use<'VideoPlaylist', MVideoPlaylistPrivacy> | ||
25 | |||
26 | // ############################################################################ | ||
27 | |||
28 | // Format for API or AP object | ||
29 | |||
30 | export type MVideoPlaylistElementFormattable = MVideoPlaylistElement & | ||
31 | Use<'Video', MVideoFormattable> | ||
32 | |||
33 | export type MVideoPlaylistElementAP = MVideoPlaylistElement & | ||
34 | Use<'Video', MVideoUrl> | ||
diff --git a/server/typings/models/video/video-playlist.ts b/server/typings/models/video/video-playlist.ts new file mode 100644 index 000000000..a40c7aca9 --- /dev/null +++ b/server/typings/models/video/video-playlist.ts | |||
@@ -0,0 +1,92 @@ | |||
1 | import { VideoPlaylistModel } from '../../../models/video/video-playlist' | ||
2 | import { PickWith } from '../../utils' | ||
3 | import { MAccount, MAccountDefault, MAccountSummary, MAccountSummaryFormattable } from '../account' | ||
4 | import { MThumbnail } from './thumbnail' | ||
5 | import { MChannelDefault, MChannelSummary, MChannelSummaryFormattable, MChannelUrl } from './video-channels' | ||
6 | import { MVideoPlaylistElementLight } from '@server/typings/models/video/video-playlist-element' | ||
7 | |||
8 | type Use<K extends keyof VideoPlaylistModel, M> = PickWith<VideoPlaylistModel, K, M> | ||
9 | |||
10 | // ############################################################################ | ||
11 | |||
12 | export type MVideoPlaylist = Omit<VideoPlaylistModel, 'OwnerAccount' | 'VideoChannel' | 'VideoPlaylistElements' | 'Thumbnail'> | ||
13 | |||
14 | // ############################################################################ | ||
15 | |||
16 | export type MVideoPlaylistId = Pick<MVideoPlaylist, 'id'> | ||
17 | export type MVideoPlaylistPrivacy = Pick<MVideoPlaylist, 'privacy'> | ||
18 | export type MVideoPlaylistUUID = Pick<MVideoPlaylist, 'uuid'> | ||
19 | export type MVideoPlaylistVideosLength = MVideoPlaylist & { videosLength?: number } | ||
20 | |||
21 | // ############################################################################ | ||
22 | |||
23 | // With elements | ||
24 | |||
25 | export type MVideoPlaylistWithElements = MVideoPlaylist & | ||
26 | Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]> | ||
27 | |||
28 | export type MVideoPlaylistIdWithElements = MVideoPlaylistId & | ||
29 | Use<'VideoPlaylistElements', MVideoPlaylistElementLight[]> | ||
30 | |||
31 | // ############################################################################ | ||
32 | |||
33 | // With account | ||
34 | |||
35 | export type MVideoPlaylistOwner = MVideoPlaylist & | ||
36 | Use<'OwnerAccount', MAccount> | ||
37 | |||
38 | export type MVideoPlaylistOwnerDefault = MVideoPlaylist & | ||
39 | Use<'OwnerAccount', MAccountDefault> | ||
40 | |||
41 | // ############################################################################ | ||
42 | |||
43 | // With thumbnail | ||
44 | |||
45 | export type MVideoPlaylistThumbnail = MVideoPlaylist & | ||
46 | Use<'Thumbnail', MThumbnail> | ||
47 | |||
48 | export type MVideoPlaylistAccountThumbnail = MVideoPlaylist & | ||
49 | Use<'OwnerAccount', MAccountDefault> & | ||
50 | Use<'Thumbnail', MThumbnail> | ||
51 | |||
52 | // ############################################################################ | ||
53 | |||
54 | // With channel | ||
55 | |||
56 | export type MVideoPlaylistAccountChannelDefault = MVideoPlaylist & | ||
57 | Use<'OwnerAccount', MAccountDefault> & | ||
58 | Use<'VideoChannel', MChannelDefault> | ||
59 | |||
60 | // ############################################################################ | ||
61 | |||
62 | // With all associations | ||
63 | |||
64 | export type MVideoPlaylistFull = MVideoPlaylist & | ||
65 | Use<'OwnerAccount', MAccountDefault> & | ||
66 | Use<'VideoChannel', MChannelDefault> & | ||
67 | Use<'Thumbnail', MThumbnail> | ||
68 | |||
69 | // ############################################################################ | ||
70 | |||
71 | // For API | ||
72 | |||
73 | export type MVideoPlaylistAccountChannelSummary = MVideoPlaylist & | ||
74 | Use<'OwnerAccount', MAccountSummary> & | ||
75 | Use<'VideoChannel', MChannelSummary> | ||
76 | |||
77 | export type MVideoPlaylistFullSummary = MVideoPlaylist & | ||
78 | Use<'Thumbnail', MThumbnail> & | ||
79 | Use<'OwnerAccount', MAccountSummary> & | ||
80 | Use<'VideoChannel', MChannelSummary> | ||
81 | |||
82 | // ############################################################################ | ||
83 | |||
84 | // Format for API or AP object | ||
85 | |||
86 | export type MVideoPlaylistFormattable = MVideoPlaylistVideosLength & | ||
87 | Use<'OwnerAccount', MAccountSummaryFormattable> & | ||
88 | Use<'VideoChannel', MChannelSummaryFormattable> | ||
89 | |||
90 | export type MVideoPlaylistAP = MVideoPlaylist & | ||
91 | Use<'Thumbnail', MThumbnail> & | ||
92 | Use<'VideoChannel', MChannelUrl> | ||
diff --git a/server/typings/models/video/video-rate.ts b/server/typings/models/video/video-rate.ts new file mode 100644 index 000000000..2ff8a625b --- /dev/null +++ b/server/typings/models/video/video-rate.ts | |||
@@ -0,0 +1,23 @@ | |||
1 | import { AccountVideoRateModel } from '@server/models/account/account-video-rate' | ||
2 | import { PickWith } from '@server/typings/utils' | ||
3 | import { MAccountAudience, MAccountUrl, MVideo, MVideoFormattable } from '..' | ||
4 | |||
5 | type Use<K extends keyof AccountVideoRateModel, M> = PickWith<AccountVideoRateModel, K, M> | ||
6 | |||
7 | // ############################################################################ | ||
8 | |||
9 | export type MAccountVideoRate = Omit<AccountVideoRateModel, 'Video' | 'Account'> | ||
10 | |||
11 | export type MAccountVideoRateAccountUrl = MAccountVideoRate & | ||
12 | Use<'Account', MAccountUrl> | ||
13 | |||
14 | export type MAccountVideoRateAccountVideo = MAccountVideoRate & | ||
15 | Use<'Account', MAccountAudience> & | ||
16 | Use<'Video', MVideo> | ||
17 | |||
18 | // ############################################################################ | ||
19 | |||
20 | // Format for API or AP object | ||
21 | |||
22 | export type MAccountVideoRateFormattable = Pick<MAccountVideoRate, 'type'> & | ||
23 | Use<'Video', MVideoFormattable> | ||
diff --git a/server/typings/models/video/video-redundancy.ts b/server/typings/models/video/video-redundancy.ts new file mode 100644 index 000000000..f3846afd7 --- /dev/null +++ b/server/typings/models/video/video-redundancy.ts | |||
@@ -0,0 +1,38 @@ | |||
1 | import { VideoRedundancyModel } from '../../../models/redundancy/video-redundancy' | ||
2 | import { PickWith, PickWithOpt } from '@server/typings/utils' | ||
3 | import { MStreamingPlaylistVideo, MVideoFile, MVideoFileVideo, MVideoUrl } from '@server/typings/models' | ||
4 | import { VideoStreamingPlaylist } from '../../../../shared/models/videos/video-streaming-playlist.model' | ||
5 | import { VideoStreamingPlaylistModel } from '@server/models/video/video-streaming-playlist' | ||
6 | import { VideoFile } from '../../../../shared/models/videos' | ||
7 | import { VideoFileModel } from '@server/models/video/video-file' | ||
8 | |||
9 | type Use<K extends keyof VideoRedundancyModel, M> = PickWith<VideoRedundancyModel, K, M> | ||
10 | |||
11 | // ############################################################################ | ||
12 | |||
13 | export type MVideoRedundancy = Omit<VideoRedundancyModel, 'VideoFile' | 'VideoStreamingPlaylist' | 'Actor'> | ||
14 | |||
15 | export type MVideoRedundancyFileUrl = Pick<MVideoRedundancy, 'fileUrl'> | ||
16 | |||
17 | // ############################################################################ | ||
18 | |||
19 | export type MVideoRedundancyFile = MVideoRedundancy & | ||
20 | Use<'VideoFile', MVideoFile> | ||
21 | |||
22 | export type MVideoRedundancyFileVideo = MVideoRedundancy & | ||
23 | Use<'VideoFile', MVideoFileVideo> | ||
24 | |||
25 | export type MVideoRedundancyStreamingPlaylistVideo = MVideoRedundancy & | ||
26 | Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> | ||
27 | |||
28 | export type MVideoRedundancyVideo = MVideoRedundancy & | ||
29 | Use<'VideoFile', MVideoFileVideo> & | ||
30 | Use<'VideoStreamingPlaylist', MStreamingPlaylistVideo> | ||
31 | |||
32 | // ############################################################################ | ||
33 | |||
34 | // Format for API or AP object | ||
35 | |||
36 | export type MVideoRedundancyAP = MVideoRedundancy & | ||
37 | PickWithOpt<VideoRedundancyModel, 'VideoFile', MVideoFile & PickWith<VideoFileModel, 'Video', MVideoUrl>> & | ||
38 | PickWithOpt<VideoRedundancyModel, 'VideoStreamingPlaylist', PickWith<VideoStreamingPlaylistModel, 'Video', MVideoUrl>> | ||
diff --git a/server/typings/models/video/video-share.ts b/server/typings/models/video/video-share.ts new file mode 100644 index 000000000..a7a90beeb --- /dev/null +++ b/server/typings/models/video/video-share.ts | |||
@@ -0,0 +1,17 @@ | |||
1 | import { VideoShareModel } from '../../../models/video/video-share' | ||
2 | import { PickWith } from '../../utils' | ||
3 | import { MActorDefault } from '../account' | ||
4 | import { MVideo } from './video' | ||
5 | |||
6 | type Use<K extends keyof VideoShareModel, M> = PickWith<VideoShareModel, K, M> | ||
7 | |||
8 | // ############################################################################ | ||
9 | |||
10 | export type MVideoShare = Omit<VideoShareModel, 'Actor' | 'Video'> | ||
11 | |||
12 | export type MVideoShareActor = MVideoShare & | ||
13 | Use<'Actor', MActorDefault> | ||
14 | |||
15 | export type MVideoShareFull = MVideoShare & | ||
16 | Use<'Actor', MActorDefault> & | ||
17 | Use<'Video', MVideo> | ||
diff --git a/server/typings/models/video/video-streaming-playlist.ts b/server/typings/models/video/video-streaming-playlist.ts new file mode 100644 index 000000000..79696bcff --- /dev/null +++ b/server/typings/models/video/video-streaming-playlist.ts | |||
@@ -0,0 +1,19 @@ | |||
1 | import { VideoStreamingPlaylistModel } from '../../../models/video/video-streaming-playlist' | ||
2 | import { PickWith, PickWithOpt } from '../../utils' | ||
3 | import { MVideoRedundancyFileUrl } from './video-redundancy' | ||
4 | import { MVideo, MVideoUrl } from '@server/typings/models' | ||
5 | |||
6 | type Use<K extends keyof VideoStreamingPlaylistModel, M> = PickWith<VideoStreamingPlaylistModel, K, M> | ||
7 | |||
8 | // ############################################################################ | ||
9 | |||
10 | export type MStreamingPlaylist = Omit<VideoStreamingPlaylistModel, 'Video' | 'RedundancyVideos'> | ||
11 | |||
12 | export type MStreamingPlaylistVideo = MStreamingPlaylist & | ||
13 | Use<'Video', MVideo> | ||
14 | |||
15 | export type MStreamingPlaylistRedundancies = MStreamingPlaylist & | ||
16 | Use<'RedundancyVideos', MVideoRedundancyFileUrl[]> | ||
17 | |||
18 | export type MStreamingPlaylistRedundanciesOpt = MStreamingPlaylist & | ||
19 | PickWithOpt<VideoStreamingPlaylistModel, 'RedundancyVideos', MVideoRedundancyFileUrl[]> | ||
diff --git a/server/typings/models/video/video.ts b/server/typings/models/video/video.ts new file mode 100644 index 000000000..9a53bd337 --- /dev/null +++ b/server/typings/models/video/video.ts | |||
@@ -0,0 +1,173 @@ | |||
1 | import { VideoModel } from '../../../models/video/video' | ||
2 | import { PickWith, PickWithOpt } from '../../utils' | ||
3 | import { | ||
4 | MChannelAccountDefault, | ||
5 | MChannelAccountLight, | ||
6 | MChannelAccountSummaryFormattable, | ||
7 | MChannelActor, | ||
8 | MChannelFormattable, | ||
9 | MChannelUserId | ||
10 | } from './video-channels' | ||
11 | import { MTag } from './tag' | ||
12 | import { MVideoCaptionLanguage } from './video-caption' | ||
13 | import { MStreamingPlaylist, MStreamingPlaylistRedundancies, MStreamingPlaylistRedundanciesOpt } from './video-streaming-playlist' | ||
14 | import { MVideoFile, MVideoFileRedundanciesOpt } from './video-file' | ||
15 | import { MThumbnail } from './thumbnail' | ||
16 | import { MVideoBlacklist, MVideoBlacklistLight, MVideoBlacklistUnfederated } from './video-blacklist' | ||
17 | import { MScheduleVideoUpdate } from './schedule-video-update' | ||
18 | import { MUserVideoHistoryTime } from '../user/user-video-history' | ||
19 | |||
20 | type Use<K extends keyof VideoModel, M> = PickWith<VideoModel, K, M> | ||
21 | |||
22 | // ############################################################################ | ||
23 | |||
24 | export type MVideo = Omit<VideoModel, 'VideoChannel' | 'Tags' | 'Thumbnails' | 'VideoPlaylistElements' | 'VideoAbuses' | | ||
25 | 'VideoFiles' | 'VideoStreamingPlaylists' | 'VideoShares' | 'AccountVideoRates' | 'VideoComments' | 'VideoViews' | 'UserVideoHistories' | | ||
26 | 'ScheduleVideoUpdate' | 'VideoBlacklist' | 'VideoImport' | 'VideoCaptions'> | ||
27 | |||
28 | // ############################################################################ | ||
29 | |||
30 | export type MVideoId = Pick<MVideo, 'id'> | ||
31 | export type MVideoUrl = Pick<MVideo, 'url'> | ||
32 | export type MVideoUUID = Pick<MVideo, 'uuid'> | ||
33 | |||
34 | export type MVideoIdUrl = MVideoId & MVideoUrl | ||
35 | export type MVideoFeed = Pick<MVideo, 'name' | 'uuid'> | ||
36 | |||
37 | // ############################################################################ | ||
38 | |||
39 | // Video raw associations: schedules, video files, tags, thumbnails, captions, streaming playlists | ||
40 | |||
41 | // "With" to not confuse with the VideoFile model | ||
42 | export type MVideoWithFile = MVideo & | ||
43 | Use<'VideoFiles', MVideoFile[]> | ||
44 | |||
45 | export type MVideoThumbnail = MVideo & | ||
46 | Use<'Thumbnails', MThumbnail[]> | ||
47 | |||
48 | export type MVideoIdThumbnail = MVideoId & | ||
49 | Use<'Thumbnails', MThumbnail[]> | ||
50 | |||
51 | export type MVideoWithFileThumbnail = MVideo & | ||
52 | Use<'VideoFiles', MVideoFile[]> & | ||
53 | Use<'Thumbnails', MThumbnail[]> | ||
54 | |||
55 | export type MVideoThumbnailBlacklist = MVideo & | ||
56 | Use<'Thumbnails', MThumbnail[]> & | ||
57 | Use<'VideoBlacklist', MVideoBlacklistLight> | ||
58 | |||
59 | export type MVideoTag = MVideo & | ||
60 | Use<'Tags', MTag[]> | ||
61 | |||
62 | export type MVideoWithSchedule = MVideo & | ||
63 | PickWithOpt<VideoModel, 'ScheduleVideoUpdate', MScheduleVideoUpdate> | ||
64 | |||
65 | export type MVideoWithCaptions = MVideo & | ||
66 | Use<'VideoCaptions', MVideoCaptionLanguage[]> | ||
67 | |||
68 | export type MVideoWithStreamingPlaylist = MVideo & | ||
69 | Use<'VideoStreamingPlaylists', MStreamingPlaylist[]> | ||
70 | |||
71 | // ############################################################################ | ||
72 | |||
73 | // Associations with not all their attributes | ||
74 | |||
75 | export type MVideoUserHistory = MVideo & | ||
76 | Use<'UserVideoHistories', MUserVideoHistoryTime[]> | ||
77 | |||
78 | export type MVideoWithBlacklistLight = MVideo & | ||
79 | Use<'VideoBlacklist', MVideoBlacklistLight> | ||
80 | |||
81 | export type MVideoAccountLight = MVideo & | ||
82 | Use<'VideoChannel', MChannelAccountLight> | ||
83 | |||
84 | export type MVideoWithRights = MVideo & | ||
85 | Use<'VideoBlacklist', MVideoBlacklistLight> & | ||
86 | Use<'Thumbnails', MThumbnail[]> & | ||
87 | Use<'VideoChannel', MChannelUserId> | ||
88 | |||
89 | // ############################################################################ | ||
90 | |||
91 | // All files with some additional associations | ||
92 | |||
93 | export type MVideoWithAllFiles = MVideo & | ||
94 | Use<'VideoFiles', MVideoFile[]> & | ||
95 | Use<'Thumbnails', MThumbnail[]> & | ||
96 | Use<'VideoStreamingPlaylists', MStreamingPlaylist[]> | ||
97 | |||
98 | export type MVideoAccountLightBlacklistAllFiles = MVideo & | ||
99 | Use<'VideoFiles', MVideoFile[]> & | ||
100 | Use<'Thumbnails', MThumbnail[]> & | ||
101 | Use<'VideoStreamingPlaylists', MStreamingPlaylist[]> & | ||
102 | Use<'VideoChannel', MChannelAccountLight> & | ||
103 | Use<'VideoBlacklist', MVideoBlacklistLight> | ||
104 | |||
105 | // ############################################################################ | ||
106 | |||
107 | // With account | ||
108 | |||
109 | export type MVideoAccountDefault = MVideo & | ||
110 | Use<'VideoChannel', MChannelAccountDefault> | ||
111 | |||
112 | export type MVideoThumbnailAccountDefault = MVideo & | ||
113 | Use<'Thumbnails', MThumbnail[]> & | ||
114 | Use<'VideoChannel', MChannelAccountDefault> | ||
115 | |||
116 | export type MVideoWithChannelActor = MVideo & | ||
117 | Use<'VideoChannel', MChannelActor> | ||
118 | |||
119 | export type MVideoFullLight = MVideo & | ||
120 | Use<'Thumbnails', MThumbnail[]> & | ||
121 | Use<'VideoBlacklist', MVideoBlacklistLight> & | ||
122 | Use<'Tags', MTag[]> & | ||
123 | Use<'VideoChannel', MChannelAccountLight> & | ||
124 | Use<'UserVideoHistories', MUserVideoHistoryTime[]> & | ||
125 | Use<'VideoFiles', MVideoFile[]> & | ||
126 | Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> & | ||
127 | Use<'VideoStreamingPlaylists', MStreamingPlaylist[]> | ||
128 | |||
129 | // ############################################################################ | ||
130 | |||
131 | // API | ||
132 | |||
133 | export type MVideoAP = MVideo & | ||
134 | Use<'Tags', MTag[]> & | ||
135 | Use<'VideoChannel', MChannelAccountLight> & | ||
136 | Use<'VideoStreamingPlaylists', MStreamingPlaylist[]> & | ||
137 | Use<'VideoCaptions', MVideoCaptionLanguage[]> & | ||
138 | Use<'VideoBlacklist', MVideoBlacklistUnfederated> & | ||
139 | Use<'VideoFiles', MVideoFileRedundanciesOpt[]> | ||
140 | |||
141 | export type MVideoAPWithoutCaption = Omit<MVideoAP, 'VideoCaptions'> | ||
142 | |||
143 | export type MVideoDetails = MVideo & | ||
144 | Use<'VideoBlacklist', MVideoBlacklistLight> & | ||
145 | Use<'Tags', MTag[]> & | ||
146 | Use<'VideoChannel', MChannelAccountLight> & | ||
147 | Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> & | ||
148 | Use<'Thumbnails', MThumbnail[]> & | ||
149 | Use<'UserVideoHistories', MUserVideoHistoryTime[]> & | ||
150 | Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundancies[]> & | ||
151 | Use<'VideoFiles', MVideoFileRedundanciesOpt[]> | ||
152 | |||
153 | export type MVideoForUser = MVideo & | ||
154 | Use<'VideoChannel', MChannelAccountDefault> & | ||
155 | Use<'ScheduleVideoUpdate', MScheduleVideoUpdate> & | ||
156 | Use<'VideoBlacklist', MVideoBlacklistLight> & | ||
157 | Use<'Thumbnails', MThumbnail[]> | ||
158 | |||
159 | // ############################################################################ | ||
160 | |||
161 | // Format for API or AP object | ||
162 | |||
163 | export type MVideoFormattable = MVideo & | ||
164 | PickWithOpt<VideoModel, 'UserVideoHistories', MUserVideoHistoryTime[]> & | ||
165 | Use<'VideoChannel', MChannelAccountSummaryFormattable> & | ||
166 | PickWithOpt<VideoModel, 'ScheduleVideoUpdate', Pick<MScheduleVideoUpdate, 'updateAt' | 'privacy'>> & | ||
167 | PickWithOpt<VideoModel, 'VideoBlacklist', Pick<MVideoBlacklist, 'reason'>> | ||
168 | |||
169 | export type MVideoFormattableDetails = MVideoFormattable & | ||
170 | Use<'VideoChannel', MChannelFormattable> & | ||
171 | Use<'Tags', MTag[]> & | ||
172 | Use<'VideoStreamingPlaylists', MStreamingPlaylistRedundanciesOpt[]> & | ||
173 | Use<'VideoFiles', MVideoFileRedundanciesOpt[]> | ||
diff --git a/server/typings/utils.ts b/server/typings/utils.ts index a86b05be2..24d43b258 100644 --- a/server/typings/utils.ts +++ b/server/typings/utils.ts | |||
@@ -1,3 +1,22 @@ | |||
1 | export type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never }[keyof T] | 1 | export type FunctionPropertyNames<T> = { |
2 | [K in keyof T]: T[K] extends Function ? K : never | ||
3 | }[keyof T] | ||
2 | 4 | ||
3 | export type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>> | 5 | export type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>> |
6 | |||
7 | export type PickWith<T, KT extends keyof T, V> = { | ||
8 | [P in KT]: T[P] extends V ? V : never | ||
9 | } | ||
10 | |||
11 | export type PickWithOpt<T, KT extends keyof T, V> = { | ||
12 | [P in KT]?: T[P] extends V ? V : never | ||
13 | } | ||
14 | |||
15 | // https://github.com/krzkaczor/ts-essentials Rocks! | ||
16 | export type DeepPartial<T> = { | ||
17 | [P in keyof T]?: T[P] extends Array<infer U> | ||
18 | ? Array<DeepPartial<U>> | ||
19 | : T[P] extends ReadonlyArray<infer U> | ||
20 | ? ReadonlyArray<DeepPartial<U>> | ||
21 | : DeepPartial<T[P]> | ||
22 | } | ||
diff --git a/shared/extra-utils/index.ts b/shared/extra-utils/index.ts index 53ddaa681..78acf72aa 100644 --- a/shared/extra-utils/index.ts +++ b/shared/extra-utils/index.ts | |||
@@ -24,4 +24,5 @@ export * from './videos/video-streaming-playlists' | |||
24 | export * from './videos/videos' | 24 | export * from './videos/videos' |
25 | export * from './videos/video-change-ownership' | 25 | export * from './videos/video-change-ownership' |
26 | export * from './feeds/feeds' | 26 | export * from './feeds/feeds' |
27 | export * from './instances-index/mock-instances-index' | ||
27 | export * from './search/videos' | 28 | export * from './search/videos' |
diff --git a/shared/extra-utils/instances-index/mock-instances-index.ts b/shared/extra-utils/instances-index/mock-instances-index.ts new file mode 100644 index 000000000..cfa4523c1 --- /dev/null +++ b/shared/extra-utils/instances-index/mock-instances-index.ts | |||
@@ -0,0 +1,38 @@ | |||
1 | import * as express from 'express' | ||
2 | |||
3 | export class MockInstancesIndex { | ||
4 | private indexInstances: { host: string, createdAt: string }[] = [] | ||
5 | |||
6 | initialize () { | ||
7 | return new Promise(res => { | ||
8 | const app = express() | ||
9 | |||
10 | app.use('/', (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
11 | if (process.env.DEBUG) console.log('Receiving request on mocked server %s.', req.url) | ||
12 | |||
13 | return next() | ||
14 | }) | ||
15 | |||
16 | app.get('/api/v1/instances/hosts', (req: express.Request, res: express.Response) => { | ||
17 | const since = req.query.since | ||
18 | |||
19 | const filtered = this.indexInstances.filter(i => { | ||
20 | if (!since) return true | ||
21 | |||
22 | return i.createdAt > since | ||
23 | }) | ||
24 | |||
25 | return res.json({ | ||
26 | total: filtered.length, | ||
27 | data: filtered | ||
28 | }) | ||
29 | }) | ||
30 | |||
31 | app.listen(42100, () => res()) | ||
32 | }) | ||
33 | } | ||
34 | |||
35 | addInstance (host: string) { | ||
36 | this.indexInstances.push({ host, createdAt: new Date().toISOString() }) | ||
37 | } | ||
38 | } | ||
diff --git a/shared/extra-utils/server/config.ts b/shared/extra-utils/server/config.ts index 8736f083f..578dd35cf 100644 --- a/shared/extra-utils/server/config.ts +++ b/shared/extra-utils/server/config.ts | |||
@@ -1,5 +1,7 @@ | |||
1 | import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../requests/requests' | 1 | import { makeDeleteRequest, makeGetRequest, makePutBodyRequest } from '../requests/requests' |
2 | import { CustomConfig } from '../../models/server/custom-config.model' | 2 | import { CustomConfig } from '../../models/server/custom-config.model' |
3 | import { DeepPartial } from '@server/typings/utils' | ||
4 | import { merge } from 'lodash' | ||
3 | 5 | ||
4 | function getConfig (url: string) { | 6 | function getConfig (url: string) { |
5 | const path = '/api/v1/config' | 7 | const path = '/api/v1/config' |
@@ -44,13 +46,25 @@ function updateCustomConfig (url: string, token: string, newCustomConfig: Custom | |||
44 | }) | 46 | }) |
45 | } | 47 | } |
46 | 48 | ||
47 | function updateCustomSubConfig (url: string, token: string, newConfig: any) { | 49 | function updateCustomSubConfig (url: string, token: string, newConfig: DeepPartial<CustomConfig>) { |
48 | const updateParams: CustomConfig = { | 50 | const updateParams: CustomConfig = { |
49 | instance: { | 51 | instance: { |
50 | name: 'PeerTube updated', | 52 | name: 'PeerTube updated', |
51 | shortDescription: 'my short description', | 53 | shortDescription: 'my short description', |
52 | description: 'my super description', | 54 | description: 'my super description', |
53 | terms: 'my super terms', | 55 | terms: 'my super terms', |
56 | codeOfConduct: 'my super coc', | ||
57 | |||
58 | creationReason: 'my super creation reason', | ||
59 | moderationInformation: 'my super moderation information', | ||
60 | administrator: 'Kuja', | ||
61 | maintenanceLifetime: 'forever', | ||
62 | businessModel: 'my super business model', | ||
63 | hardwareInformation: '2vCore 3GB RAM', | ||
64 | |||
65 | languages: [ 'en', 'es' ], | ||
66 | categories: [ 1, 2 ], | ||
67 | |||
54 | defaultClientRoute: '/videos/recently-added', | 68 | defaultClientRoute: '/videos/recently-added', |
55 | isNSFW: true, | 69 | isNSFW: true, |
56 | defaultNSFWPolicy: 'blur', | 70 | defaultNSFWPolicy: 'blur', |
@@ -130,10 +144,21 @@ function updateCustomSubConfig (url: string, token: string, newConfig: any) { | |||
130 | enabled: true, | 144 | enabled: true, |
131 | manualApproval: false | 145 | manualApproval: false |
132 | } | 146 | } |
147 | }, | ||
148 | followings: { | ||
149 | instance: { | ||
150 | autoFollowBack: { | ||
151 | enabled: false | ||
152 | }, | ||
153 | autoFollowIndex: { | ||
154 | indexUrl: 'https://instances.joinpeertube.org', | ||
155 | enabled: false | ||
156 | } | ||
157 | } | ||
133 | } | 158 | } |
134 | } | 159 | } |
135 | 160 | ||
136 | Object.assign(updateParams, newConfig) | 161 | merge(updateParams, newConfig) |
137 | 162 | ||
138 | return updateCustomConfig(url, token, updateParams) | 163 | return updateCustomConfig(url, token, updateParams) |
139 | } | 164 | } |
diff --git a/shared/extra-utils/users/user-notifications.ts b/shared/extra-utils/users/user-notifications.ts index f7de542bf..9a5fd7e86 100644 --- a/shared/extra-utils/users/user-notifications.ts +++ b/shared/extra-utils/users/user-notifications.ts | |||
@@ -279,8 +279,9 @@ async function checkNewActorFollow ( | |||
279 | expect(notification.actorFollow.follower.name).to.equal(followerName) | 279 | expect(notification.actorFollow.follower.name).to.equal(followerName) |
280 | expect(notification.actorFollow.follower.host).to.not.be.undefined | 280 | expect(notification.actorFollow.follower.host).to.not.be.undefined |
281 | 281 | ||
282 | expect(notification.actorFollow.following.displayName).to.equal(followingDisplayName) | 282 | const following = notification.actorFollow.following |
283 | expect(notification.actorFollow.following.type).to.equal(followType) | 283 | expect(following.displayName).to.equal(followingDisplayName) |
284 | expect(following.type).to.equal(followType) | ||
284 | } else { | 285 | } else { |
285 | expect(notification).to.satisfy(n => { | 286 | expect(notification).to.satisfy(n => { |
286 | return n.type !== notificationType || | 287 | return n.type !== notificationType || |
@@ -327,6 +328,37 @@ async function checkNewInstanceFollower (base: CheckerBaseParams, followerHost: | |||
327 | await checkNotification(base, notificationChecker, emailFinder, type) | 328 | await checkNotification(base, notificationChecker, emailFinder, type) |
328 | } | 329 | } |
329 | 330 | ||
331 | async function checkAutoInstanceFollowing (base: CheckerBaseParams, followerHost: string, followingHost: string, type: CheckerType) { | ||
332 | const notificationType = UserNotificationType.AUTO_INSTANCE_FOLLOWING | ||
333 | |||
334 | function notificationChecker (notification: UserNotification, type: CheckerType) { | ||
335 | if (type === 'presence') { | ||
336 | expect(notification).to.not.be.undefined | ||
337 | expect(notification.type).to.equal(notificationType) | ||
338 | |||
339 | const following = notification.actorFollow.following | ||
340 | checkActor(following) | ||
341 | expect(following.name).to.equal('peertube') | ||
342 | expect(following.host).to.equal(followingHost) | ||
343 | |||
344 | expect(notification.actorFollow.follower.name).to.equal('peertube') | ||
345 | expect(notification.actorFollow.follower.host).to.equal(followerHost) | ||
346 | } else { | ||
347 | expect(notification).to.satisfy(n => { | ||
348 | return n.type !== notificationType || n.actorFollow.following.host !== followingHost | ||
349 | }) | ||
350 | } | ||
351 | } | ||
352 | |||
353 | function emailFinder (email: object) { | ||
354 | const text: string = email[ 'text' ] | ||
355 | |||
356 | return text.includes(' automatically followed a new instance') && text.includes(followingHost) | ||
357 | } | ||
358 | |||
359 | await checkNotification(base, notificationChecker, emailFinder, type) | ||
360 | } | ||
361 | |||
330 | async function checkCommentMention ( | 362 | async function checkCommentMention ( |
331 | base: CheckerBaseParams, | 363 | base: CheckerBaseParams, |
332 | uuid: string, | 364 | uuid: string, |
@@ -427,8 +459,8 @@ async function checkVideoAutoBlacklistForModerators (base: CheckerBaseParams, vi | |||
427 | expect(notification).to.not.be.undefined | 459 | expect(notification).to.not.be.undefined |
428 | expect(notification.type).to.equal(notificationType) | 460 | expect(notification.type).to.equal(notificationType) |
429 | 461 | ||
430 | expect(notification.video.id).to.be.a('number') | 462 | expect(notification.videoBlacklist.video.id).to.be.a('number') |
431 | checkVideo(notification.video, videoName, videoUUID) | 463 | checkVideo(notification.videoBlacklist.video, videoName, videoUUID) |
432 | } else { | 464 | } else { |
433 | expect(notification).to.satisfy((n: UserNotification) => { | 465 | expect(notification).to.satisfy((n: UserNotification) => { |
434 | return n === undefined || n.video === undefined || n.video.uuid !== videoUUID | 466 | return n === undefined || n.video === undefined || n.video.uuid !== videoUUID |
@@ -480,6 +512,7 @@ export { | |||
480 | markAsReadAllNotifications, | 512 | markAsReadAllNotifications, |
481 | checkMyVideoImportIsFinished, | 513 | checkMyVideoImportIsFinished, |
482 | checkUserRegistered, | 514 | checkUserRegistered, |
515 | checkAutoInstanceFollowing, | ||
483 | checkVideoIsPublished, | 516 | checkVideoIsPublished, |
484 | checkNewVideoFromSubscription, | 517 | checkNewVideoFromSubscription, |
485 | checkNewActorFollow, | 518 | checkNewActorFollow, |
diff --git a/shared/extra-utils/users/users.ts b/shared/extra-utils/users/users.ts index 30ed1bf4a..9959fd074 100644 --- a/shared/extra-utils/users/users.ts +++ b/shared/extra-utils/users/users.ts | |||
@@ -1,12 +1,12 @@ | |||
1 | import * as request from 'supertest' | 1 | import * as request from 'supertest' |
2 | import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests' | 2 | import { makePostBodyRequest, makePutBodyRequest, updateAvatarRequest } from '../requests/requests' |
3 | import { NSFWPolicyType } from '../../models/videos/nsfw-policy.type' | ||
4 | import { UserAdminFlag } from '../../models/users/user-flag.model' | 3 | import { UserAdminFlag } from '../../models/users/user-flag.model' |
5 | import { UserRegister } from '../../models/users/user-register.model' | 4 | import { UserRegister } from '../../models/users/user-register.model' |
6 | import { UserRole } from '../../models/users/user-role' | 5 | import { UserRole } from '../../models/users/user-role' |
7 | import { ServerInfo } from '../server/servers' | 6 | import { ServerInfo } from '../server/servers' |
8 | import { userLogin } from './login' | 7 | import { userLogin } from './login' |
9 | import { UserUpdateMe } from '../../models/users' | 8 | import { UserUpdateMe } from '../../models/users' |
9 | import { omit } from 'lodash' | ||
10 | 10 | ||
11 | type CreateUserArgs = { url: string, | 11 | type CreateUserArgs = { url: string, |
12 | accessToken: string, | 12 | accessToken: string, |
@@ -214,33 +214,10 @@ function unblockUser (url: string, userId: number | string, accessToken: string, | |||
214 | .expect(expectedStatus) | 214 | .expect(expectedStatus) |
215 | } | 215 | } |
216 | 216 | ||
217 | function updateMyUser (options: { | 217 | function updateMyUser (options: { url: string, accessToken: string } & UserUpdateMe) { |
218 | url: string | ||
219 | accessToken: string | ||
220 | currentPassword?: string | ||
221 | newPassword?: string | ||
222 | nsfwPolicy?: NSFWPolicyType | ||
223 | email?: string | ||
224 | autoPlayVideo?: boolean | ||
225 | displayName?: string | ||
226 | description?: string | ||
227 | videosHistoryEnabled?: boolean | ||
228 | theme?: string | ||
229 | }) { | ||
230 | const path = '/api/v1/users/me' | 218 | const path = '/api/v1/users/me' |
231 | 219 | ||
232 | const toSend: UserUpdateMe = {} | 220 | const toSend: UserUpdateMe = omit(options, 'url', 'accessToken') |
233 | if (options.currentPassword !== undefined && options.currentPassword !== null) toSend.currentPassword = options.currentPassword | ||
234 | if (options.newPassword !== undefined && options.newPassword !== null) toSend.password = options.newPassword | ||
235 | if (options.nsfwPolicy !== undefined && options.nsfwPolicy !== null) toSend.nsfwPolicy = options.nsfwPolicy | ||
236 | if (options.autoPlayVideo !== undefined && options.autoPlayVideo !== null) toSend.autoPlayVideo = options.autoPlayVideo | ||
237 | if (options.email !== undefined && options.email !== null) toSend.email = options.email | ||
238 | if (options.description !== undefined && options.description !== null) toSend.description = options.description | ||
239 | if (options.displayName !== undefined && options.displayName !== null) toSend.displayName = options.displayName | ||
240 | if (options.theme !== undefined && options.theme !== null) toSend.theme = options.theme | ||
241 | if (options.videosHistoryEnabled !== undefined && options.videosHistoryEnabled !== null) { | ||
242 | toSend.videosHistoryEnabled = options.videosHistoryEnabled | ||
243 | } | ||
244 | 221 | ||
245 | return makePutBodyRequest({ | 222 | return makePutBodyRequest({ |
246 | url: options.url, | 223 | url: options.url, |
diff --git a/shared/models/activitypub/activity.ts b/shared/models/activitypub/activity.ts index 95801190d..492b672c7 100644 --- a/shared/models/activitypub/activity.ts +++ b/shared/models/activitypub/activity.ts | |||
@@ -91,5 +91,5 @@ export interface ActivityDislike extends BaseActivity { | |||
91 | export interface ActivityFlag extends BaseActivity { | 91 | export interface ActivityFlag extends BaseActivity { |
92 | type: 'Flag', | 92 | type: 'Flag', |
93 | content: string, | 93 | content: string, |
94 | object: APObject | 94 | object: APObject | APObject[] |
95 | } | 95 | } |
diff --git a/shared/models/activitypub/objects/video-abuse-object.ts b/shared/models/activitypub/objects/video-abuse-object.ts index 40e7abd57..5f1264a76 100644 --- a/shared/models/activitypub/objects/video-abuse-object.ts +++ b/shared/models/activitypub/objects/video-abuse-object.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | export interface VideoAbuseObject { | 1 | export interface VideoAbuseObject { |
2 | type: 'Flag', | 2 | type: 'Flag', |
3 | content: string | 3 | content: string |
4 | object: string | 4 | object: string | string[] |
5 | } | 5 | } |
diff --git a/shared/models/i18n/i18n.ts b/shared/models/i18n/i18n.ts index 218fd09ba..03a5d858a 100644 --- a/shared/models/i18n/i18n.ts +++ b/shared/models/i18n/i18n.ts | |||
@@ -39,7 +39,9 @@ const I18N_LOCALE_ALIAS = { | |||
39 | 'pl': 'pl-PL', | 39 | 'pl': 'pl-PL', |
40 | 'ru': 'ru-RU', | 40 | 'ru': 'ru-RU', |
41 | 'nl': 'nl-NL', | 41 | 'nl': 'nl-NL', |
42 | 'zh': 'zh-Hans-CN' | 42 | 'zh': 'zh-Hans-CN', |
43 | 'zh-CN': 'zh-Hans-CN', | ||
44 | 'zh-TW': 'zh-Hant-TW' | ||
43 | } | 45 | } |
44 | 46 | ||
45 | export const POSSIBLE_LOCALES = Object.keys(I18N_LOCALES) | 47 | export const POSSIBLE_LOCALES = Object.keys(I18N_LOCALES) |
diff --git a/shared/models/server/about.model.ts b/shared/models/server/about.model.ts index 10dff8b8f..6d4ba63c4 100644 --- a/shared/models/server/about.model.ts +++ b/shared/models/server/about.model.ts | |||
@@ -4,5 +4,17 @@ export interface About { | |||
4 | shortDescription: string | 4 | shortDescription: string |
5 | description: string | 5 | description: string |
6 | terms: string | 6 | terms: string |
7 | |||
8 | codeOfConduct: string | ||
9 | hardwareInformation: string | ||
10 | |||
11 | creationReason: string | ||
12 | moderationInformation: string | ||
13 | administrator: string | ||
14 | maintenanceLifetime: string | ||
15 | businessModel: string | ||
16 | |||
17 | languages: string[] | ||
18 | categories: number[] | ||
7 | } | 19 | } |
8 | } | 20 | } |
diff --git a/shared/models/server/custom-config.model.ts b/shared/models/server/custom-config.model.ts index a0541f5b6..c9957f825 100644 --- a/shared/models/server/custom-config.model.ts +++ b/shared/models/server/custom-config.model.ts | |||
@@ -6,6 +6,18 @@ export interface CustomConfig { | |||
6 | shortDescription: string | 6 | shortDescription: string |
7 | description: string | 7 | description: string |
8 | terms: string | 8 | terms: string |
9 | codeOfConduct: string | ||
10 | |||
11 | creationReason: string | ||
12 | moderationInformation: string | ||
13 | administrator: string | ||
14 | maintenanceLifetime: string | ||
15 | businessModel: string | ||
16 | hardwareInformation: string | ||
17 | |||
18 | languages: string[] | ||
19 | categories: number[] | ||
20 | |||
9 | isNSFW: boolean | 21 | isNSFW: boolean |
10 | defaultClientRoute: string | 22 | defaultClientRoute: string |
11 | defaultNSFWPolicy: NSFWPolicyType | 23 | defaultNSFWPolicy: NSFWPolicyType |
@@ -99,4 +111,16 @@ export interface CustomConfig { | |||
99 | } | 111 | } |
100 | } | 112 | } |
101 | 113 | ||
114 | followings: { | ||
115 | instance: { | ||
116 | autoFollowBack: { | ||
117 | enabled: boolean | ||
118 | } | ||
119 | |||
120 | autoFollowIndex: { | ||
121 | enabled: boolean | ||
122 | indexUrl: string | ||
123 | } | ||
124 | } | ||
125 | } | ||
102 | } | 126 | } |
diff --git a/shared/models/users/user-notification-setting.model.ts b/shared/models/users/user-notification-setting.model.ts index e2a882b69..451f40d58 100644 --- a/shared/models/users/user-notification-setting.model.ts +++ b/shared/models/users/user-notification-setting.model.ts | |||
@@ -16,4 +16,5 @@ export interface UserNotificationSetting { | |||
16 | newFollow: UserNotificationSettingValue | 16 | newFollow: UserNotificationSettingValue |
17 | commentMention: UserNotificationSettingValue | 17 | commentMention: UserNotificationSettingValue |
18 | newInstanceFollower: UserNotificationSettingValue | 18 | newInstanceFollower: UserNotificationSettingValue |
19 | autoInstanceFollowing: UserNotificationSettingValue | ||
19 | } | 20 | } |
diff --git a/shared/models/users/user-notification.model.ts b/shared/models/users/user-notification.model.ts index fafc2b7d7..e9be1ca7f 100644 --- a/shared/models/users/user-notification.model.ts +++ b/shared/models/users/user-notification.model.ts | |||
@@ -19,7 +19,9 @@ export enum UserNotificationType { | |||
19 | 19 | ||
20 | VIDEO_AUTO_BLACKLIST_FOR_MODERATORS = 12, | 20 | VIDEO_AUTO_BLACKLIST_FOR_MODERATORS = 12, |
21 | 21 | ||
22 | NEW_INSTANCE_FOLLOWER = 13 | 22 | NEW_INSTANCE_FOLLOWER = 13, |
23 | |||
24 | AUTO_INSTANCE_FOLLOWING = 14 | ||
23 | } | 25 | } |
24 | 26 | ||
25 | export interface VideoInfo { | 27 | export interface VideoInfo { |
@@ -78,10 +80,12 @@ export interface UserNotification { | |||
78 | id: number | 80 | id: number |
79 | follower: ActorInfo | 81 | follower: ActorInfo |
80 | state: FollowState | 82 | state: FollowState |
83 | |||
81 | following: { | 84 | following: { |
82 | type: 'account' | 'channel' | 85 | type: 'account' | 'channel' | 'instance' |
83 | name: string | 86 | name: string |
84 | displayName: string | 87 | displayName: string |
88 | host: string | ||
85 | } | 89 | } |
86 | } | 90 | } |
87 | 91 | ||
diff --git a/shared/models/users/user-update-me.model.ts b/shared/models/users/user-update-me.model.ts index b6c0002e5..99b9a65bd 100644 --- a/shared/models/users/user-update-me.model.ts +++ b/shared/models/users/user-update-me.model.ts | |||
@@ -15,4 +15,7 @@ export interface UserUpdateMe { | |||
15 | password?: string | 15 | password?: string |
16 | 16 | ||
17 | theme?: string | 17 | theme?: string |
18 | |||
19 | noInstanceConfigWarningModal?: boolean | ||
20 | noWelcomeModal?: boolean | ||
18 | } | 21 | } |
diff --git a/shared/models/users/user.model.ts b/shared/models/users/user.model.ts index de9825e1f..f67d262b0 100644 --- a/shared/models/users/user.model.ts +++ b/shared/models/users/user.model.ts | |||
@@ -10,6 +10,7 @@ export interface User { | |||
10 | username: string | 10 | username: string |
11 | email: string | 11 | email: string |
12 | pendingEmail: string | null | 12 | pendingEmail: string | null |
13 | |||
13 | emailVerified: boolean | 14 | emailVerified: boolean |
14 | nsfwPolicy: NSFWPolicyType | 15 | nsfwPolicy: NSFWPolicyType |
15 | 16 | ||
@@ -18,13 +19,15 @@ export interface User { | |||
18 | autoPlayVideo: boolean | 19 | autoPlayVideo: boolean |
19 | webTorrentEnabled: boolean | 20 | webTorrentEnabled: boolean |
20 | videosHistoryEnabled: boolean | 21 | videosHistoryEnabled: boolean |
22 | videoLanguages: string[] | ||
21 | 23 | ||
22 | role: UserRole | 24 | role: UserRole |
23 | roleLabel: string | 25 | roleLabel: string |
24 | 26 | ||
25 | videoQuota: number | 27 | videoQuota: number |
26 | videoQuotaDaily: number | 28 | videoQuotaDaily: number |
27 | createdAt: Date | 29 | videoQuotaUsed?: number |
30 | videoQuotaUsedDaily?: number | ||
28 | 31 | ||
29 | theme: string | 32 | theme: string |
30 | 33 | ||
@@ -35,5 +38,8 @@ export interface User { | |||
35 | blocked: boolean | 38 | blocked: boolean |
36 | blockedReason?: string | 39 | blockedReason?: string |
37 | 40 | ||
38 | videoQuotaUsed?: number | 41 | noInstanceConfigWarningModal: boolean |
42 | noWelcomeModal: boolean | ||
43 | |||
44 | createdAt: Date | ||
39 | } | 45 | } |
diff --git a/support/doc/api/openapi.yaml b/support/doc/api/openapi.yaml index 24ffdbc15..69717525d 100644 --- a/support/doc/api/openapi.yaml +++ b/support/doc/api/openapi.yaml | |||
@@ -130,9 +130,6 @@ paths: | |||
130 | summary: Get the account by name | 130 | summary: Get the account by name |
131 | parameters: | 131 | parameters: |
132 | - $ref: '#/components/parameters/name' | 132 | - $ref: '#/components/parameters/name' |
133 | - $ref: '#/components/parameters/start' | ||
134 | - $ref: '#/components/parameters/count' | ||
135 | - $ref: '#/components/parameters/sort' | ||
136 | responses: | 133 | responses: |
137 | '200': | 134 | '200': |
138 | description: successful operation | 135 | description: successful operation |
@@ -204,6 +201,10 @@ paths: | |||
204 | tags: | 201 | tags: |
205 | - Accounts | 202 | - Accounts |
206 | summary: Get all accounts | 203 | summary: Get all accounts |
204 | parameters: | ||
205 | - $ref: '#/components/parameters/start' | ||
206 | - $ref: '#/components/parameters/count' | ||
207 | - $ref: '#/components/parameters/sort' | ||
207 | responses: | 208 | responses: |
208 | '200': | 209 | '200': |
209 | description: successful operation | 210 | description: successful operation |
@@ -233,6 +234,10 @@ paths: | |||
233 | responses: | 234 | responses: |
234 | '200': | 235 | '200': |
235 | description: successful operation | 236 | description: successful operation |
237 | content: | ||
238 | application/json: | ||
239 | schema: | ||
240 | $ref: '#/components/schemas/ServerConfigAbout' | ||
236 | /config/custom: | 241 | /config/custom: |
237 | get: | 242 | get: |
238 | summary: Get the runtime configuration of the server | 243 | summary: Get the runtime configuration of the server |
@@ -244,6 +249,10 @@ paths: | |||
244 | responses: | 249 | responses: |
245 | '200': | 250 | '200': |
246 | description: successful operation | 251 | description: successful operation |
252 | content: | ||
253 | application/json: | ||
254 | schema: | ||
255 | $ref: '#/components/schemas/ServerConfigCustom' | ||
247 | put: | 256 | put: |
248 | summary: Set the runtime configuration of the server | 257 | summary: Set the runtime configuration of the server |
249 | tags: | 258 | tags: |
@@ -726,8 +735,7 @@ paths: | |||
726 | type: string | 735 | type: string |
727 | format: binary | 736 | format: binary |
728 | encoding: | 737 | encoding: |
729 | profileImage: | 738 | avatarfile: |
730 | # only accept png/jpeg | ||
731 | contentType: image/png, image/jpeg | 739 | contentType: image/png, image/jpeg |
732 | /videos: | 740 | /videos: |
733 | get: | 741 | get: |
@@ -829,9 +837,11 @@ paths: | |||
829 | thumbnailfile: | 837 | thumbnailfile: |
830 | description: Video thumbnail file | 838 | description: Video thumbnail file |
831 | type: string | 839 | type: string |
840 | format: binary | ||
832 | previewfile: | 841 | previewfile: |
833 | description: Video preview file | 842 | description: Video preview file |
834 | type: string | 843 | type: string |
844 | format: binary | ||
835 | category: | 845 | category: |
836 | description: Video category | 846 | description: Video category |
837 | type: string | 847 | type: string |
@@ -874,6 +884,11 @@ paths: | |||
874 | format: date-time | 884 | format: date-time |
875 | scheduleUpdate: | 885 | scheduleUpdate: |
876 | $ref: '#/components/schemas/VideoScheduledUpdate' | 886 | $ref: '#/components/schemas/VideoScheduledUpdate' |
887 | encoding: | ||
888 | thumbnailfile: | ||
889 | contentType: image/jpeg | ||
890 | previewfile: | ||
891 | contentType: image/jpeg | ||
877 | get: | 892 | get: |
878 | summary: Get a video by its id | 893 | summary: Get a video by its id |
879 | tags: | 894 | tags: |
@@ -1029,9 +1044,11 @@ paths: | |||
1029 | thumbnailfile: | 1044 | thumbnailfile: |
1030 | description: Video thumbnail file | 1045 | description: Video thumbnail file |
1031 | type: string | 1046 | type: string |
1047 | format: binary | ||
1032 | previewfile: | 1048 | previewfile: |
1033 | description: Video preview file | 1049 | description: Video preview file |
1034 | type: string | 1050 | type: string |
1051 | format: binary | ||
1035 | privacy: | 1052 | privacy: |
1036 | $ref: '#/components/schemas/VideoPrivacySet' | 1053 | $ref: '#/components/schemas/VideoPrivacySet' |
1037 | category: | 1054 | category: |
@@ -1080,6 +1097,13 @@ paths: | |||
1080 | - videofile | 1097 | - videofile |
1081 | - channelId | 1098 | - channelId |
1082 | - name | 1099 | - name |
1100 | encoding: | ||
1101 | videofile: | ||
1102 | contentType: video/mp4, video/webm, video/ogg, video/avi, video/quicktime, video/x-msvideo, video/x-flv, video/x-matroska, application/octet-stream | ||
1103 | thumbnailfile: | ||
1104 | contentType: image/jpeg | ||
1105 | previewfile: | ||
1106 | contentType: image/jpeg | ||
1083 | x-code-samples: | 1107 | x-code-samples: |
1084 | - lang: Shell | 1108 | - lang: Shell |
1085 | source: | | 1109 | source: | |
@@ -1142,9 +1166,11 @@ paths: | |||
1142 | thumbnailfile: | 1166 | thumbnailfile: |
1143 | description: Video thumbnail file | 1167 | description: Video thumbnail file |
1144 | type: string | 1168 | type: string |
1169 | format: binary | ||
1145 | previewfile: | 1170 | previewfile: |
1146 | description: Video preview file | 1171 | description: Video preview file |
1147 | type: string | 1172 | type: string |
1173 | format: binary | ||
1148 | privacy: | 1174 | privacy: |
1149 | $ref: '#/components/schemas/VideoPrivacySet' | 1175 | $ref: '#/components/schemas/VideoPrivacySet' |
1150 | category: | 1176 | category: |
@@ -1188,6 +1214,13 @@ paths: | |||
1188 | required: | 1214 | required: |
1189 | - channelId | 1215 | - channelId |
1190 | - name | 1216 | - name |
1217 | encoding: | ||
1218 | torrentfile: | ||
1219 | contentType: application/x-bittorrent | ||
1220 | thumbnailfile: | ||
1221 | contentType: image/jpeg | ||
1222 | previewfile: | ||
1223 | contentType: image/jpeg | ||
1191 | /videos/abuse: | 1224 | /videos/abuse: |
1192 | get: | 1225 | get: |
1193 | summary: Get list of reported video abuses | 1226 | summary: Get list of reported video abuses |
@@ -1308,6 +1341,9 @@ paths: | |||
1308 | description: The file to upload. | 1341 | description: The file to upload. |
1309 | type: string | 1342 | type: string |
1310 | format: binary | 1343 | format: binary |
1344 | encoding: | ||
1345 | captionfile: | ||
1346 | contentType: text/vtt, application/x-subrip | ||
1311 | responses: | 1347 | responses: |
1312 | '204': | 1348 | '204': |
1313 | $ref: '#/paths/~1users~1me/put/responses/204' | 1349 | $ref: '#/paths/~1users~1me/put/responses/204' |
@@ -1952,7 +1988,7 @@ components: | |||
1952 | description: 'Video file size in bytes' | 1988 | description: 'Video file size in bytes' |
1953 | torrentUrl: | 1989 | torrentUrl: |
1954 | type: string | 1990 | type: string |
1955 | torrentDownaloadUrl: | 1991 | torrentDownloadUrl: |
1956 | type: string | 1992 | type: string |
1957 | fileUrl: | 1993 | fileUrl: |
1958 | type: string | 1994 | type: string |
@@ -2227,8 +2263,6 @@ components: | |||
2227 | properties: | 2263 | properties: |
2228 | id: | 2264 | id: |
2229 | type: number | 2265 | type: number |
2230 | uuid: | ||
2231 | type: string | ||
2232 | url: | 2266 | url: |
2233 | type: string | 2267 | type: string |
2234 | name: | 2268 | name: |
@@ -2249,8 +2283,12 @@ components: | |||
2249 | allOf: | 2283 | allOf: |
2250 | - $ref: '#/components/schemas/Actor' | 2284 | - $ref: '#/components/schemas/Actor' |
2251 | - properties: | 2285 | - properties: |
2286 | userId: | ||
2287 | type: string | ||
2252 | displayName: | 2288 | displayName: |
2253 | type: string | 2289 | type: string |
2290 | description: | ||
2291 | type: string | ||
2254 | User: | 2292 | User: |
2255 | properties: | 2293 | properties: |
2256 | id: | 2294 | id: |
@@ -2294,18 +2332,102 @@ components: | |||
2294 | type: number | 2332 | type: number |
2295 | ServerConfig: | 2333 | ServerConfig: |
2296 | properties: | 2334 | properties: |
2335 | instance: | ||
2336 | type: object | ||
2337 | properties: | ||
2338 | name: | ||
2339 | type: string | ||
2340 | shortDescription: | ||
2341 | type: string | ||
2342 | defaultClientRoute: | ||
2343 | type: string | ||
2344 | isNSFW: | ||
2345 | type: boolean | ||
2346 | defaultNSFWPolicy: | ||
2347 | type: string | ||
2348 | customizations: | ||
2349 | type: object | ||
2350 | properties: | ||
2351 | javascript: | ||
2352 | type: string | ||
2353 | css: | ||
2354 | type: string | ||
2355 | plugin: | ||
2356 | type: object | ||
2357 | properties: | ||
2358 | registered: | ||
2359 | type: array | ||
2360 | items: | ||
2361 | type: string | ||
2362 | theme: | ||
2363 | type: object | ||
2364 | properties: | ||
2365 | registered: | ||
2366 | type: array | ||
2367 | items: | ||
2368 | type: string | ||
2369 | email: | ||
2370 | type: object | ||
2371 | properties: | ||
2372 | enabled: | ||
2373 | type: boolean | ||
2374 | contactForm: | ||
2375 | type: object | ||
2376 | properties: | ||
2377 | enabled: | ||
2378 | type: boolean | ||
2379 | serverVersion: | ||
2380 | type: string | ||
2381 | serverCommit: | ||
2382 | type: string | ||
2297 | signup: | 2383 | signup: |
2298 | type: object | 2384 | type: object |
2299 | properties: | 2385 | properties: |
2300 | allowed: | 2386 | allowed: |
2301 | type: boolean | 2387 | type: boolean |
2388 | allowedForCurrentIP: | ||
2389 | type: boolean | ||
2390 | requiresEmailVerification: | ||
2391 | type: boolean | ||
2302 | transcoding: | 2392 | transcoding: |
2303 | type: object | 2393 | type: object |
2304 | properties: | 2394 | properties: |
2395 | hls: | ||
2396 | type: object | ||
2397 | properties: | ||
2398 | enabled: | ||
2399 | type: boolean | ||
2305 | enabledResolutions: | 2400 | enabledResolutions: |
2306 | type: array | 2401 | type: array |
2307 | items: | 2402 | items: |
2308 | type: number | 2403 | type: number |
2404 | import: | ||
2405 | type: object | ||
2406 | properties: | ||
2407 | videos: | ||
2408 | type: object | ||
2409 | properties: | ||
2410 | http: | ||
2411 | type: object | ||
2412 | properties: | ||
2413 | enabled: | ||
2414 | type: boolean | ||
2415 | torrent: | ||
2416 | type: object | ||
2417 | properties: | ||
2418 | enabled: | ||
2419 | type: boolean | ||
2420 | autoBlacklist: | ||
2421 | type: object | ||
2422 | properties: | ||
2423 | videos: | ||
2424 | type: object | ||
2425 | properties: | ||
2426 | ofUsers: | ||
2427 | type: object | ||
2428 | properties: | ||
2429 | enabled: | ||
2430 | type: boolean | ||
2309 | avatar: | 2431 | avatar: |
2310 | type: object | 2432 | type: object |
2311 | properties: | 2433 | properties: |
@@ -2324,6 +2446,18 @@ components: | |||
2324 | video: | 2446 | video: |
2325 | type: object | 2447 | type: object |
2326 | properties: | 2448 | properties: |
2449 | image: | ||
2450 | type: object | ||
2451 | properties: | ||
2452 | extensions: | ||
2453 | type: array | ||
2454 | items: | ||
2455 | type: string | ||
2456 | size: | ||
2457 | type: object | ||
2458 | properties: | ||
2459 | max: | ||
2460 | type: number | ||
2327 | file: | 2461 | file: |
2328 | type: object | 2462 | type: object |
2329 | properties: | 2463 | properties: |
@@ -2331,6 +2465,202 @@ components: | |||
2331 | type: array | 2465 | type: array |
2332 | items: | 2466 | items: |
2333 | type: string | 2467 | type: string |
2468 | videoCaption: | ||
2469 | type: object | ||
2470 | properties: | ||
2471 | file: | ||
2472 | type: object | ||
2473 | properties: | ||
2474 | size: | ||
2475 | type: object | ||
2476 | properties: | ||
2477 | max: | ||
2478 | type: number | ||
2479 | extensions: | ||
2480 | type: array | ||
2481 | items: | ||
2482 | type: string | ||
2483 | user: | ||
2484 | type: object | ||
2485 | properties: | ||
2486 | videoQuota: | ||
2487 | type: number | ||
2488 | videoQuotaDaily: | ||
2489 | type: number | ||
2490 | trending: | ||
2491 | type: object | ||
2492 | properties: | ||
2493 | videos: | ||
2494 | type: object | ||
2495 | properties: | ||
2496 | intervalDays: | ||
2497 | type: number | ||
2498 | tracker: | ||
2499 | ype: object | ||
2500 | properties: | ||
2501 | enabled: | ||
2502 | type: boolean | ||
2503 | ServerConfigAbout: | ||
2504 | properties: | ||
2505 | instance: | ||
2506 | type: object | ||
2507 | properties: | ||
2508 | name: | ||
2509 | type: string | ||
2510 | shortDescription: | ||
2511 | type: string | ||
2512 | description: | ||
2513 | type: string | ||
2514 | terms: | ||
2515 | type: string | ||
2516 | ServerConfigCustom: | ||
2517 | properties: | ||
2518 | instance: | ||
2519 | type: object | ||
2520 | properties: | ||
2521 | name: | ||
2522 | type: string | ||
2523 | shortDescription: | ||
2524 | type: string | ||
2525 | description: | ||
2526 | type: string | ||
2527 | terms: | ||
2528 | type: string | ||
2529 | defaultClientRoute: | ||
2530 | type: string | ||
2531 | isNSFW: | ||
2532 | type: boolean | ||
2533 | defaultNSFWPolicy: | ||
2534 | type: string | ||
2535 | customizations: | ||
2536 | type: object | ||
2537 | properties: | ||
2538 | javascript: | ||
2539 | type: string | ||
2540 | css: | ||
2541 | type: string | ||
2542 | theme: | ||
2543 | type: object | ||
2544 | properties: | ||
2545 | default: | ||
2546 | type: string | ||
2547 | services: | ||
2548 | type: object | ||
2549 | properties: | ||
2550 | twitter: | ||
2551 | type: object | ||
2552 | properties: | ||
2553 | username: | ||
2554 | type: string | ||
2555 | whitelisted: | ||
2556 | type: boolean | ||
2557 | cache: | ||
2558 | type: object | ||
2559 | properties: | ||
2560 | previews: | ||
2561 | type: object | ||
2562 | properties: | ||
2563 | size: | ||
2564 | type: number | ||
2565 | captions: | ||
2566 | type: object | ||
2567 | properties: | ||
2568 | size: | ||
2569 | type: number | ||
2570 | signup: | ||
2571 | type: object | ||
2572 | properties: | ||
2573 | enabled: | ||
2574 | type: boolean | ||
2575 | limit: | ||
2576 | type: number | ||
2577 | requiresEmailVerification: | ||
2578 | type: boolean | ||
2579 | admin: | ||
2580 | type: object | ||
2581 | properties: | ||
2582 | email: | ||
2583 | type: string | ||
2584 | contactForm: | ||
2585 | type: object | ||
2586 | properties: | ||
2587 | enabled: | ||
2588 | type: boolean | ||
2589 | user: | ||
2590 | type: object | ||
2591 | properties: | ||
2592 | videoQuota: | ||
2593 | type: number | ||
2594 | videoQuotaDaily: | ||
2595 | type: number | ||
2596 | transcoding: | ||
2597 | type: object | ||
2598 | properties: | ||
2599 | enabled: | ||
2600 | type: boolean | ||
2601 | allowAdditionalExtensions: | ||
2602 | type: boolean | ||
2603 | allowAudioFiles: | ||
2604 | type: boolean | ||
2605 | threads: | ||
2606 | type: number | ||
2607 | resolutions: | ||
2608 | type: object | ||
2609 | properties: | ||
2610 | 240p: | ||
2611 | type: boolean | ||
2612 | 360p: | ||
2613 | type: boolean | ||
2614 | 480p: | ||
2615 | type: boolean | ||
2616 | 720p: | ||
2617 | type: boolean | ||
2618 | 1080p: | ||
2619 | type: boolean | ||
2620 | 2160p: | ||
2621 | type: boolean | ||
2622 | hls: | ||
2623 | type: object | ||
2624 | properties: | ||
2625 | enabled: | ||
2626 | type: boolean | ||
2627 | import: | ||
2628 | type: object | ||
2629 | properties: | ||
2630 | videos: | ||
2631 | type: object | ||
2632 | properties: | ||
2633 | http: | ||
2634 | type: object | ||
2635 | properties: | ||
2636 | enabled: | ||
2637 | type: boolean | ||
2638 | torrent: | ||
2639 | type: object | ||
2640 | properties: | ||
2641 | enabled: | ||
2642 | type: boolean | ||
2643 | autoBlacklist: | ||
2644 | type: object | ||
2645 | properties: | ||
2646 | videos: | ||
2647 | type: object | ||
2648 | properties: | ||
2649 | ofUsers: | ||
2650 | type: object | ||
2651 | properties: | ||
2652 | enabled: | ||
2653 | type: boolean | ||
2654 | followers: | ||
2655 | type: object | ||
2656 | properties: | ||
2657 | instance: | ||
2658 | type: object | ||
2659 | properties: | ||
2660 | enabled: | ||
2661 | type: boolean | ||
2662 | manualApproval: | ||
2663 | type: boolean | ||
2334 | Follow: | 2664 | Follow: |
2335 | properties: | 2665 | properties: |
2336 | id: | 2666 | id: |
diff --git a/support/doc/tools.md b/support/doc/tools.md index cf427ec84..dd2a03db7 100644 --- a/support/doc/tools.md +++ b/support/doc/tools.md | |||
@@ -11,6 +11,7 @@ | |||
11 | - [peertube-import-videos.js](#peertube-import-videosjs) | 11 | - [peertube-import-videos.js](#peertube-import-videosjs) |
12 | - [peertube-upload.js](#peertube-uploadjs) | 12 | - [peertube-upload.js](#peertube-uploadjs) |
13 | - [peertube-watch.js](#peertube-watchjs) | 13 | - [peertube-watch.js](#peertube-watchjs) |
14 | - [peertube-plugins.js](#peertube-pluginsjs) | ||
14 | - [Server tools](#server-tools) | 15 | - [Server tools](#server-tools) |
15 | - [parse-log](#parse-log) | 16 | - [parse-log](#parse-log) |
16 | - [create-transcoding-job.js](#create-transcoding-jobjs) | 17 | - [create-transcoding-job.js](#create-transcoding-jobjs) |
@@ -19,6 +20,7 @@ | |||
19 | - [optimize-old-videos.js](#optimize-old-videosjs) | 20 | - [optimize-old-videos.js](#optimize-old-videosjs) |
20 | - [update-host.js](#update-hostjs) | 21 | - [update-host.js](#update-hostjs) |
21 | - [reset-password.js](#reset-passwordjs) | 22 | - [reset-password.js](#reset-passwordjs) |
23 | - [plugin install/uninstall](#plugin-installuninstall) | ||
22 | - [REPL (Read Eval Print Loop)](#repl-read-eval-print-loop) | 24 | - [REPL (Read Eval Print Loop)](#repl-read-eval-print-loop) |
23 | - [.help](#help) | 25 | - [.help](#help) |
24 | - [Lodash example](#lodash-example) | 26 | - [Lodash example](#lodash-example) |
@@ -182,6 +184,22 @@ It provides support for different players: | |||
182 | - chromecast | 184 | - chromecast |
183 | 185 | ||
184 | 186 | ||
187 | #### peertube-plugins.js | ||
188 | |||
189 | Install/update/uninstall or list local or NPM PeerTube plugins: | ||
190 | |||
191 | ``` | ||
192 | $ cd ${CLONE} | ||
193 | $ node dist/server/tools/peertube-plugins.js --help | ||
194 | $ node dist/server/tools/peertube-plugins.js list --help | ||
195 | $ node dist/server/tools/peertube-plugins.js install --help | ||
196 | $ node dist/server/tools/peertube-plugins.js update --help | ||
197 | $ node dist/server/tools/peertube-plugins.js uninstall --help | ||
198 | |||
199 | $ node dist/server/tools/peertube-plugins.js install --path /my/plugin/path | ||
200 | $ node dist/server/tools/peertube-plugins.js install --npm-name peertube-theme-example | ||
201 | ``` | ||
202 | |||
185 | ## Server tools | 203 | ## Server tools |
186 | 204 | ||
187 | These scripts should be run on the server, in `peertube-latest` directory. | 205 | These scripts should be run on the server, in `peertube-latest` directory. |
@@ -262,22 +280,22 @@ $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production | |||
262 | The difference with `peertube plugins` CLI is that these scripts can be used even if PeerTube is not running. | 280 | The difference with `peertube plugins` CLI is that these scripts can be used even if PeerTube is not running. |
263 | If PeerTube is running, you need to restart it for the changes to take effect (whereas with `peertube plugins` CLI, plugins/themes are dynamically loaded on the server). | 281 | If PeerTube is running, you need to restart it for the changes to take effect (whereas with `peertube plugins` CLI, plugins/themes are dynamically loaded on the server). |
264 | 282 | ||
265 | To install a plugin or a theme from the disk: | 283 | To install/update a plugin or a theme from the disk: |
266 | 284 | ||
267 | ``` | 285 | ``` |
268 | $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:install -- --plugin-path /local/plugin/path | 286 | $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run plugin:install -- --plugin-path /local/plugin/path |
269 | ``` | 287 | ``` |
270 | 288 | ||
271 | From NPM: | 289 | From NPM: |
272 | 290 | ||
273 | ``` | 291 | ``` |
274 | $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:install -- --npm-name peertube-plugin-myplugin | 292 | $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run plugin:install -- --npm-name peertube-plugin-myplugin |
275 | ``` | 293 | ``` |
276 | 294 | ||
277 | To uninstall a plugin or a theme: | 295 | To uninstall a plugin or a theme: |
278 | 296 | ||
279 | ``` | 297 | ``` |
280 | $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run npm run plugin:uninstall -- --npm-name peertube-plugin-myplugin | 298 | $ sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production npm run plugin:uninstall -- --npm-name peertube-plugin-myplugin |
281 | ``` | 299 | ``` |
282 | 300 | ||
283 | ### REPL ([Read Eval Print Loop](https://nodejs.org/docs/latest-v10.x/api/repl.html)) | 301 | ### REPL ([Read Eval Print Loop](https://nodejs.org/docs/latest-v10.x/api/repl.html)) |
diff --git a/tsconfig.json b/tsconfig.json index 4d2bdd6ba..f2985f82b 100644 --- a/tsconfig.json +++ b/tsconfig.json | |||
@@ -14,11 +14,15 @@ | |||
14 | "es2016", | 14 | "es2016", |
15 | "es2017" | 15 | "es2017" |
16 | ], | 16 | ], |
17 | "typeRoots": [ "node_modules/@types", "server/typings" ] | 17 | "typeRoots": [ "node_modules/@types", "server/typings" ], |
18 | "baseUrl": "./", | ||
19 | "paths": { | ||
20 | "@server/*": [ "server/*" ], | ||
21 | "@shared/*": [ "shared/*" ] | ||
22 | } | ||
18 | }, | 23 | }, |
19 | "exclude": [ | 24 | "exclude": [ |
20 | "server/tools/", | 25 | "server/tools/", |
21 | "client/node_modules", | ||
22 | "node_modules", | 26 | "node_modules", |
23 | "dist", | 27 | "dist", |
24 | "storage", | 28 | "storage", |
@@ -828,24 +828,6 @@ bindings@~1.3.0: | |||
828 | resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5" | 828 | resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.1.tgz#21fc7c6d67c18516ec5aaa2815b145ff77b26ea5" |
829 | integrity sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew== | 829 | integrity sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew== |
830 | 830 | ||
831 | bitcore-lib@^0.13.7: | ||
832 | version "0.13.19" | ||
833 | resolved "https://registry.yarnpkg.com/bitcore-lib/-/bitcore-lib-0.13.19.tgz#48af1e9bda10067c1ab16263472b5add2000f3dc" | ||
834 | integrity sha1-SK8em9oQBnwasWJjRyta3SAA89w= | ||
835 | dependencies: | ||
836 | bn.js "=2.0.4" | ||
837 | bs58 "=2.0.0" | ||
838 | buffer-compare "=1.0.0" | ||
839 | elliptic "=3.0.3" | ||
840 | inherits "=2.0.1" | ||
841 | lodash "=3.10.1" | ||
842 | |||
843 | "bitcore-message@github:CoMakery/bitcore-message#dist": | ||
844 | version "1.0.2" | ||
845 | resolved "https://codeload.github.com/CoMakery/bitcore-message/tar.gz/8799cc327029c3d34fc725f05b2cf981363f6ebf" | ||
846 | dependencies: | ||
847 | bitcore-lib "^0.13.7" | ||
848 | |||
849 | bitfield@^2.0.0: | 831 | bitfield@^2.0.0: |
850 | version "2.0.0" | 832 | version "2.0.0" |
851 | resolved "https://registry.yarnpkg.com/bitfield/-/bitfield-2.0.0.tgz#fbe6767592fe5b4c87ecf1d04126294cc1bfa837" | 833 | resolved "https://registry.yarnpkg.com/bitfield/-/bitfield-2.0.0.tgz#fbe6767592fe5b4c87ecf1d04126294cc1bfa837" |
@@ -968,16 +950,6 @@ bluebird@^3.0.5, bluebird@^3.5.0: | |||
968 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" | 950 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f" |
969 | integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== | 951 | integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w== |
970 | 952 | ||
971 | bn.js@=2.0.4: | ||
972 | version "2.0.4" | ||
973 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.0.4.tgz#220a7cd677f7f1bfa93627ff4193776fe7819480" | ||
974 | integrity sha1-Igp81nf38b+pNif/QZN3b+eBlIA= | ||
975 | |||
976 | bn.js@^2.0.0: | ||
977 | version "2.2.0" | ||
978 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-2.2.0.tgz#12162bc2ae71fc40a5626c33438f3a875cd37625" | ||
979 | integrity sha1-EhYrwq5x/EClYmwzQ486h1zTdiU= | ||
980 | |||
981 | bn.js@^4.4.0: | 953 | bn.js@^4.4.0: |
982 | version "4.11.8" | 954 | version "4.11.8" |
983 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" | 955 | resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" |
@@ -1043,11 +1015,6 @@ braces@^3.0.1: | |||
1043 | dependencies: | 1015 | dependencies: |
1044 | fill-range "^7.0.1" | 1016 | fill-range "^7.0.1" |
1045 | 1017 | ||
1046 | brorand@^1.0.1: | ||
1047 | version "1.1.0" | ||
1048 | resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" | ||
1049 | integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= | ||
1050 | |||
1051 | browser-stdout@1.3.1: | 1018 | browser-stdout@1.3.1: |
1052 | version "1.3.1" | 1019 | version "1.3.1" |
1053 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" | 1020 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" |
@@ -1058,11 +1025,6 @@ browserify-package-json@^1.0.0: | |||
1058 | resolved "https://registry.yarnpkg.com/browserify-package-json/-/browserify-package-json-1.0.1.tgz#98dde8aa5c561fd6d3fe49bbaa102b74b396fdea" | 1025 | resolved "https://registry.yarnpkg.com/browserify-package-json/-/browserify-package-json-1.0.1.tgz#98dde8aa5c561fd6d3fe49bbaa102b74b396fdea" |
1059 | integrity sha1-mN3oqlxWH9bT/km7qhArdLOW/eo= | 1026 | integrity sha1-mN3oqlxWH9bT/km7qhArdLOW/eo= |
1060 | 1027 | ||
1061 | bs58@=2.0.0: | ||
1062 | version "2.0.0" | ||
1063 | resolved "https://registry.yarnpkg.com/bs58/-/bs58-2.0.0.tgz#72b713bed223a0ac518bbda0e3ce3f4817f39eb5" | ||
1064 | integrity sha1-crcTvtIjoKxRi72g484/SBfznrU= | ||
1065 | |||
1066 | buffer-alloc-unsafe@^1.1.0: | 1028 | buffer-alloc-unsafe@^1.1.0: |
1067 | version "1.1.0" | 1029 | version "1.1.0" |
1068 | resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" | 1030 | resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" |
@@ -1076,16 +1038,6 @@ buffer-alloc@^1.1.0, buffer-alloc@^1.2.0: | |||
1076 | buffer-alloc-unsafe "^1.1.0" | 1038 | buffer-alloc-unsafe "^1.1.0" |
1077 | buffer-fill "^1.0.0" | 1039 | buffer-fill "^1.0.0" |
1078 | 1040 | ||
1079 | buffer-compare@=1.0.0: | ||
1080 | version "1.0.0" | ||
1081 | resolved "https://registry.yarnpkg.com/buffer-compare/-/buffer-compare-1.0.0.tgz#acaa7a966e98eee9fae14b31c39a5f158fb3c4a2" | ||
1082 | integrity sha1-rKp6lm6Y7un64Usxw5pfFY+zxKI= | ||
1083 | |||
1084 | buffer-equal-constant-time@1.0.1: | ||
1085 | version "1.0.1" | ||
1086 | resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" | ||
1087 | integrity sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk= | ||
1088 | |||
1089 | buffer-equals@^1.0.3, buffer-equals@^1.0.4: | 1041 | buffer-equals@^1.0.3, buffer-equals@^1.0.4: |
1090 | version "1.0.4" | 1042 | version "1.0.4" |
1091 | resolved "https://registry.yarnpkg.com/buffer-equals/-/buffer-equals-1.0.4.tgz#0353b54fd07fd9564170671ae6f66b9cf10d27f5" | 1043 | resolved "https://registry.yarnpkg.com/buffer-equals/-/buffer-equals-1.0.4.tgz#0353b54fd07fd9564170671ae6f66b9cf10d27f5" |
@@ -2096,13 +2048,6 @@ ecc-jsbn@~0.1.1: | |||
2096 | jsbn "~0.1.0" | 2048 | jsbn "~0.1.0" |
2097 | safer-buffer "^2.1.0" | 2049 | safer-buffer "^2.1.0" |
2098 | 2050 | ||
2099 | ecdsa-sig-formatter@1.0.11: | ||
2100 | version "1.0.11" | ||
2101 | resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" | ||
2102 | integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== | ||
2103 | dependencies: | ||
2104 | safe-buffer "^5.0.1" | ||
2105 | |||
2106 | ee-first@1.1.1: | 2051 | ee-first@1.1.1: |
2107 | version "1.1.1" | 2052 | version "1.1.1" |
2108 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" | 2053 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" |
@@ -2113,16 +2058,6 @@ elegant-spinner@^1.0.1: | |||
2113 | resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" | 2058 | resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" |
2114 | integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4= | 2059 | integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4= |
2115 | 2060 | ||
2116 | elliptic@=3.0.3: | ||
2117 | version "3.0.3" | ||
2118 | resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-3.0.3.tgz#865c9b420bfbe55006b9f969f97a0d2c44966595" | ||
2119 | integrity sha1-hlybQgv75VAGuflp+XoNLESWZZU= | ||
2120 | dependencies: | ||
2121 | bn.js "^2.0.0" | ||
2122 | brorand "^1.0.1" | ||
2123 | hash.js "^1.0.0" | ||
2124 | inherits "^2.0.1" | ||
2125 | |||
2126 | emoji-regex@^7.0.1: | 2061 | emoji-regex@^7.0.1: |
2127 | version "7.0.3" | 2062 | version "7.0.3" |
2128 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" | 2063 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" |
@@ -3250,14 +3185,6 @@ has@^1.0.1, has@^1.0.3: | |||
3250 | dependencies: | 3185 | dependencies: |
3251 | function-bind "^1.1.1" | 3186 | function-bind "^1.1.1" |
3252 | 3187 | ||
3253 | hash.js@^1.0.0: | ||
3254 | version "1.1.7" | ||
3255 | resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" | ||
3256 | integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== | ||
3257 | dependencies: | ||
3258 | inherits "^2.0.3" | ||
3259 | minimalistic-assert "^1.0.1" | ||
3260 | |||
3261 | hashish@~0.0.4: | 3188 | hashish@~0.0.4: |
3262 | version "0.0.4" | 3189 | version "0.0.4" |
3263 | resolved "https://registry.yarnpkg.com/hashish/-/hashish-0.0.4.tgz#6d60bc6ffaf711b6afd60e426d077988014e6554" | 3190 | resolved "https://registry.yarnpkg.com/hashish/-/hashish-0.0.4.tgz#6d60bc6ffaf711b6afd60e426d077988014e6554" |
@@ -3481,11 +3408,6 @@ inherits@2.0.3: | |||
3481 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" | 3408 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" |
3482 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= | 3409 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= |
3483 | 3410 | ||
3484 | inherits@=2.0.1: | ||
3485 | version "2.0.1" | ||
3486 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" | ||
3487 | integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= | ||
3488 | |||
3489 | ini@^1.3.4, ini@~1.3.0: | 3411 | ini@^1.3.4, ini@~1.3.0: |
3490 | version "1.3.5" | 3412 | version "1.3.5" |
3491 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" | 3413 | resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" |
@@ -4028,25 +3950,6 @@ jsonify@~0.0.0: | |||
4028 | resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" | 3950 | resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" |
4029 | integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= | 3951 | integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= |
4030 | 3952 | ||
4031 | "jsonld-signatures@https://github.com/Chocobozzz/jsonld-signatures#rsa2017": | ||
4032 | version "1.2.2-2" | ||
4033 | resolved "https://github.com/Chocobozzz/jsonld-signatures#77660963e722eb4541d2d255f9d9d4216329665f" | ||
4034 | dependencies: | ||
4035 | bitcore-message "github:CoMakery/bitcore-message#dist" | ||
4036 | jsonld "^0.5.12" | ||
4037 | jws "^3.1.4" | ||
4038 | node-forge "^0.7.1" | ||
4039 | |||
4040 | jsonld@^0.5.12: | ||
4041 | version "0.5.21" | ||
4042 | resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-0.5.21.tgz#4d5b78d717eb92bcd1ac9d88e34efad95370c0bf" | ||
4043 | integrity sha512-1dQhaw1Eb3p7Cz5ECE2DNPwLvTmK+f6D45hACBdonJaFKP1bN9zlKLZWbPZQeZtduAc/LNv10J4ML0IiTBVahw== | ||
4044 | dependencies: | ||
4045 | rdf-canonize "^0.2.1" | ||
4046 | request "^2.83.0" | ||
4047 | semver "^5.5.0" | ||
4048 | xmldom "0.1.19" | ||
4049 | |||
4050 | jsonld@~1.1.0: | 3953 | jsonld@~1.1.0: |
4051 | version "1.1.0" | 3954 | version "1.1.0" |
4052 | resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-1.1.0.tgz#afcb168c44557a7bddead4d4513c3cbcae3bc5b9" | 3955 | resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-1.1.0.tgz#afcb168c44557a7bddead4d4513c3cbcae3bc5b9" |
@@ -4082,23 +3985,6 @@ junk@^3.1.0: | |||
4082 | resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" | 3985 | resolved "https://registry.yarnpkg.com/junk/-/junk-3.1.0.tgz#31499098d902b7e98c5d9b9c80f43457a88abfa1" |
4083 | integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== | 3986 | integrity sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ== |
4084 | 3987 | ||
4085 | jwa@^1.4.1: | ||
4086 | version "1.4.1" | ||
4087 | resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" | ||
4088 | integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== | ||
4089 | dependencies: | ||
4090 | buffer-equal-constant-time "1.0.1" | ||
4091 | ecdsa-sig-formatter "1.0.11" | ||
4092 | safe-buffer "^5.0.1" | ||
4093 | |||
4094 | jws@^3.1.4: | ||
4095 | version "3.2.2" | ||
4096 | resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" | ||
4097 | integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== | ||
4098 | dependencies: | ||
4099 | jwa "^1.4.1" | ||
4100 | safe-buffer "^5.0.1" | ||
4101 | |||
4102 | k-bucket@^4.0.0: | 3988 | k-bucket@^4.0.0: |
4103 | version "4.0.1" | 3989 | version "4.0.1" |
4104 | resolved "https://registry.yarnpkg.com/k-bucket/-/k-bucket-4.0.1.tgz#3fc2e5693f0b7bff90d7b6b476edd6087955d542" | 3990 | resolved "https://registry.yarnpkg.com/k-bucket/-/k-bucket-4.0.1.tgz#3fc2e5693f0b7bff90d7b6b476edd6087955d542" |
@@ -4335,11 +4221,6 @@ lodash@4.17.4: | |||
4335 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" | 4221 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" |
4336 | integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4= | 4222 | integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4= |
4337 | 4223 | ||
4338 | lodash@=3.10.1: | ||
4339 | version "3.10.1" | ||
4340 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" | ||
4341 | integrity sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y= | ||
4342 | |||
4343 | lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.3.0, lodash@~4.17.10: | 4224 | lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.3.0, lodash@~4.17.10: |
4344 | version "4.17.15" | 4225 | version "4.17.15" |
4345 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" | 4226 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" |
@@ -4638,11 +4519,6 @@ mimic-response@^1.0.0: | |||
4638 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" | 4519 | resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" |
4639 | integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== | 4520 | integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== |
4640 | 4521 | ||
4641 | minimalistic-assert@^1.0.1: | ||
4642 | version "1.0.1" | ||
4643 | resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" | ||
4644 | integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== | ||
4645 | |||
4646 | minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: | 4522 | minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: |
4647 | version "3.0.4" | 4523 | version "3.0.4" |
4648 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" | 4524 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" |
@@ -4734,6 +4610,11 @@ mocha@^6.0.0: | |||
4734 | yargs-parser "13.0.0" | 4610 | yargs-parser "13.0.0" |
4735 | yargs-unparser "1.5.0" | 4611 | yargs-unparser "1.5.0" |
4736 | 4612 | ||
4613 | module-alias@^2.2.1: | ||
4614 | version "2.2.1" | ||
4615 | resolved "https://registry.yarnpkg.com/module-alias/-/module-alias-2.2.1.tgz#553aea9dc7f99cd45fd75e34a574960dc46550da" | ||
4616 | integrity sha512-LTez0Eo+YtfUhgzhu/LqxkUzOpD+k5C0wXBLun0L1qE2BhHf6l09dqam8e7BnoMYA6mAlP0vSsGFQ8QHhGN/aQ== | ||
4617 | |||
4737 | moment-timezone@^0.5.21, moment-timezone@^0.5.25: | 4618 | moment-timezone@^0.5.21, moment-timezone@^0.5.25: |
4738 | version "0.5.26" | 4619 | version "0.5.26" |
4739 | resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.26.tgz#c0267ca09ae84631aa3dc33f65bedbe6e8e0d772" | 4620 | resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.26.tgz#c0267ca09ae84631aa3dc33f65bedbe6e8e0d772" |