Skip to content

Commit be89670

Browse files
committed
fix(types): correct field/domain/subfield entity types in relationship queries
- Fix ENTITY_RELATIONSHIP_QUERIES registry for topics outbound relationships: - topic_part_of_field: targetType 'topics' → 'fields' - field_part_of_domain: targetType 'topics' → 'domains' - topic_part_of_subfield: targetType 'topics' → 'subfields' - Add path-based URL handling in convertOpenAlexToInternalLink: - Support URLs like openalex.org/fields/17, /domains/3, /subfields/1703 - Handle both api.openalex.org and openalex.org formats
1 parent 0be4f2c commit be89670

File tree

2 files changed

+38
-9
lines changed

2 files changed

+38
-9
lines changed

apps/web/src/utils/openalex-link-conversion.ts

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,29 @@ export const getEntityTypeFromId = (id: string): string | null => {
3434
return typeMap[prefix] || null;
3535
};
3636

37+
/** All valid OpenAlex entity type paths */
38+
const OPENALEX_ENTITY_PATHS = [
39+
'works', 'authors', 'sources', 'institutions', 'topics',
40+
'publishers', 'funders', 'concepts',
41+
'fields', 'domains', 'subfields', 'keywords'
42+
] as const;
43+
3744
/**
3845
* Converts an OpenAlex URL or ID to an internal app path
46+
* Handles multiple URL formats:
47+
* - Direct ID: https://openalex.org/W1234567890
48+
* - Path + ID: https://openalex.org/works/W1234567890
49+
* - Path-based: https://openalex.org/fields/17, /keywords/machine-learning
50+
* - API URLs: https://api.openalex.org/works/W1234567890
3951
* @param url
4052
*/
4153
export const convertOpenAlexToInternalLink = (url: string): ConvertedLink => {
4254
const originalUrl = url;
4355

44-
// Case 1: OpenAlex entity URL (https://openalex.org/A5017898742)
45-
const entityUrlMatch = url.match(/https?:\/\/openalex\.org\/([ACFIPSTVW]\d+)/i);
46-
if (entityUrlMatch) {
47-
const entityId = entityUrlMatch[1];
56+
// Case 1: Direct entity ID URL (https://openalex.org/W1234567890)
57+
const directIdMatch = url.match(/https?:\/\/openalex\.org\/([ACFIPSTVW]\d+)$/i);
58+
if (directIdMatch) {
59+
const entityId = directIdMatch[1];
4860
const entityType = getEntityTypeFromId(entityId);
4961
if (entityType) {
5062
return {
@@ -55,7 +67,24 @@ export const convertOpenAlexToInternalLink = (url: string): ConvertedLink => {
5567
}
5668
}
5769

58-
// Case 2: OpenAlex API URL (https://api.openalex.org/works?filter=author.id:A5017898742)
70+
// Case 2: Path-based entity URLs (https://openalex.org/{entityType}/{id})
71+
// Handles both ID-based (works/W123) and numeric/string IDs (fields/17, keywords/machine-learning)
72+
const entityPathPattern = new RegExp(
73+
String.raw`https?://(?:api\.)?openalex\.org/(${OPENALEX_ENTITY_PATHS.join('|')})/([^/?#]+)`,
74+
'i'
75+
);
76+
const pathMatch = url.match(entityPathPattern);
77+
if (pathMatch) {
78+
const entityType = pathMatch[1].toLowerCase();
79+
const entityId = pathMatch[2];
80+
return {
81+
isOpenAlexLink: true,
82+
internalPath: `/${entityType}/${entityId}`,
83+
originalUrl,
84+
};
85+
}
86+
87+
// Case 3: OpenAlex API URL with query params (https://api.openalex.org/works?filter=...)
5988
const apiUrlMatch = url.match(/https?:\/\/api\.openalex\.org\/([^?]+)(\?.*)?/i);
6089
if (apiUrlMatch) {
6190
const path = apiUrlMatch[1];
@@ -67,7 +96,7 @@ export const convertOpenAlexToInternalLink = (url: string): ConvertedLink => {
6796
};
6897
}
6998

70-
// Case 3: Just an OpenAlex ID (A5017898742)
99+
// Case 4: Just an OpenAlex ID (A5017898742)
71100
if (isOpenAlexId(url)) {
72101
const entityType = getEntityTypeFromId(url);
73102
if (entityType) {

packages/types/src/entities/relationship-queries.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ export const ENTITY_RELATIONSHIP_QUERIES: Record<EntityType, EntityRelationshipQ
500500
{
501501
source: 'embedded',
502502
type: 'topic_part_of_field',
503-
targetType: 'topics',
503+
targetType: 'fields',
504504
label: 'Field',
505505
extractEmbedded: (entityData) => {
506506
const field = entityData.field as Record<string, unknown> | undefined;
@@ -516,7 +516,7 @@ export const ENTITY_RELATIONSHIP_QUERIES: Record<EntityType, EntityRelationshipQ
516516
{
517517
source: 'embedded',
518518
type: 'field_part_of_domain',
519-
targetType: 'topics',
519+
targetType: 'domains',
520520
label: 'Domain',
521521
extractEmbedded: (entityData) => {
522522
const domain = entityData.domain as Record<string, unknown> | undefined;
@@ -532,7 +532,7 @@ export const ENTITY_RELATIONSHIP_QUERIES: Record<EntityType, EntityRelationshipQ
532532
{
533533
source: 'embedded',
534534
type: 'topic_part_of_subfield',
535-
targetType: 'topics',
535+
targetType: 'subfields',
536536
label: 'Subfield',
537537
extractEmbedded: (entityData) => {
538538
const subfield = entityData.subfield as Record<string, unknown> | undefined;

0 commit comments

Comments
 (0)