From a9803269ed5751d779d8eaf417bd0748cc784d67 Mon Sep 17 00:00:00 2001 From: mkovalua Date: Mon, 2 Mar 2026 20:08:27 +0200 Subject: [PATCH 1/4] add content|resource is flagged spam page on 410 resource access request response --- src/app/app.routes.ts | 7 ++++++ .../resource-is-spammed.component.html | 13 +++++++++++ .../resource-is-spammed.component.scss | 16 ++++++++++++++ .../resource-is-spammed.component.spec.ts | 22 +++++++++++++++++++ .../resource-is-spammed.component.ts | 17 ++++++++++++++ .../preprints/services/preprints.service.ts | 14 ++++++++++-- src/app/shared/services/resource.service.ts | 12 ++++++++-- src/assets/i18n/en.json | 6 +++++ 8 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 src/app/core/components/resource-is-spammed/resource-is-spammed.component.html create mode 100644 src/app/core/components/resource-is-spammed/resource-is-spammed.component.scss create mode 100644 src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts create mode 100644 src/app/core/components/resource-is-spammed/resource-is-spammed.component.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index f40e58fea..889aa68c5 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -175,6 +175,13 @@ export const routes: Routes = [ import('./core/components/page-not-found/page-not-found.component').then((mod) => mod.PageNotFoundComponent), data: { skipBreadcrumbs: true }, }, + { + path: 'content-flagged-as-spam', + loadComponent: () => + import('./core/components/resource-is-spammed/resource-is-spammed.component').then( + (mod) => mod.ResourceIsSpammedComponent + ), + }, { path: 'project/:id/node/:nodeId/files/:provider/:fileId', loadComponent: () => diff --git a/src/app/core/components/resource-is-spammed/resource-is-spammed.component.html b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.html new file mode 100644 index 000000000..06c520a20 --- /dev/null +++ b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.html @@ -0,0 +1,13 @@ +
+

{{ 'resourceSpammed.title' | translate }}

+ +

+ {{ 'resourceSpammed.message' | translate }} +

+ +

+ {{ 'resourceSpammed.contact' | translate }} + {{ supportEmail }} + {{ 'resourceSpammed.footer' | translate }} +

+
diff --git a/src/app/core/components/resource-is-spammed/resource-is-spammed.component.scss b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.scss new file mode 100644 index 000000000..0421f87e7 --- /dev/null +++ b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.scss @@ -0,0 +1,16 @@ +@use "styles/mixins" as mix; + +:host { + @include mix.flex-center; + flex: 1; + background: var(--gradient-3); +} + +//.container { +// position: relative; +// background: var(--white); +// border-radius: 0.75rem; +// box-shadow: 0 2px 4px var(--grey-outline); +// color: var(--dark-blue-1); +// max-width: mix.rem(448px); +//} diff --git a/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts new file mode 100644 index 000000000..a44da17fb --- /dev/null +++ b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ResourceIsSpammedComponent } from './resource-is-spammed.component'; + +describe('ResourceIsSpammedComponent', () => { + let component: ResourceIsSpammedComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ResourceIsSpammedComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(ResourceIsSpammedComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/core/components/resource-is-spammed/resource-is-spammed.component.ts b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.ts new file mode 100644 index 000000000..1ae22206b --- /dev/null +++ b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.ts @@ -0,0 +1,17 @@ +import { TranslatePipe } from '@ngx-translate/core'; + +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; + +import { ENVIRONMENT } from '@core/provider/environment.provider'; + +@Component({ + selector: 'osf-resource-is-spammed', + imports: [TranslatePipe], + templateUrl: './resource-is-spammed.component.html', + styleUrl: './resource-is-spammed.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ResourceIsSpammedComponent { + private readonly environment = inject(ENVIRONMENT); + readonly supportEmail = this.environment.supportEmail; +} diff --git a/src/app/features/preprints/services/preprints.service.ts b/src/app/features/preprints/services/preprints.service.ts index 6b9b780e1..6beeacd4a 100644 --- a/src/app/features/preprints/services/preprints.service.ts +++ b/src/app/features/preprints/services/preprints.service.ts @@ -1,6 +1,7 @@ -import { map, Observable } from 'rxjs'; +import { catchError, map, Observable, throwError } from 'rxjs'; import { inject, Injectable } from '@angular/core'; +import { Router } from '@angular/router'; import { ENVIRONMENT } from '@core/provider/environment.provider'; import { RegistryModerationMapper } from '@osf/features/moderation/mappers'; @@ -38,6 +39,7 @@ import { export class PreprintsService { private readonly jsonApiService = inject(JsonApiService); private readonly environment = inject(ENVIRONMENT); + private readonly router = inject(Router); get apiUrl() { return `${this.environment.apiDomainUrl}/v2`; @@ -95,7 +97,15 @@ export class PreprintsService { null > >(`${this.apiUrl}/preprints/${id}/`, params) - .pipe(map((response) => PreprintsMapper.fromPreprintWithEmbedsJsonApi(response))); + .pipe( + map((response) => PreprintsMapper.fromPreprintWithEmbedsJsonApi(response)), + catchError((error) => { + if (error.status === 410) { + this.router.navigate(['/content-flagged-as-spam']); + } + return throwError(() => error); + }) + ); } getPreprintMetrics(id: string) { diff --git a/src/app/shared/services/resource.service.ts b/src/app/shared/services/resource.service.ts index 3b1d51fb2..e952c66fa 100644 --- a/src/app/shared/services/resource.service.ts +++ b/src/app/shared/services/resource.service.ts @@ -1,6 +1,7 @@ -import { finalize, map, Observable } from 'rxjs'; +import { catchError, finalize, map, Observable, throwError } from 'rxjs'; import { inject, Injectable } from '@angular/core'; +import { Router } from '@angular/router'; import { ENVIRONMENT } from '@core/provider/environment.provider'; @@ -24,6 +25,7 @@ export class ResourceGuidService { private jsonApiService = inject(JsonApiService); private loaderService = inject(LoaderService); private readonly environment = inject(ENVIRONMENT); + private readonly router = inject(Router); get apiUrl() { return `${this.environment.apiDomainUrl}/v2`; @@ -59,7 +61,13 @@ export class ResourceGuidService { title: res.data.attributes?.title, }) as CurrentResource ), - finalize(() => this.loaderService.hide()) + finalize(() => this.loaderService.hide()), + catchError((error) => { + if (error.status === 410) { + this.router.navigate(['/content-flagged-as-spam']); + } + return throwError(() => error); + }) ); } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index b6f5f392a..274828f12 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -2831,6 +2831,12 @@ "requestedSuccessMessage": "Your request for access has been sent.", "alreadyRequestedMessage": "You already requested access." }, + "resourceSpammed": { + "title": "This Page Is Temporarily Unavailable.", + "message": "This content was flagged as potential spam.", + "contact": "If this is a mistake, reach out to", + "footer": "for assistance. Your content remains safe and will be restored once verified." + }, "validation": { "required": "The field is required.", "email": "Please enter a valid email address.", From a8882f02f8689a04575b99029d7d236b391bb3e0 Mon Sep 17 00:00:00 2001 From: mkovalua Date: Mon, 2 Mar 2026 20:27:33 +0200 Subject: [PATCH 2/4] --amend --- .../resource-is-spammed.component.scss | 9 --------- .../resource-is-spammed.component.spec.ts | 4 ++-- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/app/core/components/resource-is-spammed/resource-is-spammed.component.scss b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.scss index 0421f87e7..5d202f7c6 100644 --- a/src/app/core/components/resource-is-spammed/resource-is-spammed.component.scss +++ b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.scss @@ -5,12 +5,3 @@ flex: 1; background: var(--gradient-3); } - -//.container { -// position: relative; -// background: var(--white); -// border-radius: 0.75rem; -// box-shadow: 0 2px 4px var(--grey-outline); -// color: var(--dark-blue-1); -// max-width: mix.rem(448px); -//} diff --git a/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts index a44da17fb..c157a5040 100644 --- a/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts +++ b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts @@ -1,5 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; - +import { TranslateModule } from '@ngx-translate/core'; import { ResourceIsSpammedComponent } from './resource-is-spammed.component'; describe('ResourceIsSpammedComponent', () => { @@ -8,7 +8,7 @@ describe('ResourceIsSpammedComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ResourceIsSpammedComponent], + imports: [TranslateModule.forRoot(), ResourceIsSpammedComponent], }).compileComponents(); fixture = TestBed.createComponent(ResourceIsSpammedComponent); From 6c85e3b13637b313ca4db366e6a60076545441e4 Mon Sep 17 00:00:00 2001 From: mkovalua Date: Mon, 2 Mar 2026 20:40:06 +0200 Subject: [PATCH 3/4] avoid linter issues --- .../resource-is-spammed/resource-is-spammed.component.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts index c157a5040..834d7a2ce 100644 --- a/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts +++ b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts @@ -1,5 +1,7 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; import { TranslateModule } from '@ngx-translate/core'; + +import { ComponentFixture, TestBed } from '@angular/core/testing'; + import { ResourceIsSpammedComponent } from './resource-is-spammed.component'; describe('ResourceIsSpammedComponent', () => { From 71144377395dda285c38146f53da3926af7ac87e Mon Sep 17 00:00:00 2001 From: mkovalua Date: Tue, 3 Mar 2026 15:25:06 +0200 Subject: [PATCH 4/4] resolve CR --- src/app/app.routes.ts | 3 ++- .../resource-is-spammed.component.html | 6 +++--- .../resource-is-spammed.component.spec.ts | 17 +++++++++++------ .../preprints/services/preprints.service.ts | 2 +- src/app/shared/services/resource.service.ts | 2 +- src/assets/i18n/en.json | 2 +- src/environments/environment.docker.ts | 2 +- 7 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 889aa68c5..49c222492 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -176,11 +176,12 @@ export const routes: Routes = [ data: { skipBreadcrumbs: true }, }, { - path: 'content-flagged-as-spam', + path: 'spam-content', loadComponent: () => import('./core/components/resource-is-spammed/resource-is-spammed.component').then( (mod) => mod.ResourceIsSpammedComponent ), + data: { skipBreadcrumbs: true }, }, { path: 'project/:id/node/:nodeId/files/:provider/:fileId', diff --git a/src/app/core/components/resource-is-spammed/resource-is-spammed.component.html b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.html index 06c520a20..a55f6556b 100644 --- a/src/app/core/components/resource-is-spammed/resource-is-spammed.component.html +++ b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.html @@ -1,7 +1,7 @@ -
-

{{ 'resourceSpammed.title' | translate }}

+
+

{{ 'resourceSpammed.title' | translate }}

-

+

{{ 'resourceSpammed.message' | translate }}

diff --git a/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts index 834d7a2ce..f2f4c8d00 100644 --- a/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts +++ b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts @@ -1,17 +1,18 @@ -import { TranslateModule } from '@ngx-translate/core'; - import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ResourceIsSpammedComponent } from './resource-is-spammed.component'; +import { provideOSFCore } from '@testing/osf.testing.provider'; + describe('ResourceIsSpammedComponent', () => { let component: ResourceIsSpammedComponent; let fixture: ComponentFixture; - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [TranslateModule.forRoot(), ResourceIsSpammedComponent], - }).compileComponents(); + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [ResourceIsSpammedComponent], + providers: [provideOSFCore()], + }); fixture = TestBed.createComponent(ResourceIsSpammedComponent); component = fixture.componentInstance; @@ -21,4 +22,8 @@ describe('ResourceIsSpammedComponent', () => { it('should create', () => { expect(component).toBeTruthy(); }); + + it('should set supportEmail from environment token', () => { + expect(component.supportEmail).toBe('support@test.com'); + }); }); diff --git a/src/app/features/preprints/services/preprints.service.ts b/src/app/features/preprints/services/preprints.service.ts index 6beeacd4a..0b037250a 100644 --- a/src/app/features/preprints/services/preprints.service.ts +++ b/src/app/features/preprints/services/preprints.service.ts @@ -101,7 +101,7 @@ export class PreprintsService { map((response) => PreprintsMapper.fromPreprintWithEmbedsJsonApi(response)), catchError((error) => { if (error.status === 410) { - this.router.navigate(['/content-flagged-as-spam']); + this.router.navigate(['/spam-content']); } return throwError(() => error); }) diff --git a/src/app/shared/services/resource.service.ts b/src/app/shared/services/resource.service.ts index e952c66fa..9255215d0 100644 --- a/src/app/shared/services/resource.service.ts +++ b/src/app/shared/services/resource.service.ts @@ -64,7 +64,7 @@ export class ResourceGuidService { finalize(() => this.loaderService.hide()), catchError((error) => { if (error.status === 410) { - this.router.navigate(['/content-flagged-as-spam']); + this.router.navigate(['/spam-content']); } return throwError(() => error); }) diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 274828f12..23f79bf7f 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -2832,7 +2832,7 @@ "alreadyRequestedMessage": "You already requested access." }, "resourceSpammed": { - "title": "This Page Is Temporarily Unavailable.", + "title": "This page is temporarily unavailable.", "message": "This content was flagged as potential spam.", "contact": "If this is a mistake, reach out to", "footer": "for assistance. Your content remains safe and will be restored once verified." diff --git a/src/environments/environment.docker.ts b/src/environments/environment.docker.ts index 92a84f2ee..157755c97 100644 --- a/src/environments/environment.docker.ts +++ b/src/environments/environment.docker.ts @@ -2,7 +2,7 @@ export const environment = { production: false, webUrl: 'http://localhost:5000', apiDomainUrl: 'http://localhost:8000', - shareTroveUrl: 'https://localhost:8003/trove', + shareTroveUrl: 'http://localhost:8003/trove', addonsApiUrl: 'http://localhost:8004/v1', funderApiUrl: 'https://api.crossref.org/', casUrl: 'http://localhost:8080',