Cleaning up code
This commit is contained in:
25
.eslintrc.js
25
.eslintrc.js
@@ -4,7 +4,10 @@ module.exports = {
|
||||
es6: true,
|
||||
node: true
|
||||
},
|
||||
ignorePatterns: [ "test/*"],
|
||||
extends: [
|
||||
"eslint:recommended"
|
||||
],
|
||||
ignorePatterns: ["test/*", ".eslintrc.js"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
project: "tsconfig.json",
|
||||
@@ -14,8 +17,6 @@ module.exports = {
|
||||
rules: {
|
||||
"@typescript-eslint/adjacent-overload-signatures": "error",
|
||||
"@typescript-eslint/array-type": "error",
|
||||
"@typescript-eslint/ban-types": "error",
|
||||
"@typescript-eslint/class-name-casing": "off",
|
||||
"@typescript-eslint/consistent-type-assertions": "error",
|
||||
"@typescript-eslint/consistent-type-definitions": "error",
|
||||
"@typescript-eslint/explicit-member-accessibility": [
|
||||
@@ -24,19 +25,6 @@ module.exports = {
|
||||
accessibility: "explicit"
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/indent": [
|
||||
"off",
|
||||
4,
|
||||
{
|
||||
FunctionDeclaration: {
|
||||
parameters: "first"
|
||||
},
|
||||
FunctionExpression: {
|
||||
parameters: "first"
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/interface-name-prefix": "off",
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"error",
|
||||
{
|
||||
@@ -56,9 +44,8 @@ module.exports = {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-misused-new": "error",
|
||||
"@typescript-eslint/no-namespace": "error",
|
||||
"@typescript-eslint/no-parameter-properties": "off",
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/no-var-requires": "error",
|
||||
"@typescript-eslint/no-require-imports": "off",
|
||||
"@typescript-eslint/prefer-for-of": "error",
|
||||
"@typescript-eslint/prefer-function-type": "error",
|
||||
"@typescript-eslint/prefer-namespace-keyword": "error",
|
||||
@@ -85,7 +72,7 @@ module.exports = {
|
||||
"eol-last": "error",
|
||||
eqeqeq: ["error", "smart"],
|
||||
"guard-for-in": "error",
|
||||
"id-blacklist": ["error", "any", "Number", "number", "String", "string", "Boolean", "boolean", "Undefined", "undefined"],
|
||||
"id-denylist": ["error", "any", "Number", "number", "String", "string", "Boolean", "boolean", "Undefined", "undefined"],
|
||||
"id-match": "error",
|
||||
"import/order": "error",
|
||||
"max-classes-per-file": ["error", 1],
|
||||
|
||||
@@ -10,7 +10,7 @@ export const resolvers = {
|
||||
*/
|
||||
searchComicVine: async (_: any, { input }: any, context: any) => {
|
||||
const { broker } = context;
|
||||
|
||||
|
||||
if (!broker) {
|
||||
throw new Error("Broker not available in context");
|
||||
}
|
||||
@@ -20,7 +20,8 @@ export const resolvers = {
|
||||
resources: input.resources,
|
||||
format: input.format || "json",
|
||||
sort: input.sort,
|
||||
field_list: input.field_list,
|
||||
// eslint-disable-next-line camelcase
|
||||
field_list: input.fieldList,
|
||||
limit: input.limit?.toString(),
|
||||
offset: input.offset?.toString(),
|
||||
});
|
||||
@@ -31,7 +32,7 @@ export const resolvers = {
|
||||
*/
|
||||
volumeBasedSearch: async (_: any, { input }: any, context: any) => {
|
||||
const { broker } = context;
|
||||
|
||||
|
||||
if (!broker) {
|
||||
throw new Error("Broker not available in context");
|
||||
}
|
||||
@@ -59,7 +60,7 @@ export const resolvers = {
|
||||
*/
|
||||
getVolume: async (_: any, { input }: any, context: any) => {
|
||||
const { broker } = context;
|
||||
|
||||
|
||||
if (!broker) {
|
||||
throw new Error("Broker not available in context");
|
||||
}
|
||||
@@ -75,7 +76,7 @@ export const resolvers = {
|
||||
*/
|
||||
getIssuesForSeries: async (_: any, { comicObjectId }: any, context: any) => {
|
||||
const { broker } = context;
|
||||
|
||||
|
||||
if (!broker) {
|
||||
throw new Error("Broker not available in context");
|
||||
}
|
||||
@@ -90,7 +91,7 @@ export const resolvers = {
|
||||
*/
|
||||
getComicVineResource: async (_: any, { input }: any, context: any) => {
|
||||
const { broker } = context;
|
||||
|
||||
|
||||
if (!broker) {
|
||||
throw new Error("Broker not available in context");
|
||||
}
|
||||
@@ -107,7 +108,7 @@ export const resolvers = {
|
||||
*/
|
||||
getStoryArcs: async (_: any, { volumeId }: any, context: any) => {
|
||||
const { broker } = context;
|
||||
|
||||
|
||||
if (!broker) {
|
||||
throw new Error("Broker not available in context");
|
||||
}
|
||||
@@ -122,7 +123,7 @@ export const resolvers = {
|
||||
*/
|
||||
getWeeklyPullList: async (_: any, { input }: any, context: any) => {
|
||||
const { broker } = context;
|
||||
|
||||
|
||||
if (!broker) {
|
||||
throw new Error("Broker not available in context");
|
||||
}
|
||||
@@ -156,7 +157,7 @@ export const resolvers = {
|
||||
*/
|
||||
fetchMetronResource: async (_: any, { input }: any, context: any) => {
|
||||
const { broker } = context;
|
||||
|
||||
|
||||
if (!broker) {
|
||||
throw new Error("Broker not available in context");
|
||||
}
|
||||
@@ -183,14 +184,8 @@ export const resolvers = {
|
||||
|
||||
// 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;
|
||||
},
|
||||
__parseValue: (value: any): any => value,
|
||||
__serialize: (value: any): any => value,
|
||||
__parseLiteral: (ast: any): any => ast.value,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
BrokerOptions,
|
||||
Errors,
|
||||
MetricRegistry,
|
||||
ServiceBroker,
|
||||
} from "moleculer";
|
||||
|
||||
/**
|
||||
@@ -91,7 +90,7 @@ const brokerConfig: BrokerOptions = {
|
||||
// Backoff factor for delay. 2 means exponential backoff.
|
||||
factor: 2,
|
||||
// A function to check failed requests.
|
||||
check: (err: Errors.MoleculerError) => err && !!err.retryable,
|
||||
check: (err: Errors.MoleculerError | Error) => err && !!(err as Errors.MoleculerError).retryable,
|
||||
},
|
||||
|
||||
// Limit of calling level. If it reaches the limit, broker will throw an MaxCallLevelError error. (Infinite loop protection)
|
||||
@@ -138,7 +137,7 @@ const brokerConfig: BrokerOptions = {
|
||||
// Number of milliseconds to switch from open to half-open state
|
||||
halfOpenTime: 10 * 1000,
|
||||
// A function to check failed requests.
|
||||
check: (err: Errors.MoleculerError) => err && err.code >= 500,
|
||||
check: (err: Errors.MoleculerError | Error) => err && (err as Errors.MoleculerError).code >= 500,
|
||||
},
|
||||
|
||||
// Settings of bulkhead feature. More info: https://moleculer.services/docs/0.14/fault-tolerance.html#Bulkhead
|
||||
@@ -154,7 +153,7 @@ const brokerConfig: BrokerOptions = {
|
||||
// Enable action & event parameter validation. More info: https://moleculer.services/docs/0.14/validating.html
|
||||
validator: true,
|
||||
|
||||
errorHandler: null,
|
||||
errorHandler: undefined,
|
||||
|
||||
// Enable/disable built-in metrics function. More info: https://moleculer.services/docs/0.14/metrics.html
|
||||
metrics: {
|
||||
|
||||
29913
package-lock.json
generated
29913
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
45
package.json
45
package.json
@@ -21,23 +21,22 @@
|
||||
"author": "",
|
||||
"devDependencies": {
|
||||
"@faker-js/faker": "^9.7.0",
|
||||
"@types/jsdom": "^16.2.14",
|
||||
"@types/jsdom": "^21.1.6",
|
||||
"@types/lodash": "^4.14.171",
|
||||
"@types/string-similarity": "^4.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^2.26.0",
|
||||
"@typescript-eslint/parser": "^2.26.0",
|
||||
"eslint": "^6.8.0",
|
||||
"eslint-plugin-import": "^2.20.2",
|
||||
"eslint-plugin-prefer-arrow": "^1.2.2",
|
||||
"jest": "^25.1.0",
|
||||
"jest-cli": "^25.1.0",
|
||||
"moleculer-repl": "^0.6.2",
|
||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||
"@typescript-eslint/parser": "^7.18.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-prefer-arrow": "^1.2.3",
|
||||
"jest": "^29.7.0",
|
||||
"jest-cli": "^29.7.0",
|
||||
"moleculer-repl": "^0.7.4",
|
||||
"puppeteer": "^24.7.1",
|
||||
"puppeteer-extra": "^3.3.6",
|
||||
"puppeteer-extra-plugin-stealth": "^2.11.2",
|
||||
"telnet-client": "^2.2.5",
|
||||
"threetwo-ui-typings": "^1.0.14",
|
||||
"ts-jest": "^25.3.0",
|
||||
"ts-jest": "^29.1.2",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
@@ -47,11 +46,10 @@
|
||||
"@graphql-tools/stitch": "^10.1.12",
|
||||
"@graphql-tools/utils": "^11.0.0",
|
||||
"@graphql-tools/wrap": "^11.1.8",
|
||||
"@types/axios": "^0.14.0",
|
||||
"@types/jest": "^25.1.4",
|
||||
"@types/jest": "^29.5.12",
|
||||
"@types/mkdirp": "^1.0.0",
|
||||
"@types/node": "^13.9.8",
|
||||
"axios": "^0.21.1",
|
||||
"axios": "^1.7.7",
|
||||
"comicgeeks": "^1.1.0",
|
||||
"date-fns": "^2.27.0",
|
||||
"delay": "^5.0.0",
|
||||
@@ -61,7 +59,7 @@
|
||||
"graphql-tag": "^2.12.6",
|
||||
"imghash": "^0.0.9",
|
||||
"ioredis": "^4.28.1",
|
||||
"jsdom": "^19.0.0",
|
||||
"jsdom": "^24.1.0",
|
||||
"leven": "^3.1.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moleculer": "^0.14.28",
|
||||
@@ -69,8 +67,7 @@
|
||||
"moleculer-web": "^0.10.5",
|
||||
"nats": "^1.3.2",
|
||||
"paginate-info": "^1.0.4",
|
||||
"query-string": "^7.0.1",
|
||||
"string-similarity": "^4.0.4"
|
||||
"query-string": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.x.x"
|
||||
@@ -84,15 +81,15 @@
|
||||
"js"
|
||||
],
|
||||
"transform": {
|
||||
"^.+\\.(ts|tsx)$": "ts-jest"
|
||||
"^.+\\.(ts|tsx)$": [
|
||||
"ts-jest",
|
||||
{
|
||||
"tsconfig": "tsconfig.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
"testMatch": [
|
||||
"**/*.spec.(ts|js)"
|
||||
],
|
||||
"globals": {
|
||||
"ts-jest": {
|
||||
"tsConfig": "tsconfig.json"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { IncomingMessage } from "http";
|
||||
import { Service, ServiceBroker, Context } from "moleculer";
|
||||
import { Service, ServiceBroker } from "moleculer";
|
||||
import ApiGateway from "moleculer-web";
|
||||
|
||||
export default class ApiService extends Service {
|
||||
@@ -72,7 +71,7 @@ export default class ApiService extends Service {
|
||||
"POST /": async (req: any, res: any) => {
|
||||
try {
|
||||
const { query, variables, operationName } = req.body;
|
||||
|
||||
|
||||
const result = await req.$ctx.broker.call("gateway.query", {
|
||||
query,
|
||||
variables,
|
||||
@@ -151,7 +150,7 @@ export default class ApiService extends Service {
|
||||
"POST /": async (req: any, res: any) => {
|
||||
try {
|
||||
const { query, variables, operationName } = req.body;
|
||||
|
||||
|
||||
const result = await req.$ctx.broker.call("gateway.queryLocal", {
|
||||
query,
|
||||
variables,
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
import { Service, ServiceBroker, Context } from "moleculer";
|
||||
import axios from "axios";
|
||||
import { isNil, isUndefined } from "lodash";
|
||||
import { fetchReleases, FilterTypes, SortTypes } from "comicgeeks";
|
||||
import { isNil } from "lodash";
|
||||
import { matchScorer, rankVolumes } from "../utils/searchmatchscorer.utils";
|
||||
import { scrapeIssuePage, getWeeklyPullList } from "../utils/scraping.utils";
|
||||
import { getWeeklyPullList } from "../utils/scraping.utils";
|
||||
const { calculateLimitAndOffset, paginate } = require("paginate-info");
|
||||
const { MoleculerError } = require("moleculer").Errors;
|
||||
|
||||
@@ -25,7 +24,7 @@ export default class ComicVineService extends Service {
|
||||
format: string;
|
||||
sort: string;
|
||||
query: string;
|
||||
field_list: string;
|
||||
fieldList: string;
|
||||
limit: string;
|
||||
offset: string;
|
||||
resources: string;
|
||||
@@ -61,10 +60,11 @@ export default class ComicVineService extends Service {
|
||||
process.env.COMICVINE_API_KEY,
|
||||
params: {
|
||||
format: "json",
|
||||
// eslint-disable-next-line camelcase
|
||||
field_list: fieldList,
|
||||
},
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Accept": "application/json",
|
||||
"User-Agent": "ThreeTwo",
|
||||
},
|
||||
});
|
||||
@@ -97,7 +97,7 @@ export default class ComicVineService extends Service {
|
||||
filter: `volume:${comicBookDetails.sourcedMetadata.comicvine.volumeInformation.id}`,
|
||||
},
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
"Accept": "application/json",
|
||||
"User-Agent": "ThreeTwo",
|
||||
},
|
||||
});
|
||||
@@ -173,6 +173,7 @@ export default class ComicVineService extends Service {
|
||||
limit: "100",
|
||||
format: "json",
|
||||
filter: `${filter}`,
|
||||
// eslint-disable-next-line camelcase
|
||||
field_list: `${fieldList}`,
|
||||
},
|
||||
headers: {
|
||||
@@ -201,7 +202,7 @@ export default class ComicVineService extends Service {
|
||||
searchParams: {
|
||||
name: string;
|
||||
subtitle?: string;
|
||||
number: string;
|
||||
issueNumber: string;
|
||||
year: string;
|
||||
};
|
||||
};
|
||||
@@ -209,35 +210,38 @@ export default class ComicVineService extends Service {
|
||||
}>
|
||||
) => {
|
||||
try {
|
||||
console.log(
|
||||
"Searching against: ",
|
||||
ctx.params.scorerConfiguration.searchParams
|
||||
);
|
||||
const { rawFileDetails, scorerConfiguration } =
|
||||
ctx.params;
|
||||
if (!scorerConfiguration) {
|
||||
throw new Error("scorerConfiguration is required");
|
||||
}
|
||||
console.log(
|
||||
"Searching against: ",
|
||||
scorerConfiguration.searchParams
|
||||
);
|
||||
const results: any = [];
|
||||
console.log(
|
||||
"passed to fetchVolumesFromCV",
|
||||
ctx.params
|
||||
);
|
||||
|
||||
|
||||
// Send initial status to client
|
||||
await this.broker.call("socket.broadcast", {
|
||||
namespace: "/",
|
||||
event: "CV_SCRAPING_STATUS",
|
||||
args: [
|
||||
{
|
||||
message: `Starting volume search for: ${ctx.params.scorerConfiguration.searchParams.name}`,
|
||||
stage: "fetching_volumes"
|
||||
message: `Starting volume search for: ${scorerConfiguration.searchParams.name}`,
|
||||
stage: "fetching_volumes",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
const volumes = await this.fetchVolumesFromCV(
|
||||
ctx.params,
|
||||
results
|
||||
);
|
||||
|
||||
|
||||
// Notify client that volume fetching is complete
|
||||
await this.broker.call("socket.broadcast", {
|
||||
namespace: "/",
|
||||
@@ -245,20 +249,20 @@ export default class ComicVineService extends Service {
|
||||
args: [
|
||||
{
|
||||
message: `Fetched ${volumes.length} volumes, now ranking matches...`,
|
||||
stage: "ranking_volumes"
|
||||
stage: "ranking_volumes",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
// 1. Run the current batch of volumes through the matcher
|
||||
const potentialVolumeMatches = rankVolumes(
|
||||
volumes,
|
||||
ctx.params.scorerConfiguration
|
||||
);
|
||||
|
||||
|
||||
// Sort by totalScore in descending order to prioritize best matches
|
||||
potentialVolumeMatches.sort((a: any, b: any) => b.totalScore - a.totalScore);
|
||||
|
||||
|
||||
// Notify client about ranked matches
|
||||
await this.broker.call("socket.broadcast", {
|
||||
namespace: "/",
|
||||
@@ -266,11 +270,11 @@ export default class ComicVineService extends Service {
|
||||
args: [
|
||||
{
|
||||
message: `Found ${potentialVolumeMatches.length} potential volume matches, searching for issues...`,
|
||||
stage: "searching_issues"
|
||||
stage: "searching_issues",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
// 2. Construct the filter string
|
||||
// 2a. volume: 1111|2222|3333
|
||||
let volumeIdString = "volume:";
|
||||
@@ -291,20 +295,18 @@ export default class ComicVineService extends Service {
|
||||
let coverDateFilter = "";
|
||||
if (
|
||||
!isNil(
|
||||
ctx.params.scorerConfiguration.searchParams
|
||||
.year
|
||||
scorerConfiguration.searchParams.year
|
||||
)
|
||||
) {
|
||||
const issueYear = parseInt(
|
||||
ctx.params.scorerConfiguration.searchParams
|
||||
.year,
|
||||
scorerConfiguration.searchParams.year,
|
||||
10
|
||||
);
|
||||
coverDateFilter = `cover_date:${
|
||||
issueYear - 1
|
||||
}-01-01|${issueYear + 1}-12-31`;
|
||||
}
|
||||
const filterString = `issue_number:${ctx.params.scorerConfiguration.searchParams.number},${volumeIdString},${coverDateFilter}`;
|
||||
const filterString = `issue_number:${scorerConfiguration.searchParams.issueNumber},${volumeIdString},${coverDateFilter}`;
|
||||
console.log(filterString);
|
||||
|
||||
const issueMatches = await axios({
|
||||
@@ -327,7 +329,7 @@ export default class ComicVineService extends Service {
|
||||
console.log(
|
||||
`Total issues matching the criteria: ${issueMatches.data.results.length}`
|
||||
);
|
||||
|
||||
|
||||
// Handle case when no issues are found
|
||||
if (issueMatches.data.results.length === 0) {
|
||||
await this.broker.call("socket.broadcast", {
|
||||
@@ -335,19 +337,19 @@ export default class ComicVineService extends Service {
|
||||
event: "CV_SCRAPING_STATUS",
|
||||
args: [
|
||||
{
|
||||
message: `No matching issues found. Try adjusting your search criteria.`,
|
||||
stage: "complete"
|
||||
message: "No matching issues found. Try adjusting your search criteria.",
|
||||
stage: "complete",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
return {
|
||||
finalMatches: [],
|
||||
rawFileDetails,
|
||||
scorerConfiguration,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Notify client about issue matches found
|
||||
await this.broker.call("socket.broadcast", {
|
||||
namespace: "/",
|
||||
@@ -355,11 +357,11 @@ export default class ComicVineService extends Service {
|
||||
args: [
|
||||
{
|
||||
message: `Found ${issueMatches.data.results.length} issue matches, fetching volume details...`,
|
||||
stage: "fetching_volume_details"
|
||||
stage: "fetching_volume_details",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
// 3. get volume information for the issue matches
|
||||
if (issueMatches.data.results.length === 1) {
|
||||
const volumeInformation =
|
||||
@@ -373,19 +375,19 @@ export default class ComicVineService extends Service {
|
||||
);
|
||||
issueMatches.data.results[0].volumeInformation =
|
||||
volumeInformation;
|
||||
|
||||
|
||||
// Notify scoring for single match
|
||||
await this.broker.call("socket.broadcast", {
|
||||
namespace: "/",
|
||||
event: "CV_SCRAPING_STATUS",
|
||||
args: [
|
||||
{
|
||||
message: `Scoring 1 match...`,
|
||||
stage: "scoring_matches"
|
||||
message: "Scoring 1 match...",
|
||||
stage: "scoring_matches",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
// Score the single match
|
||||
const scoredMatch = await this.broker.call(
|
||||
"comicvine.getComicVineMatchScores",
|
||||
@@ -395,19 +397,19 @@ export default class ComicVineService extends Service {
|
||||
scorerConfiguration,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// Notify completion
|
||||
await this.broker.call("socket.broadcast", {
|
||||
namespace: "/",
|
||||
event: "CV_SCRAPING_STATUS",
|
||||
args: [
|
||||
{
|
||||
message: `Search complete! Found 1 match.`,
|
||||
stage: "complete"
|
||||
message: "Search complete! Found 1 match.",
|
||||
stage: "complete",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
return scoredMatch;
|
||||
}
|
||||
const finalMatchesPromises = issueMatches.data.results.map(
|
||||
@@ -424,10 +426,10 @@ export default class ComicVineService extends Service {
|
||||
return issue;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// Wait for all volume details to be fetched
|
||||
const finalMatches = await Promise.all(finalMatchesPromises);
|
||||
|
||||
|
||||
// Notify client about scoring
|
||||
await this.broker.call("socket.broadcast", {
|
||||
namespace: "/",
|
||||
@@ -435,11 +437,11 @@ export default class ComicVineService extends Service {
|
||||
args: [
|
||||
{
|
||||
message: `Scoring ${finalMatches.length} matches...`,
|
||||
stage: "scoring_matches"
|
||||
stage: "scoring_matches",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
// Score the final matches
|
||||
const scoredMatches = await this.broker.call(
|
||||
"comicvine.getComicVineMatchScores",
|
||||
@@ -449,41 +451,42 @@ export default class ComicVineService extends Service {
|
||||
scorerConfiguration,
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// Notify completion
|
||||
await this.broker.call("socket.broadcast", {
|
||||
namespace: "/",
|
||||
event: "CV_SCRAPING_STATUS",
|
||||
args: [
|
||||
{
|
||||
message: `Search complete! Returning scored matches.`,
|
||||
stage: "complete"
|
||||
message: "Search complete! Returning scored matches.",
|
||||
stage: "complete",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
return scoredMatches;
|
||||
} catch (error) {
|
||||
} catch (err: unknown) {
|
||||
const error = err as any;
|
||||
console.error("Error in volumeBasedSearch:", error);
|
||||
|
||||
|
||||
// Surface error to UI
|
||||
await this.broker.call("socket.broadcast", {
|
||||
namespace: "/",
|
||||
event: "CV_SCRAPING_STATUS",
|
||||
args: [
|
||||
{
|
||||
message: `Error during search: ${error.message || 'Unknown error'}`,
|
||||
message: `Error during search: ${error.message || "Unknown error"}`,
|
||||
stage: "error",
|
||||
error: {
|
||||
message: error.message,
|
||||
code: error.code,
|
||||
type: error.type,
|
||||
retryable: error.retryable
|
||||
}
|
||||
retryable: error.retryable,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
// Re-throw or return error response
|
||||
throw error;
|
||||
}
|
||||
@@ -541,7 +544,8 @@ export default class ComicVineService extends Service {
|
||||
const issuePromises =
|
||||
volumeData.results.issues.map(
|
||||
async (issue: any) => {
|
||||
const issueUrl = `${CV_BASE_URL}issue/4000-${issue.id}/?api_key=${process.env.COMICVINE_API_KEY}&format=json&field_list=story_arc_credits,description,image`;
|
||||
const issueUrl = `${CV_BASE_URL}issue/4000-${issue.id}/?api_key=${process.env.COMICVINE_API_KEY}` +
|
||||
"&format=json&field_list=story_arc_credits,description,image";
|
||||
try {
|
||||
const issueResponse =
|
||||
await axios.get(issueUrl, {
|
||||
@@ -570,7 +574,8 @@ export default class ComicVineService extends Service {
|
||||
})
|
||||
) || []
|
||||
);
|
||||
} catch (error) {
|
||||
} catch (err: unknown) {
|
||||
const error = err as any;
|
||||
console.error(
|
||||
"An error occurred while fetching issue data:",
|
||||
error.message
|
||||
@@ -634,11 +639,12 @@ export default class ComicVineService extends Service {
|
||||
try {
|
||||
const response = await axios.get(issuesUrl, {
|
||||
params: {
|
||||
// eslint-disable-next-line camelcase
|
||||
api_key: process.env.COMICVINE_API_KEY,
|
||||
filter: `volume:${volumeId}`,
|
||||
format: "json",
|
||||
field_list:
|
||||
"id,name,image,issue_number,cover_date,description",
|
||||
// eslint-disable-next-line camelcase
|
||||
field_list: "id,name,image,issue_number,cover_date,description",
|
||||
limit: 100,
|
||||
},
|
||||
headers: {
|
||||
@@ -651,10 +657,8 @@ export default class ComicVineService extends Service {
|
||||
const issuesWithDescriptionImageAndYear =
|
||||
response.data.results.map((issue: any) => {
|
||||
const year = issue.cover_date
|
||||
? new Date(
|
||||
issue.cover_date
|
||||
).getFullYear()
|
||||
: null; // Extract the year from cover_date
|
||||
? new Date(issue.cover_date).getFullYear()
|
||||
: null;
|
||||
return {
|
||||
...issue,
|
||||
year,
|
||||
@@ -664,7 +668,8 @@ export default class ComicVineService extends Service {
|
||||
});
|
||||
|
||||
return issuesWithDescriptionImageAndYear;
|
||||
} catch (error) {
|
||||
} catch (err: unknown) {
|
||||
const error = err as any;
|
||||
this.logger.error(
|
||||
"Error fetching issues from ComicVine:",
|
||||
error.message
|
||||
|
||||
@@ -121,14 +121,12 @@ export default class GatewayService extends Service {
|
||||
this.logger.info("Local metadata Apollo Server started");
|
||||
|
||||
// Create local executor
|
||||
const localExecutor: AsyncExecutor = async ({ document, variables, context }) => {
|
||||
return execute({
|
||||
const localExecutor: AsyncExecutor = async ({ document, variables, context }) => execute({
|
||||
schema: localSchema,
|
||||
document,
|
||||
variableValues: variables,
|
||||
contextValue: { broker: context?.broker || this.broker, ctx: context?.ctx },
|
||||
}) as any;
|
||||
};
|
||||
|
||||
// Try to introspect remote schema
|
||||
let remoteSchema = null;
|
||||
@@ -155,7 +153,7 @@ export default class GatewayService extends Service {
|
||||
{ schema: remoteSchema, executor: this.createRemoteExecutor() },
|
||||
],
|
||||
mergeTypes: false,
|
||||
})
|
||||
})
|
||||
: localSchema;
|
||||
|
||||
this.apolloServer = new ApolloServer({ schema, introspection: true });
|
||||
@@ -181,11 +179,11 @@ export default class GatewayService extends Service {
|
||||
/**
|
||||
* Service lifecycle hooks
|
||||
*/
|
||||
started: async function (this: any) {
|
||||
async started() {
|
||||
await this.initApolloGateway();
|
||||
},
|
||||
|
||||
stopped: async function (this: any) {
|
||||
async stopped() {
|
||||
await this.stopApolloGateway();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
import axios from "axios";
|
||||
import { Context, Service, ServiceBroker } from "moleculer";
|
||||
|
||||
const METRON_BASE_URL = "https://metron.cloud/api/";
|
||||
|
||||
export default class MetronService extends Service {
|
||||
public constructor(public broker: ServiceBroker) {
|
||||
super(broker);
|
||||
@@ -34,8 +32,8 @@ export default class MetronService extends Service {
|
||||
},
|
||||
auth: {
|
||||
username: "frishi",
|
||||
password: "Titu@1588"
|
||||
}
|
||||
password: "Titu@1588",
|
||||
},
|
||||
});
|
||||
return results.data;
|
||||
},
|
||||
|
||||
@@ -34,11 +34,25 @@ SOFTWARE.
|
||||
import { createWriteStream, existsSync, mkdirSync } from "fs";
|
||||
import path from "path";
|
||||
import https from "https";
|
||||
import stringSimilarity from "string-similarity";
|
||||
import { isNil, map, isUndefined } from "lodash";
|
||||
import { isNil, isUndefined } from "lodash";
|
||||
import leven from "leven";
|
||||
import { isAfter, isSameYear, parseISO } from "date-fns";
|
||||
|
||||
/**
|
||||
* Compute string similarity score (0-1) using Levenshtein distance.
|
||||
* Replaces deprecated string-similarity package.
|
||||
*/
|
||||
const compareTwoStrings = (str1: string, str2: string): number => {
|
||||
if (str1 === str2) {
|
||||
return 1;
|
||||
}
|
||||
const maxLen = Math.max(str1.length, str2.length);
|
||||
if (maxLen === 0) {
|
||||
return 1;
|
||||
}
|
||||
return 1 - leven(str1, str2) / maxLen;
|
||||
};
|
||||
|
||||
const imghash = require("imghash");
|
||||
|
||||
export const matchScorer = async (
|
||||
@@ -49,13 +63,13 @@ export const matchScorer = async (
|
||||
const scoredMatches: any = [];
|
||||
|
||||
try {
|
||||
// searchMatches is already an array of match objects, not promises
|
||||
// SearchMatches is already an array of match objects, not promises
|
||||
for (const match of searchMatches) {
|
||||
match.score = 0;
|
||||
|
||||
// Check for the issue name match
|
||||
if (!isNil(searchQuery.name) && !isNil(match.name)) {
|
||||
const issueNameScore = stringSimilarity.compareTwoStrings(
|
||||
const issueNameScore = compareTwoStrings(
|
||||
searchQuery.name,
|
||||
match.name
|
||||
);
|
||||
@@ -92,7 +106,7 @@ export const rankVolumes = (volumes: any, scorerConfiguration: any) => {
|
||||
// 2. If there is a strong string comparison between the volume name and the issue name ??
|
||||
const issueNumber = parseInt(scorerConfiguration.searchParams.number, 10);
|
||||
const issueYear = parseISO(scorerConfiguration.searchParams.year);
|
||||
const rankedVolumes = volumes.map((volume: any, idx: number) => {
|
||||
const rankedVolumes = volumes.map((volume: any) => {
|
||||
let volumeMatchScore = 0;
|
||||
const volumeStartYear = !isNil(volume.start_year)
|
||||
? parseISO(volume.start_year)
|
||||
@@ -103,7 +117,7 @@ export const rankVolumes = (volumes: any, scorerConfiguration: any) => {
|
||||
const lastIssueNumber = !isNil(volume.last_issue)
|
||||
? parseInt(volume.last_issue.issue_number, 10)
|
||||
: null;
|
||||
let issueNameMatchScore = stringSimilarity.compareTwoStrings(
|
||||
let issueNameMatchScore = compareTwoStrings(
|
||||
scorerConfiguration.searchParams.name,
|
||||
volume.name
|
||||
);
|
||||
@@ -111,7 +125,7 @@ export const rankVolumes = (volumes: any, scorerConfiguration: any) => {
|
||||
// If not, move on.
|
||||
let subtitleMatchScore = 0;
|
||||
if (!isNil(scorerConfiguration.searchParams.subtitle)) {
|
||||
subtitleMatchScore = stringSimilarity.compareTwoStrings(
|
||||
subtitleMatchScore = compareTwoStrings(
|
||||
scorerConfiguration.searchParams.subtitle,
|
||||
volume.name
|
||||
);
|
||||
@@ -143,7 +157,7 @@ export const rankVolumes = (volumes: any, scorerConfiguration: any) => {
|
||||
id: volume.id,
|
||||
volumeMatchScore,
|
||||
issueNameMatchScore,
|
||||
totalScore: volumeMatchScore + issueNameMatchScore
|
||||
totalScore: volumeMatchScore + issueNameMatchScore,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
@@ -152,7 +166,7 @@ export const rankVolumes = (volumes: any, scorerConfiguration: any) => {
|
||||
};
|
||||
|
||||
const calculateLevenshteinDistance = async (match: any, rawFileDetails: any) =>
|
||||
new Promise((resolve) => {
|
||||
new Promise(resolve => {
|
||||
https.get(match.image.small_url, (response: any) => {
|
||||
console.log(rawFileDetails.cover.filePath);
|
||||
const fileName = match.id + "_" + rawFileDetails.name + ".jpg";
|
||||
@@ -200,7 +214,8 @@ const calculateLevenshteinDistance = async (match: any, rawFileDetails: any) =>
|
||||
}
|
||||
resolve(match);
|
||||
} catch (err) {
|
||||
console.warn(`Image hashing failed for ${fileName}, skipping score adjustment:`, err.message);
|
||||
const errorMessage = err instanceof Error ? err.message : String(err);
|
||||
console.warn(`Image hashing failed for ${fileName}, skipping score adjustment:`, errorMessage);
|
||||
resolve(match);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user