diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index f40e58fea..49c222492 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -175,6 +175,14 @@ export const routes: Routes = [ import('./core/components/page-not-found/page-not-found.component').then((mod) => mod.PageNotFoundComponent), data: { skipBreadcrumbs: true }, }, + { + 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', 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..a55f6556b --- /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..5d202f7c6 --- /dev/null +++ b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.scss @@ -0,0 +1,7 @@ +@use "styles/mixins" as mix; + +:host { + @include mix.flex-center; + flex: 1; + background: var(--gradient-3); +} 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..f2f4c8d00 --- /dev/null +++ b/src/app/core/components/resource-is-spammed/resource-is-spammed.component.spec.ts @@ -0,0 +1,29 @@ +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(() => { + TestBed.configureTestingModule({ + imports: [ResourceIsSpammedComponent], + providers: [provideOSFCore()], + }); + + fixture = TestBed.createComponent(ResourceIsSpammedComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + 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/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..0b037250a 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(['/spam-content']); + } + 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..9255215d0 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(['/spam-content']); + } + return throwError(() => error); + }) ); } diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index b6f5f392a..23f79bf7f 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.", 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',