82 lines
2.5 KiB
JavaScript
82 lines
2.5 KiB
JavaScript
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const { randomUUID } = require("node:crypto");
|
|
|
|
const EMPTY_DATABASE = {
|
|
fileRecords: [],
|
|
functionConfigs: [],
|
|
panelInbox: [],
|
|
identificationFiles: [],
|
|
identificationFeedback: [],
|
|
volumeConfigRequests: [],
|
|
volumeConfigs: [],
|
|
panelNotifications: [],
|
|
userInfo: [],
|
|
companies: [],
|
|
productionLines: [],
|
|
licenses: []
|
|
};
|
|
|
|
class JsonStore {
|
|
constructor(dataDirectory) {
|
|
this.dataDirectory = dataDirectory;
|
|
this.filesDirectory = path.join(dataDirectory, "files");
|
|
this.modelsDirectory = path.join(dataDirectory, "models");
|
|
this.databasePath = path.join(dataDirectory, "database.json");
|
|
this.writeQueue = Promise.resolve();
|
|
}
|
|
|
|
async initialize() {
|
|
await fs.promises.mkdir(this.filesDirectory, { recursive: true });
|
|
try {
|
|
await fs.promises.access(this.databasePath);
|
|
} catch {
|
|
await this.writeDatabase(structuredClone(EMPTY_DATABASE));
|
|
}
|
|
}
|
|
|
|
async read() {
|
|
const content = await fs.promises.readFile(this.databasePath, "utf8");
|
|
return { ...structuredClone(EMPTY_DATABASE), ...JSON.parse(content) };
|
|
}
|
|
|
|
async update(mutator) {
|
|
const operation = this.writeQueue.then(async () => {
|
|
const database = await this.read();
|
|
const result = await mutator(database);
|
|
await this.writeDatabase(database);
|
|
return result;
|
|
});
|
|
this.writeQueue = operation.catch(() => undefined);
|
|
return operation;
|
|
}
|
|
|
|
async writeDatabase(database) {
|
|
await fs.promises.mkdir(this.dataDirectory, { recursive: true });
|
|
const temporaryPath = `${this.databasePath}.${process.pid}.tmp`;
|
|
await fs.promises.writeFile(
|
|
temporaryPath,
|
|
`${JSON.stringify(database, null, 2)}\n`,
|
|
"utf8"
|
|
);
|
|
await fs.promises.rename(temporaryPath, this.databasePath);
|
|
}
|
|
|
|
createId(prefix) {
|
|
return `${prefix}_${randomUUID()}`;
|
|
}
|
|
|
|
resolveStoredFile(fileID) {
|
|
if (typeof fileID !== "string") return null;
|
|
const isModel = fileID.startsWith("model://");
|
|
if (!isModel && !fileID.startsWith("local://")) return null;
|
|
const rootDirectory = isModel ? this.modelsDirectory : this.filesDirectory;
|
|
const relativePath = fileID.slice(isModel ? "model://".length : "local://".length).replace(/\\/g, "/");
|
|
const absolutePath = path.resolve(rootDirectory, relativePath);
|
|
const relativeToRoot = path.relative(rootDirectory, absolutePath);
|
|
if (relativeToRoot.startsWith("..") || path.isAbsolute(relativeToRoot)) return null;
|
|
return absolutePath;
|
|
}
|
|
}
|
|
|
|
module.exports = { EMPTY_DATABASE, JsonStore }; |