214 lines
5.0 KiB
TypeScript
214 lines
5.0 KiB
TypeScript
import { Service, ServiceBroker } from "moleculer";
|
|
import { ApolloServer } from "@apollo/server";
|
|
import { typeDefs } from "../models/graphql/typedef";
|
|
import { resolvers } from "../models/graphql/resolvers";
|
|
|
|
/**
|
|
* GraphQL Service
|
|
* Provides a GraphQL API for canonical metadata queries and mutations
|
|
* Integrates Apollo Server with Moleculer
|
|
*/
|
|
export default class GraphQLService extends Service {
|
|
private apolloServer?: ApolloServer;
|
|
|
|
public constructor(broker: ServiceBroker) {
|
|
super(broker);
|
|
|
|
this.parseServiceSchema({
|
|
name: "graphql",
|
|
|
|
settings: {
|
|
// GraphQL endpoint path
|
|
path: "/graphql",
|
|
},
|
|
|
|
actions: {
|
|
/**
|
|
* Execute a GraphQL query
|
|
*/
|
|
query: {
|
|
params: {
|
|
query: "string",
|
|
variables: { type: "object", optional: true },
|
|
operationName: { type: "string", optional: true },
|
|
},
|
|
async handler(ctx: any) {
|
|
try {
|
|
if (!this.apolloServer) {
|
|
throw new Error("Apollo Server not initialized");
|
|
}
|
|
|
|
const { query, variables, operationName } = ctx.params;
|
|
|
|
const response = await this.apolloServer.executeOperation(
|
|
{
|
|
query,
|
|
variables,
|
|
operationName,
|
|
},
|
|
{
|
|
contextValue: {
|
|
broker: this.broker,
|
|
ctx,
|
|
},
|
|
}
|
|
);
|
|
|
|
if (response.body.kind === "single") {
|
|
return response.body.singleResult;
|
|
}
|
|
|
|
return response;
|
|
} catch (error) {
|
|
this.logger.error("GraphQL query error:", error);
|
|
throw error;
|
|
}
|
|
},
|
|
},
|
|
|
|
/**
|
|
* Get GraphQL schema
|
|
*/
|
|
getSchema: {
|
|
async handler() {
|
|
return {
|
|
typeDefs: typeDefs.loc?.source.body || "",
|
|
};
|
|
},
|
|
},
|
|
},
|
|
|
|
methods: {
|
|
/**
|
|
* Initialize Apollo Server
|
|
*/
|
|
async initApolloServer() {
|
|
this.logger.info("Initializing Apollo Server...");
|
|
|
|
this.apolloServer = new ApolloServer({
|
|
typeDefs,
|
|
resolvers,
|
|
introspection: true, // Enable GraphQL Playground in development
|
|
formatError: (error) => {
|
|
this.logger.error("GraphQL Error:", error);
|
|
return {
|
|
message: error.message,
|
|
locations: error.locations,
|
|
path: error.path,
|
|
extensions: {
|
|
code: error.extensions?.code,
|
|
},
|
|
};
|
|
},
|
|
});
|
|
|
|
await this.apolloServer.start();
|
|
this.logger.info("Apollo Server started successfully");
|
|
},
|
|
|
|
/**
|
|
* Stop Apollo Server
|
|
*/
|
|
async stopApolloServer() {
|
|
if (this.apolloServer) {
|
|
this.logger.info("Stopping Apollo Server...");
|
|
await this.apolloServer.stop();
|
|
this.apolloServer = undefined;
|
|
this.logger.info("Apollo Server stopped");
|
|
}
|
|
},
|
|
},
|
|
|
|
events: {
|
|
/**
|
|
* Trigger metadata resolution when new metadata is imported
|
|
*/
|
|
"metadata.imported": {
|
|
async handler(ctx: any) {
|
|
const { comicId, source } = ctx.params;
|
|
this.logger.info(
|
|
`Metadata imported for comic ${comicId} from ${source}`
|
|
);
|
|
|
|
// Optionally trigger auto-resolution if enabled
|
|
try {
|
|
const UserPreferences = require("../models/userpreferences.model").default;
|
|
const preferences = await UserPreferences.findOne({
|
|
userId: "default",
|
|
});
|
|
|
|
if (
|
|
preferences?.autoMerge?.enabled &&
|
|
preferences?.autoMerge?.onMetadataUpdate
|
|
) {
|
|
this.logger.info(
|
|
`Auto-resolving metadata for comic ${comicId}`
|
|
);
|
|
await this.broker.call("graphql.query", {
|
|
query: `
|
|
mutation ResolveMetadata($comicId: ID!) {
|
|
resolveMetadata(comicId: $comicId) {
|
|
id
|
|
}
|
|
}
|
|
`,
|
|
variables: { comicId },
|
|
});
|
|
}
|
|
} catch (error) {
|
|
this.logger.error("Error in auto-resolution:", error);
|
|
}
|
|
},
|
|
},
|
|
|
|
/**
|
|
* Trigger metadata resolution when comic is imported
|
|
*/
|
|
"comic.imported": {
|
|
async handler(ctx: any) {
|
|
const { comicId } = ctx.params;
|
|
this.logger.info(`Comic imported: ${comicId}`);
|
|
|
|
// Optionally trigger auto-resolution if enabled
|
|
try {
|
|
const UserPreferences = require("../models/userpreferences.model").default;
|
|
const preferences = await UserPreferences.findOne({
|
|
userId: "default",
|
|
});
|
|
|
|
if (
|
|
preferences?.autoMerge?.enabled &&
|
|
preferences?.autoMerge?.onImport
|
|
) {
|
|
this.logger.info(
|
|
`Auto-resolving metadata for newly imported comic ${comicId}`
|
|
);
|
|
await this.broker.call("graphql.query", {
|
|
query: `
|
|
mutation ResolveMetadata($comicId: ID!) {
|
|
resolveMetadata(comicId: $comicId) {
|
|
id
|
|
}
|
|
}
|
|
`,
|
|
variables: { comicId },
|
|
});
|
|
}
|
|
} catch (error) {
|
|
this.logger.error("Error in auto-resolution on import:", error);
|
|
}
|
|
},
|
|
},
|
|
},
|
|
|
|
started: async function (this: any) {
|
|
await this.initApolloServer();
|
|
},
|
|
|
|
stopped: async function (this: any) {
|
|
await this.stopApolloServer();
|
|
},
|
|
});
|
|
}
|
|
}
|