import chalk from 'chalk' import path from 'path'; import fs from 'fs/promises'; import { pathToFileURL } from 'url'; import parse from "csv-parser" import Node from "../model/Node.js" export default class Database { #nodes; #edges; #labels = {} constructor() { this.getData() } async getData() { const dbData = await fs.readFile(path.join(process.cwd(), 'db/db.json'), 'utf8'); let dbJson; try { dbJson = JSON.parse(dbData); } catch { dbJson = [] } this.#nodes = dbJson["nodes"]; this.#edges = dbJson["edges"]; console.log(chalk.yellow("DB established.")) Object.preventExtensions(this); } // superKey = "nodes" || "edges" async writeData(superKey, key, value) { const dbData = await fs.readFile(path.join(process.cwd(), 'db/db.json'), 'utf8'); let dbJson; try { dbJson = JSON.parse(dbData); } catch { dbJson = [] } dbJson[superKey][key] = value; await fs.writeFile(path.join(process.cwd(), 'db/db.json'), JSON.stringify(dbJson, null, 2), 'utf8') } async getLabelModels() { const labelHandlers = {}; const labelDir = path.join(process.cwd(), 'src/model/labels'); const files = await fs.readdir(labelDir); for (const file of files) { if (!file.endsWith('.js')) continue; const label = path.basename(file, '.js'); const modulePath = path.join(labelDir, file); const module = await import(pathToFileURL(modulePath).href); labelHandlers[label] = module.default; if (!this.#labels[label]) { this.#labels[label] = []; } } return labelHandlers } generateUserID() { let id = this.#labels["User"].length + 1; while (this.get.user(`user-${id}`)) { id++; } return `user-${id}`; // O(1) most of the time } async getAll() { return { nodes: this.#nodes } } async loadCSV(csvString) { if (!csvString || typeof csvString !== 'string') { throw new Error('loadCSVAsync: input must be a non-empty string'); } return new Promise((resolve, reject) => { const records = []; const parser = parse({ columns: true, skip_empty_lines: true, trim: true, relax_column_count: true, bom: true }); parser.on('readable', () => { let row; while ((row = parser.read()) !== null) { records.push(this.cleanRow(row)); } }); parser.on('error', (err) => { reject(err); }); parser.on('end', () => { resolve(records); }); // Feed the CSV string parser.write(csvString); parser.end(); }); } cleanRow(row) { const cleaned = {}; for (let [key, value] of Object.entries(row)) { // 1. Clean header key = key .replace(/ -MyData/g, '') .replace(/[^a-zA-Z0-9]/g, ' ') .trim() .replace(/\s+(.)/g, (_, c) => c.toUpperCase()) .replace(/^(.)/, c => c.toLowerCase()); // 2. Empty → null if (value === '' || value == null) { cleaned[key] = null; continue; } // 3. Type conversion if (key === 'registrationStatus') { cleaned[key] = value === 'True' ? true : value === 'False' ? false : null; } else if (['confidences', 'personId', 'uid', 'stateVoterId'].includes(key)) { const num = parseFloat(value); cleaned[key] = isNaN(num) ? null : num; } else { cleaned[key] = value; } } return cleaned; } }