diff --git a/examples/frontend/components/ComicDetailContainer.tsx b/examples/frontend/components/ComicDetailContainer.tsx new file mode 100644 index 0000000..13aaf48 --- /dev/null +++ b/examples/frontend/components/ComicDetailContainer.tsx @@ -0,0 +1,139 @@ +/** + * ComicDetailContainer - GraphQL Version + * + * This file should replace the existing ComicDetailContainer.tsx + * Location: src/client/components/ComicDetail/ComicDetailContainer.tsx + * + * Key changes from REST version: + * 1. Uses executeGraphQLQuery instead of axios directly + * 2. Parses JSON strings from sourcedMetadata + * 3. Maps GraphQL 'id' to REST '_id' for backward compatibility + * 4. Better error and loading states + */ + +import React, { ReactElement } from "react"; +import { useParams } from "react-router-dom"; +import { ComicDetail } from "../ComicDetail/ComicDetail"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { executeGraphQLQuery, transformComicToRestFormat } from "../../services/api/GraphQLApi"; +import { + GET_COMIC_DETAIL_QUERY, + ComicDetailQueryResponse +} from "../../graphql/queries/comicDetail"; + +export const ComicDetailContainer = (): ReactElement | null => { + const { comicObjectId } = useParams<{ comicObjectId: string }>(); + const queryClient = useQueryClient(); + + const { + data: comicBookDetailData, + isLoading, + isError, + error, + } = useQuery({ + queryKey: ["comicBookMetadata", comicObjectId], + queryFn: async () => { + // Execute GraphQL query + const result = await executeGraphQLQuery( + GET_COMIC_DETAIL_QUERY, + { id: comicObjectId } + ); + + // Transform to REST format for backward compatibility + const transformedComic = transformComicToRestFormat(result.comic); + + // Return in the format expected by ComicDetail component + return { + data: transformedComic, + }; + }, + enabled: !!comicObjectId, // Only run query if we have an ID + staleTime: 5 * 60 * 1000, // Consider data fresh for 5 minutes + retry: 2, // Retry failed requests twice + }); + + if (isError) { + return ( +
+
+ Error loading comic: + + {error instanceof Error ? error.message : 'Unknown error'} + +
+
+ ); + } + + if (isLoading) { + return ( +
+
+
+ Loading comic details... +
+
+
+ ); + } + + return ( + comicBookDetailData?.data && ( + + ) + ); +}; + +/** + * Alternative implementation with feature flag for gradual rollout + * Uncomment this version if you want to toggle between REST and GraphQL + */ +/* +export const ComicDetailContainer = (): ReactElement | null => { + const { comicObjectId } = useParams<{ comicObjectId: string }>(); + const queryClient = useQueryClient(); + + // Feature flag to toggle between REST and GraphQL + const USE_GRAPHQL = import.meta.env.VITE_USE_GRAPHQL === 'true'; + + const { + data: comicBookDetailData, + isLoading, + isError, + error, + } = useQuery({ + queryKey: ["comicBookMetadata", comicObjectId], + queryFn: async () => { + if (USE_GRAPHQL) { + // GraphQL implementation + const result = await executeGraphQLQuery( + GET_COMIC_DETAIL_QUERY, + { id: comicObjectId } + ); + + const transformedComic = transformComicToRestFormat(result.comic); + + return { + data: transformedComic, + }; + } else { + // REST implementation (fallback) + const response = await axios({ + url: `${LIBRARY_SERVICE_BASE_URI}/getComicBookById`, + method: "POST", + data: { id: comicObjectId }, + }); + + return response; + } + }, + enabled: !!comicObjectId, + }); + + // ... rest of the component remains the same +}; +*/ diff --git a/examples/frontend/graphql-queries/comicDetail.ts b/examples/frontend/graphql-queries/comicDetail.ts new file mode 100644 index 0000000..fc36633 --- /dev/null +++ b/examples/frontend/graphql-queries/comicDetail.ts @@ -0,0 +1,248 @@ +/** + * GraphQL query to fetch complete comic detail data + * + * This file should be placed in the frontend project at: + * src/client/graphql/queries/comicDetail.ts + * + * Matches the data structure expected by ComicDetail component + */ + +export const GET_COMIC_DETAIL_QUERY = ` + query GetComicDetail($id: ID!) { + comic(id: $id) { + id + + # Raw file information + rawFileDetails { + name + filePath + fileSize + extension + mimeType + containedIn + pageCount + archive { + uncompressed + expandedPath + } + cover { + filePath + stats + } + } + + # Inferred metadata from filename parsing + inferredMetadata { + issue { + name + number + year + subtitle + } + } + + # Sourced metadata from various providers + sourcedMetadata { + comicInfo + comicvine + metron + gcd + locg { + name + publisher + url + cover + description + price + rating + pulls + potw + } + } + + # Import status + importStatus { + isImported + tagged + matchedResult { + score + } + } + + # Acquisition/download information + acquisition { + source { + wanted + name + } + directconnect { + downloads { + bundleId + name + size + } + } + torrent { + infoHash + name + announce + } + } + + # Timestamps + createdAt + updatedAt + } + } +`; + +/** + * TypeScript type for the query response + * Generated from GraphQL schema + */ +export interface ComicDetailQueryResponse { + comic: { + id: string; + rawFileDetails?: { + name?: string; + filePath?: string; + fileSize?: number; + extension?: string; + mimeType?: string; + containedIn?: string; + pageCount?: number; + archive?: { + uncompressed?: boolean; + expandedPath?: string; + }; + cover?: { + filePath?: string; + stats?: any; + }; + }; + inferredMetadata?: { + issue?: { + name?: string; + number?: number; + year?: string; + subtitle?: string; + }; + }; + sourcedMetadata?: { + comicInfo?: string; // JSON string - needs parsing + comicvine?: string; // JSON string - needs parsing + metron?: string; // JSON string - needs parsing + gcd?: string; // JSON string - needs parsing + locg?: { + name?: string; + publisher?: string; + url?: string; + cover?: string; + description?: string; + price?: string; + rating?: number; + pulls?: number; + potw?: number; + }; + }; + importStatus?: { + isImported?: boolean; + tagged?: boolean; + matchedResult?: { + score?: string; + }; + }; + acquisition?: { + source?: { + wanted?: boolean; + name?: string; + }; + directconnect?: { + downloads?: Array<{ + bundleId?: number; + name?: string; + size?: string; + }>; + }; + torrent?: Array<{ + infoHash?: string; + name?: string; + announce?: string[]; + }>; + }; + createdAt?: string; + updatedAt?: string; + }; +} + +/** + * Minimal query for basic comic information + * Use this when you only need basic details + */ +export const GET_COMIC_BASIC_QUERY = ` + query GetComicBasic($id: ID!) { + comic(id: $id) { + id + rawFileDetails { + name + filePath + fileSize + pageCount + } + inferredMetadata { + issue { + name + number + year + } + } + } + } +`; + +/** + * Query for comic metadata only (no file details) + * Use this when you only need metadata + */ +export const GET_COMIC_METADATA_QUERY = ` + query GetComicMetadata($id: ID!) { + comic(id: $id) { + id + sourcedMetadata { + comicInfo + comicvine + metron + gcd + locg { + name + publisher + description + rating + } + } + canonicalMetadata { + title { + value + provenance { + source + confidence + } + } + series { + value + provenance { + source + confidence + } + } + publisher { + value + provenance { + source + confidence + } + } + } + } + } +`; diff --git a/examples/frontend/graphql-queries/libraryQueries.ts b/examples/frontend/graphql-queries/libraryQueries.ts new file mode 100644 index 0000000..0628661 --- /dev/null +++ b/examples/frontend/graphql-queries/libraryQueries.ts @@ -0,0 +1,260 @@ +/** + * GraphQL queries for library operations + * Examples for getComicBooks, getComicBookGroups, getLibraryStatistics, and searchIssue + */ + +/** + * Query to get comic books with pagination and filtering + */ +export const GET_COMIC_BOOKS = ` + query GetComicBooks($paginationOptions: PaginationOptionsInput!, $predicate: PredicateInput) { + getComicBooks(paginationOptions: $paginationOptions, predicate: $predicate) { + docs { + id + canonicalMetadata { + title { + value + provenance { + source + confidence + } + } + series { + value + } + issueNumber { + value + } + publisher { + value + } + coverImage { + value + } + } + rawFileDetails { + name + filePath + fileSize + extension + } + createdAt + updatedAt + } + totalDocs + limit + page + totalPages + hasNextPage + hasPrevPage + nextPage + prevPage + pagingCounter + } + } +`; + +/** + * Query to get comic book groups (volumes) + */ +export const GET_COMIC_BOOK_GROUPS = ` + query GetComicBookGroups { + getComicBookGroups { + id + volumes { + id + name + count_of_issues + publisher { + id + name + } + start_year + image { + medium_url + thumb_url + } + description + site_detail_url + } + } + } +`; + +/** + * Query to get library statistics + */ +export const GET_LIBRARY_STATISTICS = ` + query GetLibraryStatistics { + getLibraryStatistics { + totalDocuments + comicDirectorySize { + totalSize + totalSizeInMB + totalSizeInGB + fileCount + } + statistics { + fileTypes { + id + data + } + publisherWithMostComicsInLibrary { + id + count + } + } + } + } +`; + +/** + * Example usage with variables for getComicBooks + */ +export const exampleGetComicBooksVariables = { + paginationOptions: { + page: 1, + limit: 10, + sort: "-createdAt", // Sort by creation date, descending + lean: false, + pagination: true, + }, + predicate: { + // Optional: Add filters here + // Example: { "canonicalMetadata.publisher.value": "Marvel" } + }, +}; + +/** + * Example: Get first page of comics + */ +export const exampleGetFirstPage = { + query: GET_COMIC_BOOKS, + variables: { + paginationOptions: { + page: 1, + limit: 20, + sort: "-createdAt", + }, + }, +}; + +/** + * Example: Get comics with specific filters + */ +export const exampleGetFilteredComics = { + query: GET_COMIC_BOOKS, + variables: { + paginationOptions: { + page: 1, + limit: 10, + }, + predicate: { + "importStatus.isImported": true, + }, + }, +}; + +/** + * Query to search issues using Elasticsearch + */ +export const SEARCH_ISSUE = ` + query SearchIssue($query: SearchIssueQueryInput, $pagination: SearchPaginationInput, $type: SearchType!) { + searchIssue(query: $query, pagination: $pagination, type: $type) { + hits { + total { + value + relation + } + max_score + hits { + _index + _id + _score + _source { + id + canonicalMetadata { + title { + value + } + series { + value + } + issueNumber { + value + } + publisher { + value + } + } + rawFileDetails { + name + filePath + } + } + } + } + took + timed_out + } + } +`; + +/** + * Example: Search all comics + */ +export const exampleSearchAll = { + query: SEARCH_ISSUE, + variables: { + type: "all", + pagination: { + size: 10, + from: 0, + }, + }, +}; + +/** + * Example: Search by volume name + */ +export const exampleSearchByVolumeName = { + query: SEARCH_ISSUE, + variables: { + query: { + volumeName: "Spider-Man", + }, + type: "volumeName", + pagination: { + size: 20, + from: 0, + }, + }, +}; + +/** + * Example: Search wanted comics + */ +export const exampleSearchWanted = { + query: SEARCH_ISSUE, + variables: { + type: "wanted", + pagination: { + size: 50, + from: 0, + }, + }, +}; + +/** + * Example: Search volumes + */ +export const exampleSearchVolumes = { + query: SEARCH_ISSUE, + variables: { + type: "volumes", + pagination: { + size: 10, + from: 0, + }, + }, +}; diff --git a/examples/frontend/services/GraphQLApi.ts b/examples/frontend/services/GraphQLApi.ts new file mode 100644 index 0000000..561b7a5 --- /dev/null +++ b/examples/frontend/services/GraphQLApi.ts @@ -0,0 +1,165 @@ +/** + * GraphQL API Client Utility + * + * This file should be placed in the frontend project at: + * src/client/services/api/GraphQLApi.ts + * + * Simple wrapper around axios for executing GraphQL queries and mutations + * No additional dependencies needed (no Apollo Client) + * Works seamlessly with React Query + */ + +import axios from 'axios'; + +// Update this to match your frontend constants file +// import { LIBRARY_SERVICE_BASE_URI } from '../../constants/endpoints'; +const LIBRARY_SERVICE_BASE_URI = process.env.REACT_APP_LIBRARY_SERVICE_BASE_URI || 'http://localhost:3000/api/library'; + +/** + * Execute a GraphQL query against the threetwo-core-service GraphQL endpoint + * + * @param query - GraphQL query string + * @param variables - Query variables + * @returns Promise with query result data + * + * @example + * ```typescript + * const result = await executeGraphQLQuery( + * GET_COMIC_DETAIL_QUERY, + * { id: 'comic-id-123' } + * ); + * console.log(result.comic.rawFileDetails.name); + * ``` + */ +export const executeGraphQLQuery = async ( + query: string, + variables?: Record +): Promise => { + try { + const response = await axios.post( + `${LIBRARY_SERVICE_BASE_URI}/graphql`, + { + query, + variables, + }, + { + headers: { + 'Content-Type': 'application/json', + }, + } + ); + + // GraphQL can return partial data with errors + if (response.data.errors) { + console.error('GraphQL errors:', response.data.errors); + throw new Error( + `GraphQL errors: ${response.data.errors.map((e: any) => e.message).join(', ')}` + ); + } + + return response.data.data; + } catch (error) { + console.error('GraphQL query failed:', error); + throw error; + } +}; + +/** + * Execute a GraphQL mutation against the threetwo-core-service GraphQL endpoint + * + * @param mutation - GraphQL mutation string + * @param variables - Mutation variables + * @returns Promise with mutation result data + * + * @example + * ```typescript + * const result = await executeGraphQLMutation<{ setMetadataField: Comic }>( + * SET_METADATA_FIELD_MUTATION, + * { comicId: '123', field: 'title', value: 'New Title' } + * ); + * console.log(result.setMetadataField.canonicalMetadata.title); + * ``` + */ +export const executeGraphQLMutation = async ( + mutation: string, + variables?: Record +): Promise => { + // Mutations use the same endpoint as queries + return executeGraphQLQuery(mutation, variables); +}; + +/** + * Helper function to parse JSON strings from sourcedMetadata + * GraphQL returns these fields as JSON strings that need parsing + * + * @param sourcedMetadata - The sourcedMetadata object from GraphQL response + * @returns Parsed sourcedMetadata with JSON fields converted to objects + * + * @example + * ```typescript + * const comic = result.comic; + * comic.sourcedMetadata = parseSourcedMetadata(comic.sourcedMetadata); + * // Now comic.sourcedMetadata.comicInfo is an object, not a string + * ``` + */ +export const parseSourcedMetadata = (sourcedMetadata: any) => { + if (!sourcedMetadata) return sourcedMetadata; + + const parsed = { ...sourcedMetadata }; + + // Parse JSON strings + if (parsed.comicInfo && typeof parsed.comicInfo === 'string') { + try { + parsed.comicInfo = JSON.parse(parsed.comicInfo); + } catch (e) { + console.warn('Failed to parse comicInfo:', e); + parsed.comicInfo = {}; + } + } + + if (parsed.comicvine && typeof parsed.comicvine === 'string') { + try { + parsed.comicvine = JSON.parse(parsed.comicvine); + } catch (e) { + console.warn('Failed to parse comicvine:', e); + parsed.comicvine = {}; + } + } + + if (parsed.metron && typeof parsed.metron === 'string') { + try { + parsed.metron = JSON.parse(parsed.metron); + } catch (e) { + console.warn('Failed to parse metron:', e); + parsed.metron = {}; + } + } + + if (parsed.gcd && typeof parsed.gcd === 'string') { + try { + parsed.gcd = JSON.parse(parsed.gcd); + } catch (e) { + console.warn('Failed to parse gcd:', e); + parsed.gcd = {}; + } + } + + return parsed; +}; + +/** + * Helper function to transform GraphQL comic response to REST format + * Ensures backward compatibility with existing components + * + * @param comic - Comic object from GraphQL response + * @returns Comic object in REST format with _id field + */ +export const transformComicToRestFormat = (comic: any) => { + if (!comic) return null; + + return { + _id: comic.id, + ...comic, + sourcedMetadata: parseSourcedMetadata(comic.sourcedMetadata), + }; +}; diff --git a/examples/test-graphql-endpoint.sh b/examples/test-graphql-endpoint.sh new file mode 100755 index 0000000..958b753 --- /dev/null +++ b/examples/test-graphql-endpoint.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +# Test GraphQL Endpoint Script +# This script tests the GraphQL endpoint with various queries + +GRAPHQL_URL="http://localhost:3000/graphql" + +echo "๐Ÿงช Testing GraphQL Endpoint: $GRAPHQL_URL" +echo "================================================" +echo "" + +# Test 1: List Comics +echo "๐Ÿ“š Test 1: List Comics (first 5)" +echo "--------------------------------" +curl -s -X POST $GRAPHQL_URL \ + -H "Content-Type: application/json" \ + -d '{ + "query": "query { comics(limit: 5) { comics { id rawFileDetails { name pageCount } } totalCount } }" + }' | jq '.' +echo "" +echo "" + +# Test 2: Get Single Comic (you need to replace COMIC_ID) +echo "๐Ÿ“– Test 2: Get Single Comic" +echo "--------------------------------" +echo "โš ๏ธ Replace COMIC_ID with an actual comic ID from your database" +read -p "Enter Comic ID (or press Enter to skip): " COMIC_ID + +if [ ! -z "$COMIC_ID" ]; then + curl -s -X POST $GRAPHQL_URL \ + -H "Content-Type: application/json" \ + -d "{ + \"query\": \"query GetComic(\$id: ID!) { comic(id: \$id) { id rawFileDetails { name filePath fileSize pageCount } sourcedMetadata { locg { name publisher rating } } } }\", + \"variables\": { \"id\": \"$COMIC_ID\" } + }" | jq '.' +else + echo "Skipped" +fi +echo "" +echo "" + +# Test 3: Get User Preferences +echo "โš™๏ธ Test 3: Get User Preferences" +echo "--------------------------------" +curl -s -X POST $GRAPHQL_URL \ + -H "Content-Type: application/json" \ + -d '{ + "query": "query { userPreferences(userId: \"default\") { id userId conflictResolution minConfidenceThreshold autoMerge { enabled onImport onMetadataUpdate } } }" + }' | jq '.' +echo "" +echo "" + +# Test 4: Search Comics +echo "๐Ÿ” Test 4: Search Comics" +echo "--------------------------------" +read -p "Enter search term (or press Enter to skip): " SEARCH_TERM + +if [ ! -z "$SEARCH_TERM" ]; then + curl -s -X POST $GRAPHQL_URL \ + -H "Content-Type: application/json" \ + -d "{ + \"query\": \"query SearchComics(\$search: String) { comics(search: \$search, limit: 10) { comics { id rawFileDetails { name } } totalCount } }\", + \"variables\": { \"search\": \"$SEARCH_TERM\" } + }" | jq '.' +else + echo "Skipped" +fi +echo "" +echo "" + +# Test 5: GraphQL Introspection (get schema info) +echo "๐Ÿ”ฌ Test 5: Introspection - Available Queries" +echo "--------------------------------" +curl -s -X POST $GRAPHQL_URL \ + -H "Content-Type: application/json" \ + -d '{ + "query": "{ __schema { queryType { fields { name description } } } }" + }' | jq '.data.__schema.queryType.fields[] | {name, description}' +echo "" +echo "" + +echo "โœ… GraphQL endpoint tests complete!" +echo "" +echo "๐Ÿ’ก Tips:" +echo " - Open http://localhost:3000/graphql in your browser for GraphQL Playground" +echo " - Use 'jq' for better JSON formatting (install with: apt-get install jq)" +echo " - Check the docs at: docs/FRONTEND_GRAPHQL_INTEGRATION.md" diff --git a/models/graphql/resolvers.ts b/models/graphql/resolvers.ts index bba22ac..63befa7 100644 --- a/models/graphql/resolvers.ts +++ b/models/graphql/resolvers.ts @@ -27,6 +27,202 @@ export const resolvers = { } }, + /** + * Get comic books with advanced pagination and filtering + */ + getComicBooks: async ( + _: any, + { + paginationOptions, + predicate = {}, + }: { + paginationOptions: any; + predicate?: any; + } + ) => { + try { + const result = await Comic.paginate(predicate, paginationOptions); + return result; + } catch (error) { + console.error("Error fetching comic books:", error); + throw new Error("Failed to fetch comic books"); + } + }, + + /** + * Get comic book groups (volumes with multiple issues) + */ + getComicBookGroups: async () => { + try { + const volumes = await Comic.aggregate([ + { + $project: { + volumeInfo: + "$sourcedMetadata.comicvine.volumeInformation", + }, + }, + { + $unwind: "$volumeInfo", + }, + { + $group: { + _id: "$_id", + volumes: { + $addToSet: "$volumeInfo", + }, + }, + }, + { + $unwind: "$volumes", + }, + { $sort: { updatedAt: -1 } }, + { $skip: 0 }, + { $limit: 5 }, + ]); + + return volumes.map((vol) => ({ + id: vol._id.toString(), + volumes: vol.volumes, + })); + } catch (error) { + console.error("Error fetching comic book groups:", error); + throw new Error("Failed to fetch comic book groups"); + } + }, + + /** + * Get library statistics + */ + getLibraryStatistics: async () => { + try { + const { getSizeOfDirectory } = require("../../utils/file.utils"); + const { COMICS_DIRECTORY } = require("../../constants/directories"); + + const comicDirectorySize = await getSizeOfDirectory( + COMICS_DIRECTORY, + [".cbz", ".cbr", ".cb7"] + ); + const totalCount = await Comic.countDocuments({}); + const statistics = await Comic.aggregate([ + { + $facet: { + fileTypes: [ + { + $match: { + "rawFileDetails.extension": { + $in: [".cbr", ".cbz", ".cb7"], + }, + }, + }, + { + $group: { + _id: "$rawFileDetails.extension", + data: { $push: "$$ROOT._id" }, + }, + }, + ], + issues: [ + { + $match: { + "sourcedMetadata.comicvine.volumeInformation": + { + $gt: {}, + }, + }, + }, + { + $group: { + _id: "$sourcedMetadata.comicvine.volumeInformation", + data: { $push: "$$ROOT._id" }, + }, + }, + ], + fileLessComics: [ + { + $match: { + rawFileDetails: { + $exists: false, + }, + }, + }, + ], + issuesWithComicInfoXML: [ + { + $match: { + "sourcedMetadata.comicInfo": { + $exists: true, + $gt: { $size: 0 }, + }, + }, + }, + ], + publisherWithMostComicsInLibrary: [ + { + $unwind: + "$sourcedMetadata.comicvine.volumeInformation.publisher", + }, + { + $group: { + _id: "$sourcedMetadata.comicvine.volumeInformation.publisher.name", + count: { $sum: 1 }, + }, + }, + { $sort: { count: -1 } }, + { $limit: 1 }, + ], + }, + }, + ]); + + return { + totalDocuments: totalCount, + comicDirectorySize, + statistics, + }; + } catch (error) { + console.error("Error fetching library statistics:", error); + throw new Error("Failed to fetch library statistics"); + } + }, + + /** + * Search issues using Elasticsearch + */ + searchIssue: async ( + _: any, + { + query, + pagination = { size: 10, from: 0 }, + type, + }: { + query?: { volumeName?: string; issueNumber?: string }; + pagination?: { size?: number; from?: number }; + type: string; + }, + context: any + ) => { + try { + // Get broker from context (set up in GraphQL service) + const broker = context?.broker; + + if (!broker) { + throw new Error("Broker not available in context"); + } + + // Call the search service through the broker + const result = await broker.call("search.issue", { + query: query || {}, + pagination, + type, + }); + + return result; + } catch (error) { + console.error("Error searching issues:", error); + throw new Error(`Failed to search issues: ${error.message}`); + } + }, + /** * List comics with pagination and filtering */ diff --git a/models/graphql/typedef.ts b/models/graphql/typedef.ts index b6a2bfc..8dbd774 100644 --- a/models/graphql/typedef.ts +++ b/models/graphql/typedef.ts @@ -239,6 +239,25 @@ export const typeDefs = gql` series: String ): ComicConnection! + # Get comic books with advanced pagination and filtering + getComicBooks( + paginationOptions: PaginationOptionsInput! + predicate: PredicateInput + ): ComicBooksResult! + + # Get comic book groups (volumes with multiple issues) + getComicBookGroups: [ComicBookGroup!]! + + # Get library statistics + getLibraryStatistics: LibraryStatistics! + + # Search issues using Elasticsearch + searchIssue( + query: SearchIssueQueryInput + pagination: SearchPaginationInput + type: SearchType! + ): SearchIssueResult! + # Get user preferences userPreferences(userId: String = "default"): UserPreferences @@ -439,4 +458,162 @@ export const typeDefs = gql` message: String canonicalMetadataResolved: Boolean! } + + # Pagination options input + input PaginationOptionsInput { + page: Int + limit: Int + sort: String + lean: Boolean + leanWithId: Boolean + offset: Int + pagination: Boolean + } + + # Predicate input for filtering + # Note: This is a placeholder. In practice, predicates are passed as JSON objects + # and handled dynamically in the resolver + scalar PredicateInput + + # Comic books result with pagination + type ComicBooksResult { + docs: [Comic!]! + totalDocs: Int! + limit: Int! + page: Int + totalPages: Int! + hasNextPage: Boolean! + hasPrevPage: Boolean! + nextPage: Int + prevPage: Int + pagingCounter: Int! + } + + # Comic book group (volume with issues) + type ComicBookGroup { + id: ID! + volumes: VolumeInfo + } + + # Volume information + type VolumeInfo { + id: Int + name: String + count_of_issues: Int + publisher: Publisher + start_year: String + image: VolumeImage + description: String + site_detail_url: String + } + + # Publisher information + type Publisher { + id: Int + name: String + api_detail_url: String + } + + # Volume image + type VolumeImage { + 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 + } + + # Library statistics + type LibraryStatistics { + totalDocuments: Int! + comicDirectorySize: DirectorySize! + statistics: [StatisticsFacet!]! + } + + # Directory size information + type DirectorySize { + totalSize: Float! + totalSizeInMB: Float! + totalSizeInGB: Float! + fileCount: Int! + } + + # Statistics facet + type StatisticsFacet { + fileTypes: [FileTypeStats!] + issues: [IssueStats!] + fileLessComics: [Comic!] + issuesWithComicInfoXML: [Comic!] + publisherWithMostComicsInLibrary: [PublisherStats!] + } + + # File type statistics + type FileTypeStats { + id: String! + data: [ID!]! + } + + # Issue statistics + type IssueStats { + id: VolumeInfo + data: [ID!]! + } + + # Publisher statistics + type PublisherStats { + id: String! + count: Int! + } + # Search issue query input + input SearchIssueQueryInput { + volumeName: String + issueNumber: String + } + + # Search pagination input + input SearchPaginationInput { + size: Int + from: Int + } + + # Search type enum + enum SearchType { + all + volumeName + wanted + volumes + } + + # Search issue result + type SearchIssueResult { + hits: SearchHits! + took: Int + timed_out: Boolean + } + + # Search hits + type SearchHits { + total: SearchTotal! + max_score: Float + hits: [SearchHit!]! + } + + # Search total + type SearchTotal { + value: Int! + relation: String! + } + + # Search hit + type SearchHit { + _index: String! + _id: String! + _score: Float + _source: Comic! + } `; diff --git a/package-lock.json b/package-lock.json index e158364..1eba8ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,11 +39,12 @@ "leven": "^3.1.0", "lodash": "^4.17.21", "mkdirp": "^0.5.5", + "moleculer-apollo-server": "^0.4.0", "moleculer-bullmq": "^3.0.0", "moleculer-db": "^0.8.23", "moleculer-db-adapter-mongoose": "^0.9.2", "moleculer-io": "^2.2.0", - "moleculer-web": "^0.10.8", + "moleculer-web": "^0.10.5", "mongoosastic-ts": "^6.0.3", "mongoose": "^6.10.4", "mongoose-paginate-v2": "^1.3.18", @@ -4265,6 +4266,18 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@whatwg-node/promise-helpers": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@whatwg-node/promise-helpers/-/promise-helpers-1.3.2.tgz", + "integrity": "sha512-Nst5JdK47VIl9UcGwtv2Rcgyn5lWtZ0/mhRQ4G8NN2isxpq2TO30iqHzmwoJycjWuyUfg3GFXqP/gFHXeV57IA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.6.3" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@xmldom/xmldom": { "version": "0.9.8", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.8.tgz", @@ -5718,6 +5731,18 @@ "yup": "0.32.9" } }, + "node_modules/cross-inspect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cross-inspect/-/cross-inspect-1.0.1.tgz", + "integrity": "sha512-Pcw1JTvZLSJH83iiGWt6fRcT+BjZlCDRVwYLbUcHzv/CRpB7r0MlSrGbIyQvVSNyGnbt7G4AXuyCiDR3POvZ1A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -5782,6 +5807,12 @@ "node": ">=14" } }, + "node_modules/dataloader": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/dataloader/-/dataloader-2.2.3.tgz", + "integrity": "sha512-y2krtASINtPFS1rSDjacrFgn1dcUuoREVabwlOGOe4SdxenREqwjwjElAdwvbGM7kgZz9a3KVicWR7vcz8rnzA==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", @@ -8052,6 +8083,15 @@ "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0" } }, + "node_modules/graphql-subscriptions": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/graphql-subscriptions/-/graphql-subscriptions-3.0.0.tgz", + "integrity": "sha512-kZCdevgmzDjGAOqH7GlDmQXYAkuHoKpMlJrqF40HMPhUhM5ZWSFSxCwD/nSi6AkaijmMfsFhoJRGJ27UseCvRA==", + "license": "MIT", + "peerDependencies": { + "graphql": "^15.7.2 || ^16.0.0" + } + }, "node_modules/graphql-tag": { "version": "2.12.6", "resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.12.6.tgz", @@ -8067,6 +8107,32 @@ "graphql": "^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0" } }, + "node_modules/graphql-ws": { + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/graphql-ws/-/graphql-ws-6.0.7.tgz", + "integrity": "sha512-yoLRW+KRlDmnnROdAu7sX77VNLC0bsFoZyGQJLy1cF+X/SkLg/fWkRGrEEYQK8o2cafJ2wmEaMqMEZB3U3DYDg==", + "license": "MIT", + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "@fastify/websocket": "^10 || ^11", + "crossws": "~0.3", + "graphql": "^15.10.1 || ^16", + "ws": "^8" + }, + "peerDependenciesMeta": { + "@fastify/websocket": { + "optional": true + }, + "crossws": { + "optional": true + }, + "ws": { + "optional": true + } + } + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -10946,6 +11012,438 @@ } } }, + "node_modules/moleculer-apollo-server": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/moleculer-apollo-server/-/moleculer-apollo-server-0.4.0.tgz", + "integrity": "sha512-uHI8XgYyczOMAhS4OdGgq/5jO+JmqRsuJPkcQqXRMXGJk1WjAy35kHkRsJBImFGfLlR4YKlsBVAPqEIs22QAFg==", + "license": "MIT", + "dependencies": { + "@apollo/server": "^5.0.0", + "@graphql-tools/schema": "^10.0.25", + "dataloader": "^2.2.3", + "graphql-subscriptions": "^3.0.0", + "graphql-ws": "^6.0.6", + "lodash": "^4.17.21", + "object-hash": "^3.0.0", + "ws": "^8.18.3" + }, + "engines": { + "node": ">= 20.x.x" + }, + "peerDependencies": { + "graphql": "^16.0.0", + "moleculer": "^0.14.0 || ^0.15.0-0" + } + }, + "node_modules/moleculer-apollo-server/node_modules/@apollo/server": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@apollo/server/-/server-5.4.0.tgz", + "integrity": "sha512-E0/2C5Rqp7bWCjaDh4NzYuEPDZ+dltTf2c0FI6GCKJA6GBetVferX3h1//1rS4+NxD36wrJsGGJK+xyT/M3ysg==", + "license": "MIT", + "dependencies": { + "@apollo/cache-control-types": "^1.0.3", + "@apollo/server-gateway-interface": "^2.0.0", + "@apollo/usage-reporting-protobuf": "^4.1.1", + "@apollo/utils.createhash": "^3.0.0", + "@apollo/utils.fetcher": "^3.0.0", + "@apollo/utils.isnodelike": "^3.0.0", + "@apollo/utils.keyvaluecache": "^4.0.0", + "@apollo/utils.logger": "^3.0.0", + "@apollo/utils.usagereporting": "^2.1.0", + "@apollo/utils.withrequired": "^3.0.0", + "@graphql-tools/schema": "^10.0.0", + "async-retry": "^1.2.1", + "body-parser": "^2.2.2", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "finalhandler": "^2.1.0", + "loglevel": "^1.6.8", + "lru-cache": "^11.1.0", + "negotiator": "^1.0.0", + "uuid": "^11.1.0", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "graphql": "^16.11.0" + } + }, + "node_modules/moleculer-apollo-server/node_modules/@apollo/server-gateway-interface": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@apollo/server-gateway-interface/-/server-gateway-interface-2.0.0.tgz", + "integrity": "sha512-3HEMD6fSantG2My3jWkb9dvfkF9vJ4BDLRjMgsnD790VINtuPaEp+h3Hg9HOHiWkML6QsOhnaRqZ+gvhp3y8Nw==", + "license": "MIT", + "dependencies": { + "@apollo/usage-reporting-protobuf": "^4.1.1", + "@apollo/utils.fetcher": "^3.0.0", + "@apollo/utils.keyvaluecache": "^4.0.0", + "@apollo/utils.logger": "^3.0.0" + }, + "peerDependencies": { + "graphql": "14.x || 15.x || 16.x" + } + }, + "node_modules/moleculer-apollo-server/node_modules/@apollo/utils.createhash": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@apollo/utils.createhash/-/utils.createhash-3.0.1.tgz", + "integrity": "sha512-CKrlySj4eQYftBE5MJ8IzKwIibQnftDT7yGfsJy5KSEEnLlPASX0UTpbKqkjlVEwPPd4mEwI7WOM7XNxEuO05A==", + "license": "MIT", + "dependencies": { + "@apollo/utils.isnodelike": "^3.0.0", + "sha.js": "^2.4.11" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/moleculer-apollo-server/node_modules/@apollo/utils.fetcher": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.fetcher/-/utils.fetcher-3.1.0.tgz", + "integrity": "sha512-Z3QAyrsQkvrdTuHAFwWDNd+0l50guwoQUoaDQssLOjkmnmVuvXlJykqlEJolio+4rFwBnWdoY1ByFdKaQEcm7A==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/moleculer-apollo-server/node_modules/@apollo/utils.isnodelike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.isnodelike/-/utils.isnodelike-3.0.0.tgz", + "integrity": "sha512-xrjyjfkzunZ0DeF6xkHaK5IKR8F1FBq6qV+uZ+h9worIF/2YSzA0uoBxGv6tbTeo9QoIQnRW4PVFzGix5E7n/g==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/moleculer-apollo-server/node_modules/@apollo/utils.keyvaluecache": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.keyvaluecache/-/utils.keyvaluecache-4.0.0.tgz", + "integrity": "sha512-mKw1myRUkQsGPNB+9bglAuhviodJ2L2MRYLTafCMw5BIo7nbvCPNCkLnIHjZ1NOzH7SnMAr5c9LmXiqsgYqLZw==", + "license": "MIT", + "dependencies": { + "@apollo/utils.logger": "^3.0.0", + "lru-cache": "^11.0.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/moleculer-apollo-server/node_modules/@apollo/utils.logger": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.logger/-/utils.logger-3.0.0.tgz", + "integrity": "sha512-M8V8JOTH0F2qEi+ktPfw4RL7MvUycDfKp7aEap2eWXfL5SqWHN6jTLbj5f5fj1cceHpyaUSOZlvlaaryaxZAmg==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/moleculer-apollo-server/node_modules/@apollo/utils.withrequired": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@apollo/utils.withrequired/-/utils.withrequired-3.0.0.tgz", + "integrity": "sha512-aaxeavfJ+RHboh7c2ofO5HHtQobGX4AgUujXP4CXpREHp9fQ9jPi6K9T1jrAKe7HIipoP0OJ1gd6JamSkFIpvA==", + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/moleculer-apollo-server/node_modules/@graphql-tools/merge": { + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/@graphql-tools/merge/-/merge-9.1.7.tgz", + "integrity": "sha512-Y5E1vTbTabvcXbkakdFUt4zUIzB1fyaEnVmIWN0l0GMed2gdD01TpZWLUm4RNAxpturvolrb24oGLQrBbPLSoQ==", + "license": "MIT", + "dependencies": { + "@graphql-tools/utils": "^11.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/moleculer-apollo-server/node_modules/@graphql-tools/schema": { + "version": "10.0.31", + "resolved": "https://registry.npmjs.org/@graphql-tools/schema/-/schema-10.0.31.tgz", + "integrity": "sha512-ZewRgWhXef6weZ0WiP7/MV47HXiuFbFpiDUVLQl6mgXsWSsGELKFxQsyUCBos60Qqy1JEFAIu3Ns6GGYjGkqkQ==", + "license": "MIT", + "dependencies": { + "@graphql-tools/merge": "^9.1.7", + "@graphql-tools/utils": "^11.0.0", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/moleculer-apollo-server/node_modules/@graphql-tools/utils": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@graphql-tools/utils/-/utils-11.0.0.tgz", + "integrity": "sha512-bM1HeZdXA2C3LSIeLOnH/bcqSgbQgKEDrjxODjqi3y58xai2TkNrtYcQSoWzGbt9VMN1dORGjR7Vem8SPnUFQA==", + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.1.1", + "@whatwg-node/promise-helpers": "^1.0.0", + "cross-inspect": "1.0.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0" + } + }, + "node_modules/moleculer-apollo-server/node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/moleculer-apollo-server/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/moleculer-apollo-server/node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/moleculer-apollo-server/node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/moleculer-apollo-server/node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/moleculer-apollo-server/node_modules/lru-cache": { + "version": "11.2.6", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz", + "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/moleculer-apollo-server/node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/moleculer-apollo-server/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/moleculer-apollo-server/node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/moleculer-apollo-server/node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/moleculer-apollo-server/node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/moleculer-apollo-server/node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/moleculer-apollo-server/node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/moleculer-apollo-server/node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/moleculer-apollo-server/node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/moleculer-apollo-server/node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/moleculer-apollo-server/node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/moleculer-bullmq": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/moleculer-bullmq/-/moleculer-bullmq-3.0.0.tgz", @@ -14084,10 +14582,20 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -15787,14 +16295,69 @@ } }, "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" diff --git a/package.json b/package.json index 01c7585..830c844 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ }, "dependencies": { "@apollo/server": "^4.12.2", + "moleculer-apollo-server": "^0.4.0", "@bluelovers/fast-glob": "https://github.com/rishighan/fast-glob-v2-api.git", "@elastic/elasticsearch": "^8.13.1", "@jorgeferrero/stream-to-buffer": "^2.0.6",