add notice after config
This commit is contained in:
@@ -98,8 +98,28 @@ CREATE TABLE IF NOT EXISTS volume_config_requests (
|
||||
PRIMARY KEY (device_id, request_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS volume_configs (
|
||||
device_id TEXT PRIMARY KEY,
|
||||
file_id TEXT NOT NULL,
|
||||
file_name TEXT NOT NULL,
|
||||
request_id TEXT NOT NULL,
|
||||
updated_at_ms BIGINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS panel_notifications (
|
||||
notification_id TEXT PRIMARY KEY,
|
||||
device_id TEXT NOT NULL,
|
||||
type TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
request_id TEXT,
|
||||
file_id TEXT,
|
||||
created_at_ms BIGINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS licenses_status_device_expiry_idx ON licenses (status, device_id, expiry_at);
|
||||
CREATE INDEX IF NOT EXISTS file_records_folder_idx ON file_records (folder);
|
||||
CREATE INDEX IF NOT EXISTS identification_files_device_upload_idx ON identification_files (device_id, upload_time DESC);
|
||||
CREATE INDEX IF NOT EXISTS identification_files_expires_idx ON identification_files (expires_at) WHERE expires_at IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS identification_feedback_device_idx ON identification_feedback (device_id);
|
||||
CREATE INDEX IF NOT EXISTS identification_feedback_device_idx ON identification_feedback (device_id);
|
||||
CREATE INDEX IF NOT EXISTS panel_notifications_device_created_idx ON panel_notifications (device_id, created_at_ms);
|
||||
+1122
-1037
File diff suppressed because it is too large
Load Diff
@@ -41,6 +41,8 @@ class PostgresStore {
|
||||
const identificationFiles = await queryable.query("SELECT file_id, device_id, file_name, media_type, upload_time, size_bytes, status, processed_at, expires_at FROM identification_files");
|
||||
const feedback = await queryable.query("SELECT device_id, run_id, file_name, status, result, update_time FROM identification_feedback");
|
||||
const requests = await queryable.query("SELECT device_id, request_id, status, created_at_ms, expires_at_ms, config_file_id, config_file_name, uploaded_at_ms, update_time FROM volume_config_requests");
|
||||
const volumeConfigs = await queryable.query("SELECT device_id, file_id, file_name, request_id, updated_at_ms FROM volume_configs");
|
||||
const panelNotifications = await queryable.query("SELECT notification_id, device_id, type, title, message, request_id, file_id, created_at_ms FROM panel_notifications");
|
||||
return {
|
||||
...structuredClone(EMPTY_DATABASE),
|
||||
companies: companies.rows.map((row) => ({ id: row.id, name: row.name, code: row.code, createdAt: asIso(row.created_at) })),
|
||||
@@ -51,7 +53,9 @@ class PostgresStore {
|
||||
panelInbox: panelInbox.rows.map((row) => ({ fileID: row.file_id, deviceId: row.device_id, fileName: row.file_name, mediaType: row.media_type, uploadTime: asIso(row.upload_time) })),
|
||||
identificationFiles: identificationFiles.rows.map((row) => ({ fileID: row.file_id, deviceId: row.device_id, fileName: row.file_name, mediaType: row.media_type, uploadTime: asIso(row.upload_time), size: Number(row.size_bytes), status: row.status, processedAt: row.processed_at && asIso(row.processed_at), expiresAt: row.expires_at && asIso(row.expires_at) })),
|
||||
identificationFeedback: feedback.rows.map((row) => ({ deviceId: row.device_id, runId: row.run_id, fileName: row.file_name, status: row.status, result: row.result, updateTime: asIso(row.update_time) })),
|
||||
volumeConfigRequests: requests.rows.map((row) => ({ deviceId: row.device_id, requestId: row.request_id, status: row.status, createdAtMs: Number(row.created_at_ms), expiresAtMs: Number(row.expires_at_ms), configFileID: row.config_file_id, configFileName: row.config_file_name, uploadedAtMs: row.uploaded_at_ms && Number(row.uploaded_at_ms), updateTime: asIso(row.update_time) }))
|
||||
volumeConfigRequests: requests.rows.map((row) => ({ deviceId: row.device_id, requestId: row.request_id, status: row.status, createdAtMs: Number(row.created_at_ms), expiresAtMs: Number(row.expires_at_ms), configFileID: row.config_file_id, configFileName: row.config_file_name, uploadedAtMs: row.uploaded_at_ms && Number(row.uploaded_at_ms), updateTime: asIso(row.update_time) })),
|
||||
volumeConfigs: volumeConfigs.rows.map((row) => ({ deviceId: row.device_id, fileID: row.file_id, fileName: row.file_name, requestId: row.request_id, updatedAtMs: Number(row.updated_at_ms) })),
|
||||
panelNotifications: panelNotifications.rows.map((row) => ({ notificationId: row.notification_id, deviceId: row.device_id, type: row.type, title: row.title, message: row.message, requestId: row.request_id, fileID: row.file_id, createdAtMs: Number(row.created_at_ms) }))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -74,7 +78,7 @@ class PostgresStore {
|
||||
}
|
||||
|
||||
async writeDatabase(client, database) {
|
||||
await client.query("DELETE FROM volume_config_requests; DELETE FROM identification_feedback; DELETE FROM panel_inbox; DELETE FROM identification_files; DELETE FROM function_configs; DELETE FROM file_records; DELETE FROM licenses; DELETE FROM production_lines; DELETE FROM companies;");
|
||||
await client.query("DELETE FROM panel_notifications; DELETE FROM volume_configs; DELETE FROM volume_config_requests; DELETE FROM identification_feedback; DELETE FROM panel_inbox; DELETE FROM identification_files; DELETE FROM function_configs; DELETE FROM file_records; DELETE FROM licenses; DELETE FROM production_lines; DELETE FROM companies;");
|
||||
for (const item of database.companies) await client.query("INSERT INTO companies (id, name, code, created_at) VALUES ($1, $2, $3, $4)", [item.id, item.name, item.code, item.createdAt]);
|
||||
for (const item of database.productionLines) await client.query("INSERT INTO production_lines (id, company_id, name, code, device_id, created_at, last_seen_at) VALUES ($1, $2, $3, $4, $5, $6, $7)", [item.id, item.companyId, item.name, item.code, item.deviceId, item.createdAt, item.lastSeenAt]);
|
||||
for (const item of database.licenses) await client.query("INSERT INTO licenses (license_id, company_id, production_line_id, device_id, customer, issued_at, expiry_at, features, license, status, created_at, revoked_at, revocation_reason) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)", [item.licenseId, item.companyId, item.productionLineId, item.deviceId, item.customer, item.issuedAt, item.expiryAt, item.features, item.license, item.status, item.createdAt, item.revokedAt, item.revocationReason]);
|
||||
@@ -84,6 +88,8 @@ class PostgresStore {
|
||||
for (const item of database.identificationFiles) await client.query("INSERT INTO identification_files (file_id, device_id, file_name, media_type, upload_time, size_bytes, status, processed_at, expires_at) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)", [item.fileID, item.deviceId, item.fileName, item.mediaType, item.uploadTime, item.size, item.status, item.processedAt, item.expiresAt]);
|
||||
for (const item of database.identificationFeedback) await client.query("INSERT INTO identification_feedback (device_id, run_id, file_name, status, result, update_time) VALUES ($1,$2,$3,$4,$5,$6)", [item.deviceId, item.runId, item.fileName, item.status, item.result, item.updateTime]);
|
||||
for (const item of database.volumeConfigRequests) await client.query("INSERT INTO volume_config_requests (device_id, request_id, status, created_at_ms, expires_at_ms, config_file_id, config_file_name, uploaded_at_ms, update_time) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9)", [item.deviceId, item.requestId, item.status, item.createdAtMs, item.expiresAtMs, item.configFileID, item.configFileName, item.uploadedAtMs, item.updateTime]);
|
||||
for (const item of database.volumeConfigs) await client.query("INSERT INTO volume_configs (device_id, file_id, file_name, request_id, updated_at_ms) VALUES ($1,$2,$3,$4,$5)", [item.deviceId, item.fileID, item.fileName, item.requestId, item.updatedAtMs]);
|
||||
for (const item of database.panelNotifications) await client.query("INSERT INTO panel_notifications (notification_id, device_id, type, title, message, request_id, file_id, created_at_ms) VALUES ($1,$2,$3,$4,$5,$6,$7,$8)", [item.notificationId, item.deviceId, item.type, item.title, item.message, item.requestId, item.fileID, item.createdAtMs]);
|
||||
}
|
||||
|
||||
createId(prefix) {
|
||||
|
||||
+81
-79
@@ -1,80 +1,82 @@
|
||||
const fs = require("node:fs");
|
||||
const path = require("node:path");
|
||||
const { randomUUID } = require("node:crypto");
|
||||
|
||||
const EMPTY_DATABASE = {
|
||||
fileRecords: [],
|
||||
functionConfigs: [],
|
||||
panelInbox: [],
|
||||
identificationFiles: [],
|
||||
identificationFeedback: [],
|
||||
volumeConfigRequests: [],
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 };
|
||||
+751
-692
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user