⬇ Import flow fixes
This commit is contained in:
@@ -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)
|
* Get cached import statistics (fast, real-time)
|
||||||
* @async
|
* @async
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ export default class ApiService extends Service {
|
|||||||
aliases: {
|
aliases: {
|
||||||
"POST /": "graphql.graphql",
|
"POST /": "graphql.graphql",
|
||||||
"GET /": "graphql.graphql",
|
"GET /": "graphql.graphql",
|
||||||
|
"GET /health": "graphql.checkRemoteSchema",
|
||||||
},
|
},
|
||||||
mappingPolicy: "restrict",
|
mappingPolicy: "restrict",
|
||||||
bodyParsers: {
|
bodyParsers: {
|
||||||
|
|||||||
@@ -114,6 +114,36 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
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
|
* Execute GraphQL queries and mutations
|
||||||
* @param query - GraphQL query or mutation string
|
* @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}`);
|
this.logger.info(`Attempting to introspect remote schema at ${this.settings.metadataGraphqlUrl}`);
|
||||||
|
|
||||||
const remoteSchema = await fetchRemoteSchema(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 remoteQueryType = remoteSchema.getQueryType();
|
||||||
|
const remoteMutationType = remoteSchema.getMutationType();
|
||||||
|
|
||||||
if (remoteQueryType) {
|
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({
|
this.schema = stitchSchemas({
|
||||||
@@ -212,15 +250,35 @@ export default {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const stitchedQueryType = this.schema.getQueryType();
|
const stitchedQueryType = this.schema.getQueryType();
|
||||||
|
const stitchedMutationType = this.schema.getMutationType();
|
||||||
|
|
||||||
if (stitchedQueryType) {
|
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) {
|
} catch (remoteError: any) {
|
||||||
this.logger.warn(`Could not connect to remote metadata GraphQL at ${this.settings.metadataGraphqlUrl}: ${remoteError.message}`);
|
this.logger.error(`✗ Failed to connect to remote metadata GraphQL at ${this.settings.metadataGraphqlUrl}`);
|
||||||
this.logger.warn("Continuing with local schema only");
|
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.schema = localSchema;
|
||||||
|
this.remoteSchemaAvailable = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.logger.info("GraphQL service started successfully");
|
this.logger.info("GraphQL service started successfully");
|
||||||
|
|||||||
@@ -623,7 +623,7 @@ export default class ImportService extends Service {
|
|||||||
console.log(
|
console.log(
|
||||||
"[GraphQL Import] Triggering metadata resolution..."
|
"[GraphQL Import] Triggering metadata resolution..."
|
||||||
);
|
);
|
||||||
await this.broker.call("graphql.query", {
|
await this.broker.call("graphql.graphql", {
|
||||||
query: `
|
query: `
|
||||||
mutation ResolveMetadata($comicId: ID!) {
|
mutation ResolveMetadata($comicId: ID!) {
|
||||||
resolveMetadata(comicId: $comicId) {
|
resolveMetadata(comicId: $comicId) {
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ export async function importComicViaGraphQL(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result: any = await broker.call("graphql.query", {
|
const result: any = await broker.call("graphql.graphql", {
|
||||||
query: mutation,
|
query: mutation,
|
||||||
variables: { input },
|
variables: { input },
|
||||||
});
|
});
|
||||||
@@ -183,7 +183,7 @@ export async function updateSourcedMetadataViaGraphQL(
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result: any = await broker.call("graphql.query", {
|
const result: any = await broker.call("graphql.graphql", {
|
||||||
query: mutation,
|
query: mutation,
|
||||||
variables: {
|
variables: {
|
||||||
comicId,
|
comicId,
|
||||||
@@ -229,7 +229,7 @@ export async function resolveMetadataViaGraphQL(
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result: any = await broker.call("graphql.query", {
|
const result: any = await broker.call("graphql.graphql", {
|
||||||
query: mutation,
|
query: mutation,
|
||||||
variables: { comicId },
|
variables: { comicId },
|
||||||
});
|
});
|
||||||
@@ -287,7 +287,7 @@ export async function getComicViaGraphQL(
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result: any = await broker.call("graphql.query", {
|
const result: any = await broker.call("graphql.graphql", {
|
||||||
query,
|
query,
|
||||||
variables: { id: comicId },
|
variables: { id: comicId },
|
||||||
});
|
});
|
||||||
@@ -336,7 +336,7 @@ export async function analyzeMetadataConflictsViaGraphQL(
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result: any = await broker.call("graphql.query", {
|
const result: any = await broker.call("graphql.graphql", {
|
||||||
query,
|
query,
|
||||||
variables: { comicId },
|
variables: { comicId },
|
||||||
});
|
});
|
||||||
@@ -373,7 +373,7 @@ export async function bulkResolveMetadataViaGraphQL(
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result: any = await broker.call("graphql.query", {
|
const result: any = await broker.call("graphql.graphql", {
|
||||||
query: mutation,
|
query: mutation,
|
||||||
variables: { comicIds },
|
variables: { comicIds },
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user