diff --git a/src/app/core/interceptors/auth.interceptor.ts b/src/app/core/interceptors/auth.interceptor.ts index 9c73826bc..e9eac5253 100644 --- a/src/app/core/interceptors/auth.interceptor.ts +++ b/src/app/core/interceptors/auth.interceptor.ts @@ -5,6 +5,8 @@ import { Observable } from 'rxjs'; import { HttpEvent, HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http'; import { inject } from '@angular/core'; +import { environment } from 'src/environments/environment'; + export const authInterceptor: HttpInterceptorFn = ( req: HttpRequest, next: HttpHandlerFn @@ -13,7 +15,7 @@ export const authInterceptor: HttpInterceptorFn = ( const csrfToken = cookieService.get('api-csrf'); - if (!req.url.includes('/api.crossref.org/funders')) { + if (!req.url.startsWith(environment.funderApiUrl)) { const headers: Record = {}; headers['Accept'] = req.responseType === 'text' ? '*/*' : 'application/vnd.api+json;version=2.20'; diff --git a/src/app/core/interceptors/view-only.interceptor.ts b/src/app/core/interceptors/view-only.interceptor.ts index e77c731ab..cf1a2c867 100644 --- a/src/app/core/interceptors/view-only.interceptor.ts +++ b/src/app/core/interceptors/view-only.interceptor.ts @@ -6,6 +6,8 @@ import { Router } from '@angular/router'; import { getViewOnlyParam } from '@osf/shared/helpers/view-only.helper'; +import { environment } from 'src/environments/environment'; + export const viewOnlyInterceptor: HttpInterceptorFn = ( req: HttpRequest, next: HttpHandlerFn @@ -14,7 +16,7 @@ export const viewOnlyInterceptor: HttpInterceptorFn = ( const viewOnlyParam = getViewOnlyParam(router); - if (!req.url.includes('/api.crossref.org/funders') && viewOnlyParam) { + if (!req.url.startsWith(environment.funderApiUrl) && viewOnlyParam) { if (req.url.includes('view_only=')) { return next(req); } diff --git a/src/app/features/metadata/dialogs/funding-dialog/funding-dialog.component.html b/src/app/features/metadata/dialogs/funding-dialog/funding-dialog.component.html index b0288a118..c2473b8cf 100644 --- a/src/app/features/metadata/dialogs/funding-dialog/funding-dialog.component.html +++ b/src/app/features/metadata/dialogs/funding-dialog/funding-dialog.component.html @@ -10,13 +10,13 @@ { let component: FundingDialogComponent; let fixture: ComponentFixture; @@ -25,7 +30,7 @@ describe('FundingDialogComponent', () => { MockProvider(DynamicDialogConfig, { data: { funders: [] } }), provideMockStore({ signals: [ - { selector: MetadataSelectors.getFundersList, value: MOCK_FUNDERS }, + { selector: MetadataSelectors.getFundersList, value: MOCK_ROR_FUNDERS }, { selector: MetadataSelectors.getFundersLoading, value: false }, ], }), @@ -41,22 +46,15 @@ describe('FundingDialogComponent', () => { expect(component).toBeTruthy(); }); - it('should add funding entry', () => { - const initialLength = component.fundingEntries.length; - component.addFundingEntry(); - - expect(component.fundingEntries.length).toBe(initialLength + 1); - const entry = component.fundingEntries.at(component.fundingEntries.length - 1); - expect(entry.get('funderName')?.value).toBe(null); - expect(entry.get('awardTitle')?.value).toBe(''); - }); - - it('should not remove funding entry when only one exists', () => { + it('should not remove last funding entry and close dialog with empty result', () => { + const dialogRef = TestBed.inject(DynamicDialogRef); + const closeSpy = jest.spyOn(dialogRef, 'close'); expect(component.fundingEntries.length).toBe(1); component.removeFundingEntry(0); expect(component.fundingEntries.length).toBe(1); + expect(closeSpy).toHaveBeenCalledWith({ fundingEntries: [] }); }); it('should save valid form data', () => { @@ -145,16 +143,19 @@ describe('FundingDialogComponent', () => { expect(entry.get('funderIdentifierType')?.value).toBe(initialValues.funderIdentifierType); }); - it('should remove funding entry when more than one exists', () => { - component.addFundingEntry(); - expect(component.fundingEntries.length).toBe(2); + it('should update funding entry when funder is selected from ROR list', () => { + const entry = component.fundingEntries.at(0); - component.removeFundingEntry(0); - expect(component.fundingEntries.length).toBe(1); + component.onFunderSelected('Test Funder', 0); + + expect(entry.get('funderName')?.value).toBe('Test Funder'); + expect(entry.get('funderIdentifier')?.value).toBe('https://ror.org/0test'); + expect(entry.get('funderIdentifierType')?.value).toBe('ROR'); }); - it('should not remove funding entry when only one exists', () => { - expect(component.fundingEntries.length).toBe(1); + it('should remove funding entry when more than one exists', () => { + component.addFundingEntry(); + expect(component.fundingEntries.length).toBe(2); component.removeFundingEntry(0); expect(component.fundingEntries.length).toBe(1); @@ -172,7 +173,7 @@ describe('FundingDialogComponent', () => { const supplement = { funderName: 'Test Funder', funderIdentifier: 'test-id', - funderIdentifierType: 'Crossref Funder ID', + funderIdentifierType: 'ROR', title: 'Test Award', url: 'https://test.com', awardNumber: 'AWARD-123', @@ -183,7 +184,7 @@ describe('FundingDialogComponent', () => { const entry = component.fundingEntries.at(component.fundingEntries.length - 1); expect(entry.get('funderName')?.value).toBe('Test Funder'); expect(entry.get('funderIdentifier')?.value).toBe('test-id'); - expect(entry.get('funderIdentifierType')?.value).toBe('Crossref Funder ID'); + expect(entry.get('funderIdentifierType')?.value).toBe('ROR'); expect(entry.get('awardTitle')?.value).toBe('Test Award'); expect(entry.get('awardUri')?.value).toBe('https://test.com'); expect(entry.get('awardNumber')?.value).toBe('AWARD-123'); @@ -227,32 +228,107 @@ describe('FundingDialogComponent', () => { expect(entry.get('awardNumber')?.value).toBe(''); }); - it('should emit search query to searchSubject', () => { - const searchSpy = jest.spyOn(component['searchSubject'], 'next'); + it('should dispatch getFundersList after debounce when searching', fakeAsync(() => { + const store = TestBed.inject(Store); + const dispatchSpy = jest.spyOn(store, 'dispatch'); - component.onFunderSearch('test search'); + component.onFunderSearch('query'); + expect(dispatchSpy).not.toHaveBeenCalled(); + tick(300); + expect(dispatchSpy).toHaveBeenCalledWith(new GetFundersList('query')); + })); - expect(searchSpy).toHaveBeenCalledWith('test search'); + it('should pre-populate entries from config funders on init', () => { + TestBed.resetTestingModule(); + TestBed.configureTestingModule({ + imports: [FundingDialogComponent, OSFTestingModule], + providers: [ + MockProviders(DynamicDialogRef, DestroyRef), + MockProvider(DynamicDialogConfig, { data: { funders: [MOCK_FUNDERS[0]] } }), + provideMockStore({ + signals: [ + { selector: MetadataSelectors.getFundersList, value: [] }, + { selector: MetadataSelectors.getFundersLoading, value: false }, + ], + }), + ], + }).compileComponents(); + const f = TestBed.createComponent(FundingDialogComponent); + f.detectChanges(); + const c = f.componentInstance; + expect(c.fundingEntries.length).toBe(1); + const entry = c.fundingEntries.at(0); + expect(entry.get('funderName')?.value).toBe(MOCK_FUNDERS[0].funderName); + expect(entry.get('funderIdentifier')?.value).toBe(MOCK_FUNDERS[0].funderIdentifier); + expect(entry.get('funderIdentifierType')?.value).toBe(MOCK_FUNDERS[0].funderIdentifierType); + expect(entry.get('awardTitle')?.value).toBe(MOCK_FUNDERS[0].awardTitle); + expect(entry.get('awardUri')?.value).toBe(MOCK_FUNDERS[0].awardUri); + expect(entry.get('awardNumber')?.value).toBe(MOCK_FUNDERS[0].awardNumber); }); - it('should handle empty search term', () => { - const searchSpy = jest.spyOn(component['searchSubject'], 'next'); - - component.onFunderSearch(''); + it('getOptionsForIndex returns custom option plus list when entry name is not in list', () => { + const entry = component.fundingEntries.at(0); + entry.patchValue({ funderName: 'Custom Funder', funderIdentifier: 'custom-id' }); + const options = component.getOptionsForIndex(0); + expect(options).toHaveLength(2); + expect(options[0]).toEqual({ id: 'custom-id', name: 'Custom Funder' }); + expect(options[1]).toEqual(MOCK_ROR_FUNDERS[0]); + }); - expect(searchSpy).toHaveBeenCalledWith(''); + it('getOptionsForIndex returns list when entry has no name', () => { + const options = component.getOptionsForIndex(0); + expect(options).toEqual(MOCK_ROR_FUNDERS); }); - it('should handle multiple search calls', () => { - const searchSpy = jest.spyOn(component['searchSubject'], 'next'); + it('filterMessage returns loading key when funders loading', () => { + TestBed.resetTestingModule(); + const loadingSignal = signal(true); + TestBed.configureTestingModule({ + imports: [FundingDialogComponent, OSFTestingModule], + providers: [ + MockProviders(DynamicDialogRef, DestroyRef), + MockProvider(DynamicDialogConfig, { data: { funders: [] } }), + provideMockStore({ + signals: [ + { selector: MetadataSelectors.getFundersList, value: [] }, + { selector: MetadataSelectors.getFundersLoading, value: loadingSignal }, + ], + }), + ], + }).compileComponents(); + const f = TestBed.createComponent(FundingDialogComponent); + f.detectChanges(); + expect(f.componentInstance.filterMessage()).toBe('project.metadata.funding.dialog.loadingFunders'); + loadingSignal.set(false); + expect(f.componentInstance.filterMessage()).toBe('project.metadata.funding.dialog.noFundersFound'); + }); - component.onFunderSearch('first'); - component.onFunderSearch('second'); - component.onFunderSearch('third'); + it('save returns only entries with at least one of funderName, awardTitle, awardUri, awardNumber', () => { + const dialogRef = TestBed.inject(DynamicDialogRef); + const closeSpy = jest.spyOn(dialogRef, 'close'); + component.addFundingEntry(); + component.fundingEntries.at(0).patchValue({ funderName: 'Funder A', awardTitle: 'Award A' }); + component.fundingEntries.at(1).patchValue({ funderName: 'Funder B', awardTitle: 'Award B' }); + fixture.detectChanges(); + component.save(); + expect(closeSpy).toHaveBeenCalledWith({ + fundingEntries: [ + expect.objectContaining({ funderName: 'Funder A', awardTitle: 'Award A' }), + expect.objectContaining({ funderName: 'Funder B', awardTitle: 'Award B' }), + ], + }); + }); - expect(searchSpy).toHaveBeenCalledTimes(3); - expect(searchSpy).toHaveBeenNthCalledWith(1, 'first'); - expect(searchSpy).toHaveBeenNthCalledWith(2, 'second'); - expect(searchSpy).toHaveBeenNthCalledWith(3, 'third'); + it('should not save when awardUri is invalid', () => { + const dialogRef = TestBed.inject(DynamicDialogRef); + const closeSpy = jest.spyOn(dialogRef, 'close'); + const entry = component.fundingEntries.at(0); + entry.patchValue({ + funderName: 'Test Funder', + awardUri: 'not-a-valid-url', + }); + fixture.detectChanges(); + component.save(); + expect(closeSpy).not.toHaveBeenCalled(); }); }); diff --git a/src/app/features/metadata/dialogs/funding-dialog/funding-dialog.component.ts b/src/app/features/metadata/dialogs/funding-dialog/funding-dialog.component.ts index 0e2f80403..6ee276d68 100644 --- a/src/app/features/metadata/dialogs/funding-dialog/funding-dialog.component.ts +++ b/src/app/features/metadata/dialogs/funding-dialog/funding-dialog.component.ts @@ -15,7 +15,14 @@ import { FormArray, FormControl, FormGroup, ReactiveFormsModule, Validators } fr import { CustomValidators } from '@osf/shared/helpers/custom-form-validators.helper'; -import { Funder, FundingDialogResult, FundingEntryForm, FundingForm, SupplementData } from '../../models'; +import { + Funder, + FundingDialogResult, + FundingEntryForm, + FundingForm, + RorFunderOption, + SupplementData, +} from '../../models'; import { GetFundersList, MetadataSelectors } from '../../store'; @Component({ @@ -33,15 +40,6 @@ export class FundingDialogComponent implements OnInit { fundersList = select(MetadataSelectors.getFundersList); fundersLoading = select(MetadataSelectors.getFundersLoading); - funderOptions = computed(() => { - const funders = this.fundersList() || []; - return funders.map((funder) => ({ - label: funder.name, - value: funder.name, - id: funder.id, - uri: funder.uri, - })); - }); fundingForm = new FormGroup({ fundingEntries: new FormArray>([]) }); @@ -108,6 +106,18 @@ export class FundingDialogComponent implements OnInit { }); } + getOptionsForIndex(index: number): RorFunderOption[] { + const list = this.fundersList() ?? []; + const entry = this.fundingEntries.at(index); + const name = entry?.get('funderName')?.value; + + if (!name || list.some((f) => f.name === name)) { + return list; + } + + return [{ id: entry?.get('funderIdentifier')?.value ?? '', name }, ...list]; + } + addFundingEntry(supplement?: SupplementData): void { const entry = this.createFundingEntryGroup(supplement); this.fundingEntries.push(entry); @@ -132,8 +142,8 @@ export class FundingDialogComponent implements OnInit { const entry = this.fundingEntries.at(index); entry.patchValue({ funderName: selectedFunder.name, - funderIdentifier: selectedFunder.uri, - funderIdentifierType: 'Crossref Funder ID', + funderIdentifier: selectedFunder.id, + funderIdentifierType: 'ROR', }); } } diff --git a/src/app/features/metadata/mappers/index.ts b/src/app/features/metadata/mappers/index.ts index 43aace43c..5c0905874 100644 --- a/src/app/features/metadata/mappers/index.ts +++ b/src/app/features/metadata/mappers/index.ts @@ -1,2 +1,3 @@ export * from './cedar-records.mapper'; export * from './metadata.mapper'; +export * from './ror.mapper'; diff --git a/src/app/features/metadata/mappers/ror.mapper.ts b/src/app/features/metadata/mappers/ror.mapper.ts new file mode 100644 index 000000000..701b2a140 --- /dev/null +++ b/src/app/features/metadata/mappers/ror.mapper.ts @@ -0,0 +1,19 @@ +import { RorFunderOption, RorOrganization, RorSearchResponse } from '../models/ror.model'; + +export class RorMapper { + static toFunderOptions(response: RorSearchResponse): RorFunderOption[] { + return response.items.map((org) => ({ + id: org.id, + name: this.getRorDisplayName(org), + })); + } + + static getRorDisplayName(org: RorOrganization): string { + const rorDisplay = org.names?.find((n) => n.types?.includes('ror_display')); + if (rorDisplay?.value) return rorDisplay.value; + const label = org.names?.find((n) => n.types?.includes('label')); + if (label?.value) return label.value; + if (org.names?.length && org.names[0].value) return org.names[0].value; + return org.id ?? ''; + } +} diff --git a/src/app/features/metadata/models/index.ts b/src/app/features/metadata/models/index.ts index 5d1c3aded..30e978c95 100644 --- a/src/app/features/metadata/models/index.ts +++ b/src/app/features/metadata/models/index.ts @@ -5,3 +5,4 @@ export * from './funding-dialog.model'; export * from './metadata.model'; export * from './metadata-json-api.model'; export * from './resource-information-form.model'; +export * from './ror.model'; diff --git a/src/app/features/metadata/models/metadata.model.ts b/src/app/features/metadata/models/metadata.model.ts index 43c44bf84..00e71c8bc 100644 --- a/src/app/features/metadata/models/metadata.model.ts +++ b/src/app/features/metadata/models/metadata.model.ts @@ -40,33 +40,3 @@ export interface Funder { awardUri: string; awardTitle: string; } - -export interface CrossRefFundersResponse { - status: string; - 'message-type': string; - 'message-version': string; - message: CrossRefFundersMessage; -} - -export interface CrossRefFundersMessage { - 'items-per-page': number; - query: CrossRefQuery; - 'total-results': number; - items: CrossRefFunder[]; -} - -export interface CrossRefQuery { - 'start-index': number; - 'search-terms': string | null; -} - -export interface CrossRefFunder { - id: string; - location: string; - name: string; - 'alt-names': string[]; - uri: string; - replaces: string[]; - 'replaced-by': string[]; - tokens: string[]; -} diff --git a/src/app/features/metadata/models/ror.model.ts b/src/app/features/metadata/models/ror.model.ts new file mode 100644 index 000000000..5775df7e4 --- /dev/null +++ b/src/app/features/metadata/models/ror.model.ts @@ -0,0 +1,111 @@ +export interface RorAdmin { + created: { + date: string; + schema_version: string; + }; + last_modified: { + date: string; + schema_version: string; + }; +} + +export interface RorExternalId { + all: string[]; + preferred: string | null; + type: 'grid' | 'fundref' | 'isni' | 'wikidata'; +} + +export interface RorLink { + type: 'website' | 'wikipedia'; + value: string; +} + +export interface RorGeonamesDetails { + continent_code: string; + continent_name: string; + country_code: string; + country_name: string; + country_subdivision_code: string; + country_subdivision_name: string; + lat: number; + lng: number; + name: string; +} + +export interface RorLocation { + geonames_details: RorGeonamesDetails; + geonames_id: number; +} + +export interface RorName { + lang: string | null; + types: ('ror_display' | 'label' | 'alias' | 'acronym')[]; + value: string; +} + +export interface RorRelationship { + type: string; + id: string; + label: string; +} + +export interface RorOrganization { + id: string; + admin: RorAdmin; + domains: string[]; + established: number | null; + external_ids: RorExternalId[]; + links: RorLink[]; + locations: RorLocation[]; + names: RorName[]; + relationships: RorRelationship[]; + status: 'active' | 'inactive' | 'withdrawn'; + types: ( + | 'education' + | 'healthcare' + | 'company' + | 'archive' + | 'nonprofit' + | 'government' + | 'facility' + | 'other' + | 'funder' + )[]; +} + +export interface RorMetaCount { + id: string; + title: string; + count: number; +} + +export interface RorMeta { + types: RorMetaCount[]; + countries: RorMetaCount[]; + continents: RorMetaCount[]; + statuses: RorMetaCount[]; +} + +export interface RorSearchResponse { + items: RorOrganization[]; + meta: RorMeta; + number_of_results: number; + time_taken: number; +} + +export interface RorFunderOption { + id: string; + name: string; +} + +export interface RorDisplayData { + id: string; + displayName: string; + acronym?: string; + type: string; + country: string; + city: string; + established?: number; + website?: string; + status: string; +} diff --git a/src/app/features/metadata/services/metadata.service.ts b/src/app/features/metadata/services/metadata.service.ts index 75ae0c86b..ea45ecbe7 100644 --- a/src/app/features/metadata/services/metadata.service.ts +++ b/src/app/features/metadata/services/metadata.service.ts @@ -10,7 +10,7 @@ import { LicenseOptions } from '@osf/shared/models/license/license.model'; import { BaseNodeAttributesJsonApi } from '@osf/shared/models/nodes/base-node-attributes-json-api.model'; import { JsonApiService } from '@osf/shared/services/json-api.service'; -import { CedarRecordsMapper, MetadataMapper } from '../mappers'; +import { CedarRecordsMapper, MetadataMapper, RorMapper } from '../mappers'; import { CedarMetadataRecord, CedarMetadataRecordJsonApi, @@ -21,7 +21,8 @@ import { MetadataJsonApi, MetadataJsonApiResponse, } from '../models'; -import { CrossRefFundersResponse, CustomItemMetadataRecord, MetadataModel } from '../models/metadata.model'; +import { CustomItemMetadataRecord, MetadataModel } from '../models/metadata.model'; +import { RorFunderOption, RorSearchResponse } from '../models/ror.model'; @Injectable({ providedIn: 'root', @@ -78,14 +79,18 @@ export class MetadataService { ); } - getFundersList(searchQuery?: string): Observable { - let url = `${this.funderApiUrl}funders?mailto=support%40osf.io`; + getFundersList(searchQuery?: string): Observable { + let url = `${this.funderApiUrl}/organizations?filter=types:funder`; if (searchQuery && searchQuery.trim()) { url += `&query=${encodeURIComponent(searchQuery.trim())}`; } - return this.jsonApiService.get(url); + const headers = this.environment.rorClientId ? { 'Client-Id': this.environment.rorClientId } : undefined; + + return this.jsonApiService + .get(url, undefined, undefined, headers) + .pipe(map((response) => RorMapper.toFunderOptions(response))); } getMetadataCedarTemplates(url?: string): Observable { diff --git a/src/app/features/metadata/store/metadata.model.ts b/src/app/features/metadata/store/metadata.model.ts index 1deae0c00..a5e05dc5b 100644 --- a/src/app/features/metadata/store/metadata.model.ts +++ b/src/app/features/metadata/store/metadata.model.ts @@ -6,12 +6,13 @@ import { } from '@osf/features/metadata/models'; import { AsyncStateModel } from '@osf/shared/models/store/async-state.model'; -import { CrossRefFunder, MetadataModel } from '../models'; +import { MetadataModel } from '../models'; +import { RorFunderOption } from '../models/ror.model'; export interface MetadataStateModel { metadata: AsyncStateModel; customMetadata: AsyncStateModel; - fundersList: AsyncStateModel; + fundersList: AsyncStateModel; cedarTemplates: AsyncStateModel; cedarRecord: AsyncStateModel; cedarRecords: AsyncStateModel; diff --git a/src/app/features/metadata/store/metadata.state.ts b/src/app/features/metadata/store/metadata.state.ts index 245895fd9..305f9d7ab 100644 --- a/src/app/features/metadata/store/metadata.state.ts +++ b/src/app/features/metadata/store/metadata.state.ts @@ -118,9 +118,9 @@ export class MetadataState { }); return this.metadataService.getFundersList(action.search).pipe( - tap((response) => { + tap((options) => { ctx.patchState({ - fundersList: { data: response.message.items, isLoading: false, error: null }, + fundersList: { data: options, isLoading: false, error: null }, }); }), catchError((error) => handleSectionError(ctx, 'fundersList', error)) diff --git a/src/app/shared/models/environment.model.ts b/src/app/shared/models/environment.model.ts index 9b399bbed..5a5bcba54 100644 --- a/src/app/shared/models/environment.model.ts +++ b/src/app/shared/models/environment.model.ts @@ -5,6 +5,7 @@ export interface EnvironmentModel { shareTroveUrl: string; addonsApiUrl: string; funderApiUrl: string; + rorClientId: string; casUrl: string; recaptchaSiteKey: string; twitterHandle: string; diff --git a/src/app/shared/services/json-api.service.ts b/src/app/shared/services/json-api.service.ts index 09865805c..41fec68ee 100644 --- a/src/app/shared/services/json-api.service.ts +++ b/src/app/shared/services/json-api.service.ts @@ -11,8 +11,13 @@ import { JsonApiResponse } from '@osf/shared/models/common/json-api.model'; export class JsonApiService { http: HttpClient = inject(HttpClient); - get(url: string, params?: Record, context?: HttpContext): Observable { - return this.http.get(url, { params: this.buildHttpParams(params), context }); + get( + url: string, + params?: Record, + context?: HttpContext, + headers?: Record + ): Observable { + return this.http.get(url, { params: this.buildHttpParams(params), context, headers }); } private buildHttpParams(params?: Record): HttpParams { diff --git a/src/assets/config/template.json b/src/assets/config/template.json index 55891f4a1..826cb39c4 100644 --- a/src/assets/config/template.json +++ b/src/assets/config/template.json @@ -4,6 +4,7 @@ "shareTroveUrl": "", "addonsApiUrl": "", "funderApiUrl": "", + "rorClientId": "", "casUrl": "", "recaptchaSiteKey": "", "dataciteTrackerRepoId": null, diff --git a/src/environments/environment.development.ts b/src/environments/environment.development.ts index c4230b680..a7df308e9 100644 --- a/src/environments/environment.development.ts +++ b/src/environments/environment.development.ts @@ -27,9 +27,10 @@ export const environment = { */ addonsApiUrl: 'https://addons.staging3.osf.io/v1', /** - * API endpoint for funder metadata resolution via Crossref. + * API endpoint for funder metadata resolution via ROR. */ - funderApiUrl: 'https://api.crossref.org/', + funderApiUrl: 'https://api.ror.org/v2', + rorClientId: '', /** * URL for OSF Central Authentication Service (CAS). */ diff --git a/src/environments/environment.docker.ts b/src/environments/environment.docker.ts index 92a84f2ee..a84e569ab 100644 --- a/src/environments/environment.docker.ts +++ b/src/environments/environment.docker.ts @@ -4,7 +4,8 @@ export const environment = { apiDomainUrl: 'http://localhost:8000', shareTroveUrl: 'https://localhost:8003/trove', addonsApiUrl: 'http://localhost:8004/v1', - funderApiUrl: 'https://api.crossref.org/', + funderApiUrl: 'https://api.ror.org/v2', + rorClientId: '', casUrl: 'http://localhost:8080', recaptchaSiteKey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI', twitterHandle: 'OSFramework', diff --git a/src/environments/environment.staging.ts b/src/environments/environment.staging.ts index a7725102f..6e1406f77 100644 --- a/src/environments/environment.staging.ts +++ b/src/environments/environment.staging.ts @@ -27,9 +27,10 @@ export const environment = { */ addonsApiUrl: 'https://addons.staging4.osf.io/v1', /** - * API endpoint for funder metadata resolution via Crossref. + * API endpoint for funder metadata resolution via ROR. */ - funderApiUrl: 'https://api.crossref.org/', + funderApiUrl: 'https://api.ror.org/v2', + rorClientId: '', /** * URL for OSF Central Authentication Service (CAS). */ diff --git a/src/environments/environment.test-osf.ts b/src/environments/environment.test-osf.ts index 87f7a0c6f..68b2ef785 100644 --- a/src/environments/environment.test-osf.ts +++ b/src/environments/environment.test-osf.ts @@ -7,7 +7,7 @@ export const environment = { apiDomainUrl: 'https://api.test.osf.io', shareTroveUrl: 'https://staging-share.osf.io/trove', addonsApiUrl: 'https://addons.test.osf.io/v1', - funderApiUrl: 'https://api.crossref.org/', + funderApiUrl: 'https://api.ror.org/v2', casUrl: 'https://accounts.test.osf.io', recaptchaSiteKey: '6LdPCWgUAAAAAKAD7jTctbZwF4sqYQjTYF3c3pOk', twitterHandle: 'OSFramework', diff --git a/src/environments/environment.test.ts b/src/environments/environment.test.ts index 08568d965..d36b8e42f 100644 --- a/src/environments/environment.test.ts +++ b/src/environments/environment.test.ts @@ -7,7 +7,7 @@ export const environment = { apiDomainUrl: 'https://api.test.osf.io', shareTroveUrl: 'https://staging-share.osf.io/trove', addonsApiUrl: 'https://addons.test.osf.io/v1', - funderApiUrl: 'https://api.crossref.org/', + funderApiUrl: 'https://api.ror.org/v2', casUrl: 'https://accounts.test.osf.io', recaptchaSiteKey: '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI', twitterHandle: 'OSFramework', diff --git a/src/environments/environment.ts b/src/environments/environment.ts index a7725102f..6e1406f77 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -27,9 +27,10 @@ export const environment = { */ addonsApiUrl: 'https://addons.staging4.osf.io/v1', /** - * API endpoint for funder metadata resolution via Crossref. + * API endpoint for funder metadata resolution via ROR. */ - funderApiUrl: 'https://api.crossref.org/', + funderApiUrl: 'https://api.ror.org/v2', + rorClientId: '', /** * URL for OSF Central Authentication Service (CAS). */ diff --git a/src/testing/mocks/funder.mock.ts b/src/testing/mocks/funder.mock.ts index 415e6f7a5..f8ea586b2 100644 --- a/src/testing/mocks/funder.mock.ts +++ b/src/testing/mocks/funder.mock.ts @@ -4,7 +4,7 @@ export const MOCK_FUNDERS: Funder[] = [ { funderName: 'National Science Foundation', funderIdentifier: '10.13039/100000001', - funderIdentifierType: 'Crossref Funder ID', + funderIdentifierType: 'ROR', awardNumber: 'NSF-1234567', awardUri: 'https://www.nsf.gov/awardsearch/showAward?AWD_ID=1234567', awardTitle: 'Research Grant for Advanced Computing', @@ -12,7 +12,7 @@ export const MOCK_FUNDERS: Funder[] = [ { funderName: 'National Institutes of Health', funderIdentifier: '10.13039/100000002', - funderIdentifierType: 'Crossref Funder ID', + funderIdentifierType: 'ROR', awardNumber: 'NIH-R01-GM123456', awardUri: 'https://reporter.nih.gov/project-details/12345678', awardTitle: 'Biomedical Research Project',