🪢 Added resolvers for LoCG

This commit is contained in:
2026-03-04 23:36:10 -05:00
parent cad3326417
commit b753481754
10 changed files with 3906 additions and 253 deletions

196
models/graphql/resolvers.ts Normal file
View File

@@ -0,0 +1,196 @@
/**
* GraphQL Resolvers for ThreeTwo Metadata Service
* Maps GraphQL queries to Moleculer service actions
*/
export const resolvers = {
Query: {
/**
* Search ComicVine for volumes, issues, characters, etc.
*/
searchComicVine: async (_: any, { input }: any, context: any) => {
const { broker } = context;
if (!broker) {
throw new Error("Broker not available in context");
}
return broker.call("comicvine.search", {
query: input.query,
resources: input.resources,
format: input.format || "json",
sort: input.sort,
field_list: input.field_list,
limit: input.limit?.toString(),
offset: input.offset?.toString(),
});
},
/**
* Advanced volume-based search with scoring and filtering
*/
volumeBasedSearch: async (_: any, { input }: any, context: any) => {
const { broker } = context;
if (!broker) {
throw new Error("Broker not available in context");
}
const result = await broker.call("comicvine.volumeBasedSearch", {
query: input.query,
resources: input.resources,
format: input.format || "json",
limit: input.limit,
offset: input.offset,
fieldList: input.fieldList,
scorerConfiguration: input.scorerConfiguration,
rawFileDetails: input.rawFileDetails,
});
// Transform the result to match GraphQL schema
return {
results: result.results || result,
totalResults: result.totalResults || result.length || 0,
};
},
/**
* Get volume details by URI
*/
getVolume: async (_: any, { input }: any, context: any) => {
const { broker } = context;
if (!broker) {
throw new Error("Broker not available in context");
}
return broker.call("comicvine.getVolumes", {
volumeURI: input.volumeURI,
fieldList: input.fieldList,
});
},
/**
* Get all issues for a series by comic object ID
*/
getIssuesForSeries: async (_: any, { comicObjectId }: any, context: any) => {
const { broker } = context;
if (!broker) {
throw new Error("Broker not available in context");
}
return broker.call("comicvine.getIssuesForSeries", {
comicObjectId,
});
},
/**
* Get generic ComicVine resource (issues, volumes, etc.)
*/
getComicVineResource: async (_: any, { input }: any, context: any) => {
const { broker } = context;
if (!broker) {
throw new Error("Broker not available in context");
}
return broker.call("comicvine.getResource", {
resources: input.resources,
filter: input.filter,
fieldList: input.fieldList,
});
},
/**
* Get story arcs for a volume
*/
getStoryArcs: async (_: any, { volumeId }: any, context: any) => {
const { broker } = context;
if (!broker) {
throw new Error("Broker not available in context");
}
return broker.call("comicvine.getStoryArcs", {
volumeId,
});
},
/**
* Get weekly pull list from League of Comic Geeks
*/
getWeeklyPullList: async (_: any, { input }: any, context: any) => {
const { broker } = context;
if (!broker) {
throw new Error("Broker not available in context");
}
const locgResponse = await broker.call("comicvine.getWeeklyPullList", {
startDate: input.startDate,
currentPage: input.currentPage.toString(),
pageSize: input.pageSize.toString(),
});
// Transform LOCG response to match GraphQL schema
return {
result: locgResponse.result.map((item: any) => ({
name: item.issueName,
publisher: item.publisher,
url: item.issueUrl,
cover: item.coverImageUrl,
description: item.description || null,
price: item.price || null,
rating: item.rating || null,
pulls: item.pulls || null,
potw: item.potw || null,
publicationDate: item.publicationDate || null,
})),
meta: locgResponse.meta,
};
},
/**
* Fetch resource from Metron API
*/
fetchMetronResource: async (_: any, { input }: any, context: any) => {
const { broker } = context;
if (!broker) {
throw new Error("Broker not available in context");
}
const result = await broker.call("metron.fetchResource", {
resource: input.resource,
method: input.method,
query: input.query,
});
return {
data: result,
status: 200,
};
},
},
Mutation: {
/**
* Placeholder for future mutations
*/
_empty: (): null => null,
},
// Custom scalar resolver for JSON
JSON: {
__parseValue(value: any): any {
return value;
},
__serialize(value: any): any {
return value;
},
__parseLiteral(ast: any): any {
return ast.value;
},
},
};

357
models/graphql/typedef.ts Normal file
View File

@@ -0,0 +1,357 @@
import { gql } from "graphql-tag";
/**
* GraphQL Type Definitions for ThreeTwo Metadata Service
* Covers ComicVine and Metron API endpoints
*/
export const typeDefs = gql`
# ============================================
# ComicVine Types
# ============================================
# Image URLs for various sizes
type ImageUrls {
icon_url: String
medium_url: String
screen_url: String
screen_large_url: String
small_url: String
super_url: String
thumb_url: String
tiny_url: String
original_url: String
image_tags: String
}
# Publisher information
type Publisher {
id: Int
name: String
api_detail_url: String
}
# Volume information
type Volume {
id: Int!
name: String!
api_detail_url: String
site_detail_url: String
start_year: String
publisher: Publisher
count_of_issues: Int
image: ImageUrls
description: String
deck: String
}
# Issue information
type Issue {
id: Int!
name: String
issue_number: String
api_detail_url: String
site_detail_url: String
cover_date: String
store_date: String
volume: Volume
image: ImageUrls
description: String
person_credits: [PersonCredit!]
character_credits: [CharacterCredit!]
team_credits: [TeamCredit!]
location_credits: [LocationCredit!]
story_arc_credits: [StoryArcCredit!]
}
# Person credit (writer, artist, etc.)
type PersonCredit {
id: Int
name: String
api_detail_url: String
site_detail_url: String
role: String
}
# Character credit
type CharacterCredit {
id: Int
name: String
api_detail_url: String
site_detail_url: String
}
# Team credit
type TeamCredit {
id: Int
name: String
api_detail_url: String
site_detail_url: String
}
# Location credit
type LocationCredit {
id: Int
name: String
api_detail_url: String
site_detail_url: String
}
# Story arc credit
type StoryArcCredit {
id: Int
name: String
api_detail_url: String
site_detail_url: String
deck: String
description: String
image: ImageUrls
}
# ComicVine search result
type ComicVineSearchResult {
error: String!
limit: Int!
offset: Int!
number_of_page_results: Int!
number_of_total_results: Int!
status_code: Int!
results: [SearchResultItem!]!
}
# Generic search result item (can be volume, issue, etc.)
type SearchResultItem {
id: Int
name: String
api_detail_url: String
site_detail_url: String
image: ImageUrls
description: String
deck: String
# Volume-specific fields
start_year: String
publisher: Publisher
count_of_issues: Int
# Issue-specific fields
issue_number: String
volume: Volume
cover_date: String
}
# Volume-based search result with scoring
type VolumeSearchResult {
volume: Volume!
score: Float
matchedIssues: [Issue!]
}
# Volume-based search response
type VolumeBasedSearchResponse {
results: [VolumeSearchResult!]!
totalResults: Int!
}
# Weekly pull list item (from League of Comic Geeks)
type PullListItem {
name: String
publisher: String
url: String
cover: String
description: String
price: String
rating: Float
pulls: Int
potw: Int
publicationDate: String
}
# Paginated pull list response
type PullListResponse {
result: [PullListItem!]!
meta: PaginationMeta!
}
# Pagination metadata
type PaginationMeta {
currentPage: Int!
totalPages: Int!
pageSize: Int!
totalCount: Int!
hasNextPage: Boolean!
hasPreviousPage: Boolean!
}
# Story arc with enriched data
type StoryArc {
id: Int!
name: String!
deck: String
description: String
image: ImageUrls
issues: [Issue!]
}
# Generic ComicVine resource response
type ComicVineResourceResponse {
error: String!
limit: Int!
offset: Int!
number_of_page_results: Int!
number_of_total_results: Int!
status_code: Int!
results: [SearchResultItem!]!
}
# Volume detail response
type VolumeDetailResponse {
error: String!
status_code: Int!
results: Volume!
}
# Issues for series response
type IssuesForSeriesResponse {
error: String!
limit: Int!
offset: Int!
number_of_page_results: Int!
number_of_total_results: Int!
status_code: Int!
results: [Issue!]!
}
# ============================================
# Metron Types
# ============================================
# Generic Metron resource (flexible JSON response)
scalar JSON
type MetronResponse {
data: JSON
status: Int!
}
# ============================================
# Input Types
# ============================================
# Search parameters
input SearchInput {
query: String!
resources: String!
format: String
sort: String
field_list: String
limit: Int
offset: Int
}
# Volume-based search configuration
input VolumeSearchInput {
query: String!
resources: String!
format: String
limit: Int
offset: Int
fieldList: String
scorerConfiguration: ScorerConfigurationInput
rawFileDetails: JSON
}
# Scorer configuration for matching
input ScorerConfigurationInput {
searchParams: SearchParamsInput
}
# Search parameters for scoring
input SearchParamsInput {
name: String
number: String
year: String
volume: String
}
# Get volumes input
input GetVolumesInput {
volumeURI: String!
fieldList: String
}
# Get resource input
input GetResourceInput {
resources: String!
filter: String
fieldList: String
}
# Weekly pull list input
input WeeklyPullListInput {
startDate: String!
currentPage: Int!
pageSize: Int!
}
# Metron fetch resource input
input MetronFetchInput {
resource: String!
method: String!
query: String
}
# ============================================
# Queries
# ============================================
type Query {
"""
Search ComicVine for volumes, issues, characters, etc.
"""
searchComicVine(input: SearchInput!): ComicVineSearchResult!
"""
Advanced volume-based search with scoring and filtering
"""
volumeBasedSearch(input: VolumeSearchInput!): VolumeBasedSearchResponse!
"""
Get volume details by URI
"""
getVolume(input: GetVolumesInput!): VolumeDetailResponse!
"""
Get all issues for a series by comic object ID
"""
getIssuesForSeries(comicObjectId: ID!): IssuesForSeriesResponse!
"""
Get generic ComicVine resource (issues, volumes, etc.)
"""
getComicVineResource(input: GetResourceInput!): ComicVineResourceResponse!
"""
Get story arcs for a volume
"""
getStoryArcs(volumeId: Int!): [StoryArc!]!
"""
Get weekly pull list from League of Comic Geeks
"""
getWeeklyPullList(input: WeeklyPullListInput!): PullListResponse!
"""
Fetch resource from Metron API
"""
fetchMetronResource(input: MetronFetchInput!): MetronResponse!
}
# ============================================
# Mutations
# ============================================
type Mutation {
"""
Placeholder for future mutations
"""
_empty: String
}
`;