From 46ba2a84f10bc91ea0e43831b7ec506c43b5619b Mon Sep 17 00:00:00 2001 From: Tony Date: Thu, 25 Jul 2024 22:38:31 +0800 Subject: [PATCH] feat(route): inspirehep (#16261) --- lib/routes/inspirehep/author.ts | 46 ++++++ lib/routes/inspirehep/literature.ts | 42 ++++++ lib/routes/inspirehep/namespace.ts | 7 + lib/routes/inspirehep/types.ts | 210 ++++++++++++++++++++++++++++ lib/routes/inspirehep/utils.ts | 15 ++ 5 files changed, 320 insertions(+) create mode 100644 lib/routes/inspirehep/author.ts create mode 100644 lib/routes/inspirehep/literature.ts create mode 100644 lib/routes/inspirehep/namespace.ts create mode 100644 lib/routes/inspirehep/types.ts create mode 100644 lib/routes/inspirehep/utils.ts diff --git a/lib/routes/inspirehep/author.ts b/lib/routes/inspirehep/author.ts new file mode 100644 index 00000000000000..ed7eb2b6d49076 --- /dev/null +++ b/lib/routes/inspirehep/author.ts @@ -0,0 +1,46 @@ +import { Route } from '@/types'; +import ofetch from '@/utils/ofetch'; +import { AuthorResponse, LiteratureResponse } from './types'; +import cache from '@/utils/cache'; + +import { baseUrl, parseLiterature } from './utils'; + +export const route: Route = { + path: '/authors/:id', + example: '/inspirehep/authors/1696909', + parameters: { id: 'Author ID' }, + name: 'Author Search', + maintainers: ['TonyRL'], + radar: [ + { + source: ['inspirehep.net/authors/:id'], + }, + ], + handler, +}; + +export const getAuthorById = (id: string) => cache.tryGet(`inspirehep:author:${id}`, () => ofetch(`${baseUrl}/api/authors/${id}`)); + +async function handler(ctx) { + const id = ctx.req.param('id'); + const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 25; + + const authorInfo = (await getAuthorById(id)) as AuthorResponse; + const response = await ofetch(`${baseUrl}/api/literature`, { + query: { + sort: 'mostrecent', + size: limit, + page: 1, + search_type: 'hep-author-publication', + author: authorInfo.metadata.facet_author_name, + }, + }); + + const items = parseLiterature(response.hits.hits); + + return { + title: `${authorInfo.metadata.name.preferred_name} - INSPIRE`, + link: `${baseUrl}/authors/${id}`, + item: items, + }; +} diff --git a/lib/routes/inspirehep/literature.ts b/lib/routes/inspirehep/literature.ts new file mode 100644 index 00000000000000..64a2f1f545caea --- /dev/null +++ b/lib/routes/inspirehep/literature.ts @@ -0,0 +1,42 @@ +import { Route } from '@/types'; +import ofetch from '@/utils/ofetch'; +import { LiteratureResponse } from './types'; + +import { baseUrl, parseLiterature } from './utils'; + +export const route: Route = { + path: '/literature/:q', + example: '/inspirehep/literature/Physics', + parameters: { q: 'Search keyword' }, + name: 'Literature Search', + maintainers: ['TonyRL'], + radar: [ + { + source: ['inspirehep.net/literature'], + target: (_params, url) => `/inspirehep/literature/${new URL(url).searchParams.get('q')}`, + }, + ], + handler, +}; + +async function handler(ctx) { + const q = ctx.req.param('q'); + const limit = ctx.req.query('limit') ? Number.parseInt(ctx.req.query('limit')) : 25; + + const response = await ofetch(`${baseUrl}/api/literature`, { + query: { + sort: 'mostrecent', + size: limit, + page: 1, + q, + }, + }); + + const items = parseLiterature(response.hits.hits); + + return { + title: 'Literature Search - INSPIRE', + link: `${baseUrl}/literature?sort=mostrecent&size=${limit}&page=1&q=${q}`, + item: items, + }; +} diff --git a/lib/routes/inspirehep/namespace.ts b/lib/routes/inspirehep/namespace.ts new file mode 100644 index 00000000000000..514cf79ce96933 --- /dev/null +++ b/lib/routes/inspirehep/namespace.ts @@ -0,0 +1,7 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: 'INSPIRE', + url: 'inspirehep.net', + categories: ['journal'], +}; diff --git a/lib/routes/inspirehep/types.ts b/lib/routes/inspirehep/types.ts new file mode 100644 index 00000000000000..44c44cfb6a9010 --- /dev/null +++ b/lib/routes/inspirehep/types.ts @@ -0,0 +1,210 @@ +interface Links { + self?: string; + next?: string; + bibtex: string; + 'latex-eu': string; + 'latex-us': string; + json: string; + cv: string; + citations: string; +} + +interface Metadata { + number_of_authors: number; + date: string; + publication_info: { + year: number; + artid?: string; + journal_volume?: string; + journal_title?: string; + journal_issue?: string; + }[]; + citation_count: number; + is_collection_hidden: boolean; + authors: { + uuid: string; + record: { + $ref: string; + }; + full_name: string; + first_name: string; + ids: { + schema: string; + value: string; + }[]; + last_name: string; + recid: number; + affiliations: { + value: string; + record: { + $ref: string; + }; + curated_relation?: boolean; + }[]; + raw_affiliations: { + value: string; + }[]; + curated_relation?: boolean; + }[]; + citation_count_without_self_citations: number; + titles: { + title: string; + source: string; + }[]; + texkeys: string[]; + imprints: { + date: string; + publisher?: string; + }[]; + abstracts: { + value: string; + source: string; + }[]; + document_type: string[]; + control_number: number; + inspire_categories: { + term: string; + source?: string; + }[]; + number_of_pages?: number; + keywords?: { + value: string; + source?: string; + schema?: string; + }[]; + thesis_info?: { + institutions: { + name: string; + record: { + $ref: string; + }; + curated_relation?: boolean; + }[]; + degree_type: string; + date: string; + }; + license?: { + url: string; + license: string; + imposing?: string; + }[]; + persistent_identifiers?: { + value: string; + schema: string; + source: string; + }[]; + supervisors?: { + uuid: string; + record: { + $ref: string; + }; + full_name: string; + inspire_roles: string[]; + }[]; + isbns?: { + value: string; + medium?: string; + }[]; + urls?: { + value: string; + description?: string; + }[]; + dois?: { + value: string; + }[]; + number_of_references?: number; + external_system_identifiers?: { + url_name: string; + url_link: string; + }[]; + report_numbers?: { + value: string; + }[]; + accelerator_experiments?: { + name: string; + record: { + $ref: string; + }; + }[]; + documents?: { + source: string; + key: string; + url: string; + fulltext: boolean; + hidden: boolean; + filename: string; + }[]; + citation_pdf_urls?: string[]; + fulltext_links?: { + description: string; + value: string; + }[]; +} + +interface Literature { + created: string; + metadata: Metadata; + links: Links; + updated: string; + id: string; +} + +export interface AuthorResponse { + id: string; + uuid: string; + revision_id: number; + updated: string; + links: { + json: string; + }; + metadata: { + can_edit: boolean; + orcid: string; + bai: string; + facet_author_name: string; + should_display_positions: boolean; + positions: { + institution: string; + current: boolean; + display_date: string; + record: { + $ref: string; + }; + }[]; + project_membership: { + name: string; + record: { + $ref: string; + }; + current: boolean; + curated_relation: boolean; + }[]; + ids: { + value: string; + schema: string; + }[]; + name: { + value: string; + preferred_name: string; + }; + stub: boolean; + status: string; + deleted: boolean; + control_number: number; + legacy_version: string; + legacy_creation_date: string; + }; + created: string; +} + +export interface LiteratureResponse { + hits: { + hits: Literature[]; + total: number; + }; + links: Links; + sort_options: { + value: string; + title: string; + }[]; +} diff --git a/lib/routes/inspirehep/utils.ts b/lib/routes/inspirehep/utils.ts new file mode 100644 index 00000000000000..fe903b5e919e12 --- /dev/null +++ b/lib/routes/inspirehep/utils.ts @@ -0,0 +1,15 @@ +import { LiteratureResponse } from './types'; +import { parseDate } from '@/utils/parse-date'; + +export const baseUrl = 'https://inspirehep.net'; + +export const parseLiterature = (hits: LiteratureResponse['hits']['hits']) => + hits.map((item) => ({ + title: item.metadata.titles.map((t) => t.title).join(' '), + link: `${baseUrl}/literature/${item.id}`, + description: item.metadata.abstracts?.map((a) => `${a.value}`).join('
'), + pubDate: parseDate(item.created), + updated: parseDate(item.updated), + category: item.metadata.keywords?.map((k) => k.value), + author: item.metadata.authors.map((a) => `${a.first_name} ${a.last_name}${a.affiliations ? ` (${a.affiliations.map((aff) => aff.value).join(', ')})` : ''}`).join(', '), + }));