⬇ Import flow fixes

This commit is contained in:
2026-03-05 13:33:48 -05:00
parent cc30dcc14f
commit 71267ecc7e
5 changed files with 124 additions and 13 deletions

View File

@@ -622,6 +622,58 @@ export const resolvers = {
}
},
/**
* Get import statistics for a directory
* @async
* @function getImportStatistics
* @param {any} _ - Parent resolver (unused)
* @param {Object} args - Query arguments
* @param {string} [args.directoryPath] - Optional directory path to analyze
* @param {Object} context - GraphQL context with broker
* @returns {Promise<Object>} Import statistics including total files, imported count, and new files
* @throws {Error} If statistics calculation fails
* @description Analyzes a directory (or default COMICS_DIRECTORY) and compares
* files against the database to determine import status. This performs a full
* filesystem scan and is slower than getCachedImportStatistics.
*
* @example
* ```graphql
* query {
* getImportStatistics(directoryPath: "/path/to/comics") {
* success
* directory
* stats {
* totalLocalFiles
* alreadyImported
* newFiles
* percentageImported
* }
* }
* }
* ```
*/
getImportStatistics: async (
_: any,
{ directoryPath }: { directoryPath?: string },
context: any
) => {
try {
const broker = context?.broker;
if (!broker) {
throw new Error("Broker not available in context");
}
const result = await broker.call("library.getImportStatistics", {
directoryPath,
});
return result;
} catch (error) {
console.error("Error fetching import statistics:", error);
throw new Error(`Failed to fetch import statistics: ${error.message}`);
}
},
/**
* Get cached import statistics (fast, real-time)
* @async

View File

@@ -92,6 +92,7 @@ export default class ApiService extends Service {
aliases: {
"POST /": "graphql.graphql",
"GET /": "graphql.graphql",
"GET /health": "graphql.checkRemoteSchema",
},
mappingPolicy: "restrict",
bodyParsers: {

View File

@@ -114,6 +114,36 @@ export default {
},
actions: {
/**
* Check remote schema health and availability
* @returns Status of remote schema connection with appropriate HTTP status
*/
checkRemoteSchema: {
async handler(ctx: Context<any>) {
const status: any = {
remoteSchemaAvailable: this.remoteSchemaAvailable || false,
remoteUrl: this.settings.metadataGraphqlUrl,
localSchemaOnly: !this.remoteSchemaAvailable,
};
if (this.remoteSchemaAvailable && this.schema) {
const queryType = this.schema.getQueryType();
if (queryType) {
const fields = Object.keys(queryType.getFields());
status.availableQueryFields = fields;
status.hasWeeklyPullList = fields.includes('getWeeklyPullList');
}
}
// Set HTTP status code based on schema stitching status
// 200 = Schema stitching complete (remote available)
// 503 = Service degraded (local only, remote unavailable)
(ctx.meta as any).$statusCode = this.remoteSchemaAvailable ? 200 : 503;
return status;
},
},
/**
* Execute GraphQL queries and mutations
* @param query - GraphQL query or mutation string
@@ -196,11 +226,19 @@ export default {
this.logger.info(`Attempting to introspect remote schema at ${this.settings.metadataGraphqlUrl}`);
const remoteSchema = await fetchRemoteSchema(this.settings.metadataGraphqlUrl);
this.logger.info("Successfully introspected remote metadata schema");
this.logger.info("Successfully introspected remote metadata schema");
const remoteQueryType = remoteSchema.getQueryType();
const remoteMutationType = remoteSchema.getMutationType();
if (remoteQueryType) {
this.logger.info(`Remote schema Query fields: ${Object.keys(remoteQueryType.getFields()).join(', ')}`);
const remoteQueryFields = Object.keys(remoteQueryType.getFields());
this.logger.info(`✓ Remote schema has ${remoteQueryFields.length} Query fields: ${remoteQueryFields.join(', ')}`);
}
if (remoteMutationType) {
const remoteMutationFields = Object.keys(remoteMutationType.getFields());
this.logger.info(`✓ Remote schema has ${remoteMutationFields.length} Mutation fields: ${remoteMutationFields.join(', ')}`);
}
this.schema = stitchSchemas({
@@ -212,15 +250,35 @@ export default {
});
const stitchedQueryType = this.schema.getQueryType();
const stitchedMutationType = this.schema.getMutationType();
if (stitchedQueryType) {
this.logger.info(`Stitched schema Query fields: ${Object.keys(stitchedQueryType.getFields()).join(', ')}`);
const stitchedQueryFields = Object.keys(stitchedQueryType.getFields());
this.logger.info(`✓ Stitched schema has ${stitchedQueryFields.length} Query fields`);
// Verify critical remote fields are present
const criticalFields = ['getWeeklyPullList'];
const missingFields = criticalFields.filter(field => !stitchedQueryFields.includes(field));
if (missingFields.length > 0) {
this.logger.warn(`⚠ Missing expected remote fields: ${missingFields.join(', ')}`);
}
}
this.logger.info("Successfully stitched local and remote schemas");
if (stitchedMutationType) {
const stitchedMutationFields = Object.keys(stitchedMutationType.getFields());
this.logger.info(`✓ Stitched schema has ${stitchedMutationFields.length} Mutation fields`);
}
this.logger.info("✓ Successfully stitched local and remote schemas");
this.remoteSchemaAvailable = true;
} catch (remoteError: any) {
this.logger.warn(`Could not connect to remote metadata GraphQL at ${this.settings.metadataGraphqlUrl}: ${remoteError.message}`);
this.logger.warn("Continuing with local schema only");
this.logger.error(`✗ Failed to connect to remote metadata GraphQL at ${this.settings.metadataGraphqlUrl}`);
this.logger.error(`✗ Error: ${remoteError.message}`);
this.logger.warn("⚠ FALLING BACK TO LOCAL SCHEMA ONLY");
this.logger.warn("⚠ Remote queries like 'getWeeklyPullList' will NOT be available");
this.logger.warn(`⚠ To fix: Ensure metadata-graphql service is running at ${this.settings.metadataGraphqlUrl}`);
this.schema = localSchema;
this.remoteSchemaAvailable = false;
}
this.logger.info("GraphQL service started successfully");

View File

@@ -623,7 +623,7 @@ export default class ImportService extends Service {
console.log(
"[GraphQL Import] Triggering metadata resolution..."
);
await this.broker.call("graphql.query", {
await this.broker.call("graphql.graphql", {
query: `
mutation ResolveMetadata($comicId: ID!) {
resolveMetadata(comicId: $comicId) {

View File

@@ -135,7 +135,7 @@ export async function importComicViaGraphQL(
}
try {
const result: any = await broker.call("graphql.query", {
const result: any = await broker.call("graphql.graphql", {
query: mutation,
variables: { input },
});
@@ -183,7 +183,7 @@ export async function updateSourcedMetadataViaGraphQL(
`;
try {
const result: any = await broker.call("graphql.query", {
const result: any = await broker.call("graphql.graphql", {
query: mutation,
variables: {
comicId,
@@ -229,7 +229,7 @@ export async function resolveMetadataViaGraphQL(
`;
try {
const result: any = await broker.call("graphql.query", {
const result: any = await broker.call("graphql.graphql", {
query: mutation,
variables: { comicId },
});
@@ -287,7 +287,7 @@ export async function getComicViaGraphQL(
`;
try {
const result: any = await broker.call("graphql.query", {
const result: any = await broker.call("graphql.graphql", {
query,
variables: { id: comicId },
});
@@ -336,7 +336,7 @@ export async function analyzeMetadataConflictsViaGraphQL(
`;
try {
const result: any = await broker.call("graphql.query", {
const result: any = await broker.call("graphql.graphql", {
query,
variables: { comicId },
});
@@ -373,7 +373,7 @@ export async function bulkResolveMetadataViaGraphQL(
`;
try {
const result: any = await broker.call("graphql.query", {
const result: any = await broker.call("graphql.graphql", {
query: mutation,
variables: { comicIds },
});