diff --git a/api/eslint.config.mjs b/api/eslint.config.mjs index 6bc17acc..8a7f28e0 100644 --- a/api/eslint.config.mjs +++ b/api/eslint.config.mjs @@ -20,8 +20,9 @@ export default [ languageOptions: { parser: tsParser, }, - rules: { - "@typescript-eslint/no-require-imports": "off" + rules: { + "@typescript-eslint/no-require-imports": "off", + "@typescript-eslint/triple-slash-reference": "off" }, } ]; diff --git a/api/package-lock.json b/api/package-lock.json index 55998565..ea5dedd6 100644 --- a/api/package-lock.json +++ b/api/package-lock.json @@ -44,8 +44,10 @@ "devDependencies": { "@types/express": "^5.0.6", "@types/express-session": "^1.18.2", + "@types/ini": "^4.1.1", "@types/jest": "^30", "@types/node": "^26", + "@types/pdfkit": "^0.17.6", "@typescript-eslint/eslint-plugin": "^8.52", "@typescript-eslint/parser": "^8.52", "eslint": "^10", @@ -2485,6 +2487,13 @@ "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "license": "MIT" }, + "node_modules/@types/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@types/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -2570,6 +2579,16 @@ "@types/passport": "*" } }, + "node_modules/@types/pdfkit": { + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.17.6.tgz", + "integrity": "sha512-tIwzxk2uWKp0Cq9JIluQXJid77lYhF52EsIOwhsMF4iWLA6YneoBR1xVKYYdAysHuepUB0OX4tdwMiUDdGKmig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/qs": { "version": "6.15.1", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", diff --git a/api/package.json b/api/package.json index 3786c70a..7bb2f85e 100644 --- a/api/package.json +++ b/api/package.json @@ -56,6 +56,9 @@ "devDependencies": { "@types/express": "^5.0.6", "@types/express-session": "^1.18.2", + "@types/ini": "^4.1.1", + "@types/jest": "^30.0.0", + "@types/pdfkit": "^0.17.6", "@types/jest": "^30", "@types/node": "^26", "@typescript-eslint/eslint-plugin": "^8.52", diff --git a/api/src/app.ts b/api/src/app.ts index bc3c8a7d..cd752cf1 100644 --- a/api/src/app.ts +++ b/api/src/app.ts @@ -1,7 +1,7 @@ -import * as express from "express"; -import * as cookieParser from "cookie-parser"; +import express from "express"; +import cookieParser from "cookie-parser"; import * as path from "path"; -import * as logger from "morgan"; +import logger from "morgan"; import Config from "./models/Config"; const app = express(); diff --git a/api/src/jobs/Derivative.test.ts b/api/src/jobs/Derivative.test.ts index 7df53718..e614509e 100644 --- a/api/src/jobs/Derivative.test.ts +++ b/api/src/jobs/Derivative.test.ts @@ -16,8 +16,8 @@ describe("Derivative", () => { describe("run", () => { let job: Job; - let consoleErrorSpy; - let consoleLogSpy; + let consoleErrorSpy: jest.SpyInstance; + let consoleLogSpy: jest.SpyInstance; beforeEach(() => { job = { data: { diff --git a/api/src/jobs/Derivative.ts b/api/src/jobs/Derivative.ts index 829310b4..ec37f745 100644 --- a/api/src/jobs/Derivative.ts +++ b/api/src/jobs/Derivative.ts @@ -12,7 +12,7 @@ class Derivative implements QueueJob { // For each page const order = PageOrder.fromJob(job.data); - const generatingPromises = []; + const generatingPromises: Promise[] = []; order.raw.forEach((page) => { // For each size const image = ImageFile.build(`${job.data.dir}/${page.filename}`); diff --git a/api/src/models/AudioFile.ts b/api/src/models/AudioFile.ts index 4d8f8744..40e26eb3 100644 --- a/api/src/models/AudioFile.ts +++ b/api/src/models/AudioFile.ts @@ -8,7 +8,7 @@ class AudioFile extends AbstractAVFile { return new AudioFile(filename, dir, Config.getInstance()); } - static fromRaw(raw: Record, config: Config = null): AudioFile { + static fromRaw(raw: Record, config: Config | null = null): AudioFile { return new AudioFile(raw.filename, raw.label, config ?? Config.getInstance()); } } diff --git a/api/src/models/Category.ts b/api/src/models/Category.ts index 96f215a1..a4b9e22b 100644 --- a/api/src/models/Category.ts +++ b/api/src/models/Category.ts @@ -5,7 +5,7 @@ import path = require("path"); import Job from "./Job"; -export class CategoryRaw { +export interface CategoryRaw { category: string; jobs: Array; } diff --git a/api/src/models/Config.ts b/api/src/models/Config.ts index 49f7bc6e..43898146 100644 --- a/api/src/models/Config.ts +++ b/api/src/models/Config.ts @@ -2,7 +2,7 @@ import fs = require("fs"); import ini = require("ini"); import { FedoraModel, License } from "../services/FedoraCatalog"; -type ConfigValue = string | string[] | ConfigRecord; +type ConfigValue = string | string[] | ConfigRecord | ConfigRecord[] | License; interface ConfigRecord { [key: string]: ConfigValue; } @@ -10,7 +10,7 @@ interface ConfigRecord { class Config { private static instance: Config; - protected ini; + protected ini: ConfigRecord; constructor(ini: ConfigRecord) { this.ini = ini; @@ -37,117 +37,117 @@ class Config { } get backendUrl(): string { - return this.ini["backend_url"] ?? "http://localhost:9000"; + return (this.ini["backend_url"] ?? "http://localhost:9000") as string; } get clientUrl(): string { - return this.ini["client_url"]; + return this.ini["client_url"] as string; } get fedoraUsername(): string { - return this.ini["fedora_username"]; + return this.ini["fedora_username"] as string; } get fedoraPassword(): string { - return this.ini["fedora_password"]; + return this.ini["fedora_password"] as string; } get fedoraPidNameSpace(): string { - return this.ini["fedora_pid_namespace"]; + return this.ini["fedora_pid_namespace"] as string; } get ffmpegPath(): string { - return this.ini["ffmpeg_path"]; + return this.ini["ffmpeg_path"] as string; } get fitsCommand(): string { - return this.ini["fits_command"]; + return this.ini["fits_command"] as string; } get sessionKey(): string { - return this.ini["session_key"] ?? "vanilla hot cocoa"; + return (this.ini["session_key"] ?? "vanilla hot cocoa") as string; } get tesseractPath(): string { - return this.ini["tesseract_path"]; + return this.ini["tesseract_path"] as string; } get tesseractAllowedChars(): string { - return this.ini["tesseract_allowed_characters"]; + return this.ini["tesseract_allowed_characters"] as string; } get vufindUrl(): string { - return this.ini["vufind_url"] ?? ""; + return (this.ini["vufind_url"] ?? "") as string; } get pdfDirectory(): string { - return this.ini["pdf_directory"]; + return this.ini["pdf_directory"] as string; } get textcleanerPath(): string { - return this.ini["textcleaner_path"]; + return this.ini["textcleaner_path"] as string; } get textcleanerSwitches(): string { - return this.ini["textcleaner_switches"]; + return this.ini["textcleaner_switches"] as string; } get holdingArea(): string { - const holdingArea = this.ini["holding_area_path"]; + const holdingArea = this.ini["holding_area_path"] as string; return holdingArea.endsWith("/") ? holdingArea : holdingArea + "/"; } get ocrmypdfPath(): string { - return this.ini["ocrmypdf_path"]; + return this.ini["ocrmypdf_path"] as string; } get processedAreaPath(): string { - return this.ini["processed_area_path"]; + return this.ini["processed_area_path"] as string; } get restBaseUrl(): string { - return this.ini["base_url"]; + return this.ini["base_url"] as string; } get javaPath(): string { - return this.ini["java_path"] ?? "java"; + return (this.ini["java_path"] ?? "java") as string; } - get tikaConfigFile(): string { - return this.ini["tika_config_file"] ?? null; + get tikaConfigFile(): string | null { + return (this.ini["tika_config_file"] ?? null) as string | null; } get tikaPath(): string { - return this.ini["tika_path"]; + return this.ini["tika_path"] as string; } get solrCore(): string { - return this.ini["solr_core"] ?? "biblio"; + return (this.ini["solr_core"] ?? "biblio") as string; } get solrUrl(): string { - return this.ini["solr_url"] ?? "http://localhost:8983/solr"; + return (this.ini["solr_url"] ?? "http://localhost:8983/solr") as string; } get solrDocumentCacheDir(): boolean | string { - return this.ini["solr_document_cache_dir"] ?? false; + return (this.ini["solr_document_cache_dir"] ?? false) as boolean | string; } get allowedOrigins(): string[] { - return this.ini["allowed_origins"] ?? []; + return (this.ini["allowed_origins"] ?? []) as string[]; } get pidNamespace(): string { - return this.ini["fedora_pid_namespace"] ?? "vudl"; + return (this.ini["fedora_pid_namespace"] ?? "vudl") as string; } get initialPidValue(): number { - return parseInt(this.ini["fedora_initial_pid"] ?? "0"); + return parseInt((this.ini["fedora_initial_pid"] ?? "0") as string); } get dataModels(): Record { return ( - this.ini["data_models"] ?? { + (this.ini["data_models"] as Record | undefined) ?? { Image: "vudl-system:ImageData", PDF: "vudl-system:PDFData", DOC: "vudl-system:DOCData", @@ -161,7 +161,7 @@ class Config { get collectionModels(): Record { return ( - this.ini["collection_models"] ?? { + (this.ini["collection_models"] as Record | undefined) ?? { List: "vudl-system:ListCollection", Resource: "vudl-system:ResourceCollection", Folder: "vudl-system:FolderCollection", @@ -170,27 +170,27 @@ class Config { } get institution(): string { - return this.ini["institution"] ?? "My University"; + return (this.ini["institution"] ?? "My University") as string; } get collection(): string { - return this.ini["collection"] ?? "Digital Library"; + return (this.ini["collection"] ?? "Digital Library") as string; } get topLevelPids(): Array { - return this.ini["top_level_pids"] ?? []; + return (this.ini["top_level_pids"] ?? []) as string[]; } get articlesToStrip(): Array { - return this.ini["articles_to_strip"] ?? []; + return (this.ini["articles_to_strip"] ?? []) as string[]; } get trashPid(): string | null { - return this.ini["trash_pid"] ?? null; + return (this.ini["trash_pid"] ?? null) as string | null; } get favoritePids(): Array { - const favorites = this.ini["favorite_pids"] ?? []; + const favorites = (this.ini["favorite_pids"] ?? []) as string[]; const trash = this.trashPid; if (trash && !favorites.includes(trash)) { favorites.push(trash); @@ -199,23 +199,23 @@ class Config { } get languageMap(): Record { - return this.ini["LanguageMap"] ?? {}; + return (this.ini["LanguageMap"] ?? {}) as Record; } get minimumValidYear(): number { - return parseInt(this.ini["minimum_valid_year"] ?? 1000); + return parseInt((this.ini["minimum_valid_year"] ?? "1000") as string); } get models(): Record { - return this.ini["models"] || {}; + return (this.ini["models"] || {}) as Record; } get databaseSettings(): ConfigRecord { - return this.ini["Database"] ?? {}; + return (this.ini["Database"] ?? {}) as ConfigRecord; } get databaseClient(): string { - return (this.databaseSettings["client"] as string) ?? "sqlite3"; + return (this.databaseSettings["client"] ?? "sqlite3") as string; } get databaseConnectionSettings(): ConfigRecord { @@ -223,15 +223,15 @@ class Config { } get authenticationSettings(): ConfigRecord { - return this.ini["Authentication"] ?? []; + return (this.ini["Authentication"] ?? {}) as ConfigRecord; } get authenticationStrategy(): string { - return (this.authenticationSettings["strategy"] as string) ?? "local"; + return (this.authenticationSettings["strategy"] ?? "local") as string; } get authenticationHashAlgorithm(): string { - return (this.authenticationSettings["hash_algorithm"] as string) ?? "sha1"; + return (this.authenticationSettings["hash_algorithm"] ?? "sha1") as string; } get authenticationLegalUsernames(): Array { @@ -242,12 +242,12 @@ class Config { if (typeof this.authenticationSettings["require_passwords"] === "boolean") { return this.authenticationSettings["require_passwords"]; } - const stringValue = (this.authenticationSettings["require_passwords"] as string) ?? "true"; + const stringValue = (this.authenticationSettings["require_passwords"] ?? "true") as string; return stringValue.trim().toLowerCase() !== "false"; } get authenticationSalt(): string { - return (this.authenticationSettings["salt"] as string) ?? "VuDLSaltValue"; + return (this.authenticationSettings["salt"] ?? "VuDLSaltValue") as string; } get databaseInitialUsers(): Record { @@ -255,96 +255,99 @@ class Config { } get samlCertificate(): string { - return (this.authenticationSettings["saml_certificate"] as string) ?? ""; + return (this.authenticationSettings["saml_certificate"] ?? "") as string; } get samlEntryPoint(): string { - return (this.authenticationSettings["saml_entry_point"] as string) ?? ""; + return (this.authenticationSettings["saml_entry_point"] ?? "") as string; } get licenses(): Record { - return this.ini["licenses"] ?? {}; + return (this.ini["licenses"] ?? {}) as Record; } get agentDefaults(): Record { - return this.ini?.["agent"]?.["defaults"] ?? {}; + return ((this.ini["agent"] as ConfigRecord | undefined)?.["defaults"] ?? {}) as Record; } get agentRoles(): Array { - return this.ini?.["agent"]?.["roles"] ?? []; + return ((this.ini["agent"] as ConfigRecord | undefined)?.["roles"] ?? []) as string[]; } get agentTypes(): Array { - return this.ini?.["agent"]?.["types"] ?? []; + return ((this.ini["agent"] as ConfigRecord | undefined)?.["types"] ?? []) as string[]; } get dublinCoreFields(): Record>> { - return this.ini?.["dublin_core"] ?? {}; + return (this.ini["dublin_core"] ?? {}) as Record>>; } get redisConnectionSettings(): Record { - return this.ini?.["queue"]?.["connection"] ?? {}; + return ((this.ini["queue"] as ConfigRecord | undefined)?.["connection"] ?? {}) as Record; } get redisDefaultQueueName(): string { - return this.ini?.["queue"]?.["defaultQueueName"] ?? "vudl"; + return ((this.ini["queue"] as ConfigRecord | undefined)?.["defaultQueueName"] ?? "vudl") as string; } get redisQueueJobMap(): Record { - return this.ini?.["queue"]?.["jobMap"] ?? {}; + return ((this.ini["queue"] as ConfigRecord | undefined)?.["jobMap"] ?? {}) as Record; } get redisLockDuration(): number { - return parseInt(this.ini?.["queue"]?.["lockDuration"] ?? "30000"); + return parseInt(((this.ini["queue"] as ConfigRecord | undefined)?.["lockDuration"] ?? "30000") as string); } get processMetadataDefaults(): Record { - return this.ini?.["process_metadata_defaults"] ?? {}; + return (this.ini["process_metadata_defaults"] ?? {}) as Record; } get toolPresets(): Array> { - return this.ini?.["tool_presets"] ?? []; + return (this.ini["tool_presets"] ?? []) as Array>; } get sharpOptions(): Record { - const pixelLimit = this.ini?.["sharp"]?.["limitInputPixels"] ?? "268402689"; + const pixelLimit = ((this.ini["sharp"] as ConfigRecord | undefined)?.["limitInputPixels"] ?? + "268402689") as string; return { limitInputPixels: parseInt(pixelLimit), }; } get max409Retries(): number { - return this.ini["max_409_retries"] ?? 3; + return parseInt((this.ini["max_409_retries"] ?? "3") as string); } get maxUploadSize(): number { - return this.ini?.["upload"]?.["sizeLimit"] ?? 200 * 1024 * 1024; + // Default to 200MB (1024 * 1024 * 200 = 209715200) + return parseInt(((this.ini["upload"] as ConfigRecord | undefined)?.["sizeLimit"] ?? "209715200") as string); } get notifyMethod(): string { - return this.ini?.["notify"]?.["method"] ?? "ntfy"; + return ((this.ini["notify"] as ConfigRecord | undefined)?.["method"] ?? "ntfy") as string; } get ntfyConfig(): Record { return { - defaultChannel: this.ini?.["notify"]?.["ntfy_defaultChannel"] ?? "vudl-ntfy", + defaultChannel: ((this.ini["notify"] as ConfigRecord | undefined)?.["ntfy_defaultChannel"] ?? + "vudl-ntfy") as string, }; } get indexerLockRetries(): number { - return parseInt(this.ini?.["indexer"]?.["lockRetries"] ?? 60); + return parseInt(((this.ini["indexer"] as ConfigRecord | undefined)?.["lockRetries"] ?? "60") as string); } get indexerLockWaitMs(): number { - return parseInt(this.ini?.["indexer"]?.["lockWaitMs"] ?? 1000); + return parseInt(((this.ini["indexer"] as ConfigRecord | undefined)?.["lockWaitMs"] ?? "1000") as string); } get indexerExceptionRetries(): number { - return parseInt(this.ini?.["indexer"]?.["exceptionRetries"] ?? 10); + return parseInt(((this.ini["indexer"] as ConfigRecord | undefined)?.["exceptionRetries"] ?? "10") as string); } get indexerExceptionWaitMs(): number { - return parseInt(this.ini?.["indexer"]?.["exceptionWaitMs"] ?? 500); + return parseInt(((this.ini["indexer"] as ConfigRecord | undefined)?.["exceptionWaitMs"] ?? "500") as string); } } diff --git a/api/src/models/FedoraDataCollection.test.ts b/api/src/models/FedoraDataCollection.test.ts index 55c107f4..d88ab26d 100644 --- a/api/src/models/FedoraDataCollection.test.ts +++ b/api/src/models/FedoraDataCollection.test.ts @@ -1,7 +1,7 @@ import Config from "./Config"; import FedoraDataCollection from "./FedoraDataCollection"; -let fedoraData; +let fedoraData: FedoraDataCollection; beforeEach(() => { Config.setInstance(new Config({})); fedoraData = FedoraDataCollection.build("foo:123"); diff --git a/api/src/models/FedoraDataCollection.ts b/api/src/models/FedoraDataCollection.ts index b02e4357..d288f20d 100644 --- a/api/src/models/FedoraDataCollection.ts +++ b/api/src/models/FedoraDataCollection.ts @@ -42,10 +42,10 @@ class FedoraDataCollection { metadata: Record> = {}, fedoraDetails: Record> = {}, fedoraDatastreams: Array = [], - fedora: Fedora = null, - extractor: MetadataExtractor = null, - tika: TikaExtractor = null, - config: Config = null, + fedora: Fedora | null = null, + extractor: MetadataExtractor | null = null, + tika: TikaExtractor | null = null, + config: Config | null = null, ): FedoraDataCollection { return new FedoraDataCollection( pid, @@ -91,7 +91,7 @@ class FedoraDataCollection { * Create a flattened list of all PIDs "above" the current one. */ getAllParents(): Array { - const results = []; + const results: Array = []; this.parents.forEach((parent) => { const parentPids = [parent.pid].concat(parent.getAllParents()); parentPids.forEach((pid) => { @@ -116,7 +116,7 @@ class FedoraDataCollection { }; } - async getThumbnailHash(type: string): Promise { + async getThumbnailHash(type: string): Promise { const hashes = (await this.datastreamDetails.getThumbnails()).hasMessageDigest ?? []; for (const hash of hashes) { const parts = hash.split(":"); @@ -132,7 +132,7 @@ class FedoraDataCollection { return fitsData[name] ?? []; } - async getFitsValueAsString(name: string): Promise { + async getFitsValueAsString(name: string): Promise { const fitsData = await this.datastreamDetails.getFitsData(); if (typeof fitsData[name] === "undefined") { return null; @@ -140,12 +140,12 @@ class FedoraDataCollection { return fitsData[name][0] ?? null; } - async getFileSize(): Promise { + async getFileSize(): Promise { return await this.getFitsValueAsString("size"); } async getFullText(): Promise> { - let fullText = []; + let fullText: Array = []; const rawFullText = await this.datastreamDetails.getFullText(); for (const current in rawFullText) { fullText = fullText.concat(rawFullText[current]); @@ -156,11 +156,11 @@ class FedoraDataCollection { }); } - async getImageHeight(): Promise { + async getImageHeight(): Promise { return await this.getFitsValueAsString("imageHeight"); } - async getImageWidth(): Promise { + async getImageWidth(): Promise { return await this.getFitsValueAsString("imageWidth"); } @@ -176,7 +176,7 @@ class FedoraDataCollection { get models(): Array { // Separate identifier from URI prefix return (this.fedoraDetails.hasModel ?? []).map((model) => { - return model.split("/").pop(); + return model.split("/").pop() as string; }); } diff --git a/api/src/models/ImageFile.ts b/api/src/models/ImageFile.ts index a916e1a0..c05fe992 100644 --- a/api/src/models/ImageFile.ts +++ b/api/src/models/ImageFile.ts @@ -1,10 +1,7 @@ import sharp from "sharp"; import path = require("path"); - import { execSync } from "child_process"; - import Config from "./Config"; - import fs = require("fs"); class ImageFile { diff --git a/api/src/models/Job.ts b/api/src/models/Job.ts index 2d3ea81b..9f4df828 100644 --- a/api/src/models/Job.ts +++ b/api/src/models/Job.ts @@ -1,5 +1,7 @@ +/// import { createWriteStream, openSync, closeSync, existsSync as fileExists, statSync } from "fs"; import PDFDocument = require("pdfkit"); + import path = require("path"); import Config from "./Config"; @@ -11,7 +13,7 @@ import QueueManager from "../services/QueueManager"; class Job { dir: string; name: string; - _metadata: JobMetadata = null; + _metadata: JobMetadata | null = null; config: Config; queue: QueueManager; @@ -71,7 +73,7 @@ class Job { protected async getLargeJpegs(): Promise> { const pages = this.metadata.order.pages; - const jpegs = []; + const jpegs: string[] = []; for (const i in pages) { const image = ImageFile.build(this.dir + "/" + pages[i].filename); jpegs[i] = await image.derivative("LARGE"); diff --git a/api/src/models/JobMetadata.ts b/api/src/models/JobMetadata.ts index 698405b6..c8cf8b4f 100644 --- a/api/src/models/JobMetadata.ts +++ b/api/src/models/JobMetadata.ts @@ -16,12 +16,12 @@ interface JobMetadataRaw { class JobMetadata { job: Job; - page: PageRaw; + page: PageRaw | null = null; _filename: string; - _order: PageOrder = null; - _documents: DocumentOrder = null; - _audio: AudioOrder = null; - _video: VideoOrder = null; + _order: PageOrder | null = null; + _documents: DocumentOrder | null = null; + _audio: AudioOrder | null = null; + _video: VideoOrder | null = null; published = false; constructor(job: Job) { @@ -58,7 +58,7 @@ class JobMetadata { return this.job.dir + "/derivatives.lock"; } - get derivativeStatus(): Record { + get derivativeStatus(): { expected: number; processed: number; building: boolean } { const lockfileExists: boolean = fs.existsSync(this.derivativeLockfile); const status = { expected: 0, @@ -121,7 +121,9 @@ class JobMetadata { get ingestInfo(): string { const logfile: string = this.job.dir + "/ingest.log"; - return fs.existsSync(logfile) ? fs.readFileSync(logfile, "utf-8").split("\n").filter(Boolean).pop() : ""; + return fs.existsSync(logfile) + ? (fs.readFileSync(logfile, "utf-8").split("\n").filter(Boolean).pop() ?? "") + : ""; } get order(): PageOrder { diff --git a/api/src/models/PageOrder.ts b/api/src/models/PageOrder.ts index 3f199273..9fc1bde3 100644 --- a/api/src/models/PageOrder.ts +++ b/api/src/models/PageOrder.ts @@ -30,7 +30,7 @@ class PageOrder { return firstPartResults === 0 ? a.localeCompare(b) : firstPartResults; }); const pages = files.map((file) => { - return new Page(path.basename(file), null); + return new Page(path.basename(file), ""); }); return new PageOrder(pages); } diff --git a/api/src/models/VideoFile.ts b/api/src/models/VideoFile.ts index 713116d4..9bc12b3d 100644 --- a/api/src/models/VideoFile.ts +++ b/api/src/models/VideoFile.ts @@ -9,7 +9,7 @@ class VideoFile extends AbstractAVFile { return new VideoFile(filename, dir, Config.getInstance()); } - static fromRaw(raw: Record, config: Config = null): VideoFile { + static fromRaw(raw: Record, config: Config | null = null): VideoFile { return new VideoFile(raw.filename, raw.label, config ?? Config.getInstance()); } diff --git a/api/src/routes/auth.test.ts b/api/src/routes/auth.test.ts index 6890ce9a..31457428 100644 --- a/api/src/routes/auth.test.ts +++ b/api/src/routes/auth.test.ts @@ -1,5 +1,5 @@ -import * as request from "supertest"; -import * as session from "express-session"; +import request from "supertest"; +import session from "express-session"; import { StatusCodes } from "http-status-codes"; import app from "../app"; import { getAuthRouter } from "./auth"; diff --git a/api/src/routes/edit.test.ts b/api/src/routes/edit.test.ts index a67617fd..f8e28d30 100644 --- a/api/src/routes/edit.test.ts +++ b/api/src/routes/edit.test.ts @@ -1,4 +1,4 @@ -import * as request from "supertest"; +import request from "supertest"; import { StatusCodes } from "http-status-codes"; import app from "../app"; import edit from "./edit"; diff --git a/api/src/routes/index.test.ts b/api/src/routes/index.test.ts index 9b2ba0bf..a848f2c0 100644 --- a/api/src/routes/index.test.ts +++ b/api/src/routes/index.test.ts @@ -1,4 +1,4 @@ -import * as request from "supertest"; +import request from "supertest"; import { StatusCodes } from "http-status-codes"; import app from "../app"; import index from "./index"; diff --git a/api/src/routes/messenger.test.ts b/api/src/routes/messenger.test.ts index 819bec02..1939b1fc 100644 --- a/api/src/routes/messenger.test.ts +++ b/api/src/routes/messenger.test.ts @@ -1,4 +1,4 @@ -import * as request from "supertest"; +import request from "supertest"; import { StatusCodes } from "http-status-codes"; import app from "../app"; import messenger from "./messenger"; diff --git a/api/src/server.ts b/api/src/server.ts index b9ccab46..3567b51b 100644 --- a/api/src/server.ts +++ b/api/src/server.ts @@ -2,9 +2,9 @@ * Module dependencies. */ -import * as http from "http"; -import * as passport from "passport"; -import * as session from "express-session"; +import http from "http"; +import passport from "passport"; +import session from "express-session"; import debug from "debug"; import app from "./app"; diff --git a/api/src/services/JobQueue.test.ts b/api/src/services/JobQueue.test.ts index 5531bb9c..28f737f9 100644 --- a/api/src/services/JobQueue.test.ts +++ b/api/src/services/JobQueue.test.ts @@ -5,19 +5,19 @@ const mockDerivative = { run: jest.fn(), }; jest.mock("../jobs/Derivative", () => { - return { default: jest.fn().mockImplementation(() => mockDerivative) }; + return { __esModule: true, default: jest.fn().mockImplementation(() => mockDerivative) }; }); jest.mock("../jobs/GeneratePdf", () => { - return { default: jest.fn() }; + return { __esModule: true, default: jest.fn() }; }); jest.mock("../jobs/Index", () => { - return { default: jest.fn() }; + return { __esModule: true, default: jest.fn() }; }); jest.mock("../jobs/Ingest", () => { - return { default: jest.fn() }; + return { __esModule: true, default: jest.fn() }; }); jest.mock("../jobs/Metadata", () => { - return { default: jest.fn() }; + return { __esModule: true, default: jest.fn() }; }); describe("JobQueue", () => { diff --git a/api/src/services/QueueManager.ts b/api/src/services/QueueManager.ts index 1b8d52b5..501a0e94 100644 --- a/api/src/services/QueueManager.ts +++ b/api/src/services/QueueManager.ts @@ -29,14 +29,14 @@ class QueueManager { }; } - protected getQueue(queueName: string = null): Queue { - return new Queue(queueName ?? this.config.redisDefaultQueueName, this.queueBaseOptions); + protected getQueue(queueName: string = ""): Queue { + return new Queue(queueName || this.config.redisDefaultQueueName, this.queueBaseOptions); } - public getWorker(callback: Processor, queueName: string = null): Worker { + public getWorker(callback: Processor, queueName: string = ""): Worker { const options: WorkerOptions = this.queueBaseOptions; options.lockDuration = this.config.redisLockDuration; - return new Worker(queueName ?? this.config.redisDefaultQueueName, callback, options); + return new Worker(queueName || this.config.redisDefaultQueueName, callback, options); } protected getQueueNameForJob(jobName: string): string { @@ -61,7 +61,7 @@ class QueueManager { return await this.addToQueue("ingest", { dir }); } - public async sendNotification(body: string, channel: string | null = null): Promise { + public async sendNotification(body: string, channel: string = ""): Promise { return await this.addToQueue("notify", { body, channel }); } @@ -69,7 +69,7 @@ class QueueManager { return await this.addToQueue("reindex", { file }); } - public async hasPendingIndexJob(q, queueJob): Promise { + public async hasPendingIndexJob(q: Queue, queueJob: { pid: string; action: string }): Promise { if (this.cache.isEnabled()) { return this.cache.isPidLocked(queueJob.pid, queueJob.action); } diff --git a/api/src/services/Solr.test.ts b/api/src/services/Solr.test.ts index 26183165..5cd2ee3a 100644 --- a/api/src/services/Solr.test.ts +++ b/api/src/services/Solr.test.ts @@ -1,6 +1,6 @@ import Solr from "./Solr"; import SolrCache from "./SolrCache"; -import * as fs from "fs"; +import fs from "fs"; describe("Solr", () => { let solr; diff --git a/api/src/services/SolrCache.test.ts b/api/src/services/SolrCache.test.ts index 89e465c4..46f80581 100644 --- a/api/src/services/SolrCache.test.ts +++ b/api/src/services/SolrCache.test.ts @@ -1,5 +1,5 @@ import { SolrAddDoc, SolrCache } from "./SolrCache"; -import * as fs from "fs"; +import fs from "fs"; import glob = require("glob"); describe("SolrCache", () => { diff --git a/api/src/services/SolrCache.ts b/api/src/services/SolrCache.ts index dff41620..2ecbbeca 100644 --- a/api/src/services/SolrCache.ts +++ b/api/src/services/SolrCache.ts @@ -159,7 +159,7 @@ export class SolrCache { } let document: Array = []; - let currentBatch: { file: string; size: number } = { file: null, size: 0 }; + let currentBatch: { file: string; size: number } = { file: "", size: 0 }; docs.forEach((file) => { const nextObject = this.readSolrAddDocFromFile(file); if (nextObject?.add?.doc === undefined) { @@ -167,7 +167,7 @@ export class SolrCache { return; } document.push(nextObject.add.doc); - if (currentBatch.file === null) { + if (currentBatch.file === "") { currentBatch.file = file.replace(new RegExp("^" + this.cacheDir), targetDir); console.log(`Starting batch ${currentBatch.file}`); } @@ -175,7 +175,7 @@ export class SolrCache { if (currentBatch.size == batchSize) { this.writeFile(currentBatch.file, JSON.stringify(document)); document = []; - currentBatch = { file: null, size: 0 }; + currentBatch = { file: "", size: 0 }; } }); if (currentBatch.size > 0) { diff --git a/api/src/types/pdfkit-augment.d.ts b/api/src/types/pdfkit-augment.d.ts new file mode 100644 index 00000000..a25f4124 --- /dev/null +++ b/api/src/types/pdfkit-augment.d.ts @@ -0,0 +1,15 @@ +// Work around for a flaw in @types/pdfkit, which omits `openImage()` from the type definitions. +// even though pdfkit implements and exports it (lib/mixins/images.js). + +declare namespace PDFKit.Mixins { + interface OpenedImage { + width: number; + height: number; + } + + interface PDFImage { + openImage(src: string | Buffer): OpenedImage; + image(src: OpenedImage, x?: number, y?: number, options?: ImageOption): this; + image(src: OpenedImage, options?: ImageOption): this; + } +} diff --git a/api/tsconfig.json b/api/tsconfig.json index 0d5405de..c1c4c64c 100644 --- a/api/tsconfig.json +++ b/api/tsconfig.json @@ -2,9 +2,13 @@ "compilerOptions": { "sourceMap": true, "allowJs": true, - "target": "es5", + "target": "es2022", + "module": "commonjs", + "strict": false, "outDir": "./dist", - "skipLibCheck": true + "skipLibCheck": true, + "esModuleInterop": true, + "types": ["jest"] }, "include": [ "./src/**/*"