mirror of
https://github.com/drawdb-io/drawdb.git
synced 2025-05-24 18:39:12 +00:00
Merge pull request #1 from yuanwei92/dev
[SPRINT-9146] AZL Nix - New DrawDB language option
This commit is contained in:
commit
26461ceaa4
9
package-lock.json
generated
9
package-lock.json
generated
@ -19,6 +19,7 @@
|
||||
"axios": "^1.7.4",
|
||||
"classnames": "^2.5.1",
|
||||
"dexie": "^3.2.4",
|
||||
"dexie-observable": "^4.0.1-beta.13",
|
||||
"dexie-react-hooks": "^1.1.7",
|
||||
"file-saver": "^2.0.5",
|
||||
"framer-motion": "^10.18.0",
|
||||
@ -3053,6 +3054,14 @@
|
||||
"node": ">=6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dexie-observable": {
|
||||
"version": "4.0.1-beta.13",
|
||||
"resolved": "https://registry.npmjs.org/dexie-observable/-/dexie-observable-4.0.1-beta.13.tgz",
|
||||
"integrity": "sha512-axmgPk7yjoPwj+0DdQIE5s1MBXi+6XcIFIjBKdQAmSGpsLQSth/LHvMOQ3q3Wj6pwIE5hqIxg2GL75sVqQbhEw==",
|
||||
"peerDependencies": {
|
||||
"dexie": "^3.0.2 || ^4.0.1-alpha.5"
|
||||
}
|
||||
},
|
||||
"node_modules/dexie-react-hooks": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/dexie-react-hooks/-/dexie-react-hooks-1.1.7.tgz",
|
||||
|
@ -21,6 +21,7 @@
|
||||
"axios": "^1.7.4",
|
||||
"classnames": "^2.5.1",
|
||||
"dexie": "^3.2.4",
|
||||
"dexie-observable": "^4.0.1-beta.13",
|
||||
"dexie-react-hooks": "^1.1.7",
|
||||
"file-saver": "^2.0.5",
|
||||
"framer-motion": "^10.18.0",
|
||||
|
17
server/README.md
Normal file
17
server/README.md
Normal file
@ -0,0 +1,17 @@
|
||||
# DrawDB Node.js Wrapper
|
||||
|
||||
This is a Node.js application that serves static files for the DrawDB frontend and provides an API to handle file operations for `.ddb` files.
|
||||
|
||||
## Environment variables:
|
||||
- `DRAWDB_FILE_DIR`: Directory where `.ddb` files will be stored (default: `/usercode`).
|
||||
- `DRAWDB_HOME`: Path to the DrawDB frontend static files (default: `../dist`).
|
||||
- `DRAWDB_PORT`: Port number for the app (default: `8080`).
|
||||
|
||||
## Run dev
|
||||
```bash
|
||||
export DRAWDB_FILE_DIR=/some-dir-to-write-ddb-files
|
||||
cd server
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The server will be running at `http://localhost:8080` (or the port specified by `DRAWDB_PORT`).
|
90
server/index.js
Normal file
90
server/index.js
Normal file
@ -0,0 +1,90 @@
|
||||
const express = require('express');
|
||||
const cors = require('cors')
|
||||
const path = require('path');
|
||||
const app = express();
|
||||
const fs = require('node:fs');
|
||||
|
||||
const DRAWDB_FILE_DIR = process.env.DRAWDB_FILE_DIR || '/usercode'
|
||||
const DRAWDB_HOME = process.env.DRAWDB_HOME || path.join(__dirname, '../dist');
|
||||
const DRAWDB_PORT = process.env.DRAWDB_PORT || 8080;
|
||||
|
||||
app.use(cors())
|
||||
app.use(express.json())
|
||||
|
||||
// Serve the static files from the DrawDB app
|
||||
app.use(express.static(DRAWDB_HOME));
|
||||
|
||||
// Endpoints
|
||||
app.post('/api/usercode-files/', (req, res) => {
|
||||
const { filename, content } = req.body;
|
||||
if (!filename || !content) {
|
||||
return res.status(400).send('Filename and content are required');
|
||||
}
|
||||
|
||||
const filePath = path.join(DRAWDB_FILE_DIR, filename);
|
||||
fs.writeFile(filePath, JSON.stringify(content), 'utf8', (err) => {
|
||||
if (err) {
|
||||
return res.status(500).send('Error writing ddb file');
|
||||
}
|
||||
res.send('File written successfully');
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api/usercode-files/:filename', (req, res) => {
|
||||
const filename = req.params.filename;
|
||||
const filePath = path.join(DRAWDB_FILE_DIR, filename);
|
||||
|
||||
fs.readFile(filePath, 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
return res.status(404).send('File not found');
|
||||
}
|
||||
return res.status(500).send('Error reading file');
|
||||
}
|
||||
res.send(data);
|
||||
});
|
||||
});
|
||||
|
||||
app.delete('/api/usercode-files/:filename', (req, res) => {
|
||||
const filename = req.params.filename;
|
||||
if (!filename) {
|
||||
return res.status(400).send('Filename is required');
|
||||
}
|
||||
|
||||
const filePath = path.join(DRAWDB_FILE_DIR, filename);
|
||||
fs.access(filePath, fs.constants.F_OK, (err) => {
|
||||
if (err) {
|
||||
return res.send('File does not exists.');
|
||||
}
|
||||
fs.unlink(filePath, (err) => {
|
||||
if (err) {
|
||||
return res.status(500).send('Error deleting file');
|
||||
}
|
||||
res.send('File deleted successfully');
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
app.get('/api/diagrams', (_req, res) => {
|
||||
const dirPath = DRAWDB_FILE_DIR;
|
||||
fs.readdir(dirPath, (err, files) => {
|
||||
if (err) {
|
||||
return res.status(500).send('Unable to scan directory');
|
||||
}
|
||||
const ddbFiles = files.filter(file => path.extname(file) === '.ddb');
|
||||
const fileContents = ddbFiles.map(file => {
|
||||
const filePath = path.join(dirPath, file);
|
||||
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
||||
return JSON.parse(fileContent);
|
||||
});
|
||||
res.json(fileContents);
|
||||
});
|
||||
});
|
||||
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(DRAWDB_HOME, 'index.html'));
|
||||
});
|
||||
|
||||
app.listen(DRAWDB_PORT, () => {
|
||||
console.log(`DrawDB is running on port ${DRAWDB_PORT}`);
|
||||
});
|
1997
server/package-lock.json
generated
Normal file
1997
server/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
23
server/package.json
Normal file
23
server/package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "drawdb-wrapper",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node index.js",
|
||||
"start:prod": "node server.js",
|
||||
"build": "webpack"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack": "^5.94.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
}
|
||||
}
|
2
server/server.js
Normal file
2
server/server.js
Normal file
File diff suppressed because one or more lines are too long
12
server/webpack.config.js
Normal file
12
server/webpack.config.js
Normal file
@ -0,0 +1,12 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
mode: 'production',
|
||||
entry: './index.js',
|
||||
output: {
|
||||
path: path.join(__dirname),
|
||||
publicPath: '/',
|
||||
filename: 'server.js',
|
||||
},
|
||||
target: 'node',
|
||||
};
|
@ -1,9 +1,12 @@
|
||||
import Dexie from "dexie";
|
||||
import 'dexie-observable';
|
||||
import { templateSeeds } from "./seeds";
|
||||
import { diagramToDdbFile, ddbFileToDiagram, writeDdbFiles, deleteDdbFiles } from "./usercode"
|
||||
import { ddbDiagramIsValid } from "../utils/validateSchema";
|
||||
|
||||
export const db = new Dexie("drawDB");
|
||||
|
||||
db.version(6).stores({
|
||||
db.version(7).stores({
|
||||
diagrams: "++id, lastModified, loadedFromGistId",
|
||||
templates: "++id, custom",
|
||||
});
|
||||
@ -11,3 +14,64 @@ db.version(6).stores({
|
||||
db.on("populate", (transaction) => {
|
||||
transaction.templates.bulkAdd(templateSeeds).catch((e) => console.log(e));
|
||||
});
|
||||
|
||||
db.on("ready", async (db) => {
|
||||
// Use ddb files as source for diagrams
|
||||
const diagramsRes = await fetch('/api/diagrams', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
})
|
||||
const ddbFiles = await diagramsRes.json();
|
||||
const validDdbFiles = ddbFiles.filter(ddbFile => ddbDiagramIsValid(ddbFile));
|
||||
const diagrams = validDdbFiles.map(f => ddbFileToDiagram(f))
|
||||
return db.transaction('rw', db.diagrams, async () => {
|
||||
await db.diagrams.clear();
|
||||
await db.diagrams.bulkAdd(diagrams);
|
||||
})
|
||||
})
|
||||
|
||||
const debounce = (func, delay) => {
|
||||
let timer;
|
||||
return function (...args) {
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout(() => {
|
||||
func.apply(this, args);
|
||||
}, delay);
|
||||
};
|
||||
}
|
||||
|
||||
const debouncedChangesHandler = debounce(async (changes) => {
|
||||
handleDiagramChanges(changes.filter(c => c.table === "diagrams"))
|
||||
}, 1500);
|
||||
|
||||
const handleDiagramChanges = (diagramChanges) => {
|
||||
diagramChanges.forEach(({ type, obj, oldObj }) => {
|
||||
const ddbFile = diagramToDdbFile(obj);
|
||||
|
||||
switch (type) {
|
||||
case 1:
|
||||
writeDdbFiles([ddbFile]);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (oldObj && obj.id === oldObj.id && obj.name !== oldObj.name) {
|
||||
const oldDdbFile = diagramToDdbFile(oldObj);
|
||||
deleteDdbFiles([oldDdbFile]);
|
||||
}
|
||||
writeDdbFiles([ddbFile]);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
deleteDdbFiles([ddbFile]);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
db.on('changes', debouncedChangesHandler)
|
||||
|
81
src/data/usercode.js
Normal file
81
src/data/usercode.js
Normal file
@ -0,0 +1,81 @@
|
||||
import { databases } from "./databases";
|
||||
import { DB } from "./constants";
|
||||
|
||||
export function diagramToDdbFile(diagram) {
|
||||
const {
|
||||
name,
|
||||
lastModified,
|
||||
tables,
|
||||
references,
|
||||
notes,
|
||||
areas,
|
||||
database,
|
||||
types,
|
||||
enums
|
||||
} = diagram;
|
||||
|
||||
let date = lastModified ?? new Date();
|
||||
if (typeof lastModified === "number" || typeof lastModified === "string") {
|
||||
date = new Date(lastModified)
|
||||
}
|
||||
|
||||
return {
|
||||
author: "Unnamed",
|
||||
title: name,
|
||||
date: date.toISOString(),
|
||||
tables: tables,
|
||||
relationships: references,
|
||||
notes: notes,
|
||||
subjectAreas: areas,
|
||||
database: database,
|
||||
...(databases[database].hasTypes && { types: types }),
|
||||
...(databases[database].hasEnums && { enums: enums }),
|
||||
}
|
||||
}
|
||||
|
||||
export function ddbFileToDiagram(ddb) {
|
||||
const {
|
||||
database = DB.GENERIC,
|
||||
title = "Untitled Diagram",
|
||||
tables,
|
||||
date,
|
||||
relationships,
|
||||
subjectAreas,
|
||||
notes,
|
||||
enums,
|
||||
types
|
||||
} = ddb;
|
||||
|
||||
return {
|
||||
database: database,
|
||||
name: title,
|
||||
lastModified: date ? new Date(date) : new Date(),
|
||||
tables: tables,
|
||||
references: relationships,
|
||||
notes: notes,
|
||||
areas: subjectAreas,
|
||||
...(databases[database].hasEnums && { enums: enums }),
|
||||
...(databases[database].hasTypes && { types: types }),
|
||||
};
|
||||
}
|
||||
|
||||
export function writeDdbFiles(ddbFiles) {
|
||||
const writePromises = ddbFiles.map(ddbFile => {
|
||||
return fetch('/api/usercode-files', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ filename: `${ddbFile.title}.ddb`, content: ddbFile })
|
||||
});
|
||||
})
|
||||
return Promise.all(writePromises);
|
||||
}
|
||||
|
||||
export function deleteDdbFiles(ddbFiles) {
|
||||
const deletePromises = ddbFiles.map(ddbFile => {
|
||||
return fetch(`/api/usercode-files/${ddbFile.title}.ddb`, { method: 'DELETE' });
|
||||
});
|
||||
return Promise.all(deletePromises);
|
||||
}
|
Loading…
Reference in New Issue
Block a user