Alpha Version of Syncing and Desktop/Anroid PWA

This commit is contained in:
Honey committed 2026-02-18 20:07:10 +01:00
1 parent 8cd8133f9e
commit a8779d7be1
9 files changed
+1381 -454

No files matched your search

+66 -2
View File
@@ -37,11 +37,13 @@ const http = require("http");
const PORT = process.env.PORT || 4000;
const JWT_SECRET = process.env.JWT_SECRET || "change-me-in-production-" + crypto.randomBytes(16).toString("hex");
const ENCRYPTION_KEY = process.env.ENCRYPTION_KEY || crypto.randomBytes(32).toString("hex");
const DATA_DIR = path.join(__dirname, "data");
const UPLOAD_DIR = path.join(__dirname, "uploads");
if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR, { recursive: true });
if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true });
/* ─────────── Database ─────────── */
const db = new Database(path.join(__dirname, "inknote.db"));
const db = new Database(path.join(DATA_DIR, "inknote.db"));
db.pragma("journal_mode = WAL");
db.pragma("foreign_keys = ON");
@@ -213,6 +215,43 @@ app.post("/api/auth/login", (req, res) => {
res.json({ token, user: { id: user.id, email: user.email, displayName: user.display_name } });
});
/* ─── Auth profile routes ─── */
app.get("/api/auth/me", authMiddleware, (req, res) => {
const user = db.prepare("SELECT id, email, display_name FROM users WHERE id = ?").get(req.userId);
if (!user) return res.status(404).json({ error: "User not found" });
res.json({ id: user.id, email: user.email, displayName: user.display_name });
});
app.put("/api/auth/profile", authMiddleware, (req, res) => {
const { displayName } = req.body;
if (!displayName || !displayName.trim()) return res.status(400).json({ error: "Display name required" });
db.prepare("UPDATE users SET display_name = ?, updated_at = datetime('now') WHERE id = ?").run(displayName.trim(), req.userId);
const user = db.prepare("SELECT id, email, display_name FROM users WHERE id = ?").get(req.userId);
res.json({ id: user.id, email: user.email, displayName: user.display_name });
});
app.put("/api/auth/email", authMiddleware, (req, res) => {
const { newEmail, password } = req.body;
if (!newEmail || !password) return res.status(400).json({ error: "Email and password required" });
const user = db.prepare("SELECT * FROM users WHERE id = ?").get(req.userId);
if (!bcrypt.compareSync(password, user.password_hash)) return res.status(401).json({ error: "Wrong password" });
const exists = db.prepare("SELECT id FROM users WHERE email = ? AND id != ?").get(newEmail, req.userId);
if (exists) return res.status(409).json({ error: "Email already in use" });
db.prepare("UPDATE users SET email = ?, updated_at = datetime('now') WHERE id = ?").run(newEmail, req.userId);
res.json({ id: user.id, email: newEmail, displayName: user.display_name });
});
app.put("/api/auth/password", authMiddleware, (req, res) => {
const { currentPassword, newPassword } = req.body;
if (!currentPassword || !newPassword) return res.status(400).json({ error: "Both passwords required" });
if (newPassword.length < 6) return res.status(400).json({ error: "Password must be at least 6 characters" });
const user = db.prepare("SELECT * FROM users WHERE id = ?").get(req.userId);
if (!bcrypt.compareSync(currentPassword, user.password_hash)) return res.status(401).json({ error: "Current password is wrong" });
const hash = bcrypt.hashSync(newPassword, 12);
db.prepare("UPDATE users SET password_hash = ?, updated_at = datetime('now') WHERE id = ?").run(hash, req.userId);
res.json({ success: true });
});
/* ─── Notebook routes ─── */
app.get("/api/notebooks", authMiddleware, (req, res) => {
const notebooks = db.prepare("SELECT * FROM notebooks WHERE user_id = ? AND deleted_at IS NULL ORDER BY sort_order").all(req.userId);
@@ -256,6 +295,11 @@ app.get("/api/notes", authMiddleware, (req, res) => {
res.json(notes);
});
app.get("/api/notes/trash", authMiddleware, (req, res) => {
const notes = db.prepare("SELECT id, user_id, notebook_id, type, title, starred, tags, sort_order, created_at, updated_at, deleted_at, version FROM notes WHERE user_id = ? AND deleted_at IS NOT NULL ORDER BY deleted_at DESC").all(req.userId);
res.json(notes);
});
app.get("/api/notes/:id", authMiddleware, (req, res) => {
const note = db.prepare("SELECT * FROM notes WHERE id = ? AND user_id = ?").get(req.params.id, req.userId);
if (!note) return res.status(404).json({ error: "Not found" });
@@ -328,12 +372,32 @@ app.put("/api/notes/:id", authMiddleware, (req, res) => {
res.json({ success: true, vectorClock: newClock });
});
/* ─── Trash batch endpoint (MUST be before :id routes) ─── */
app.delete("/api/notes/trash/empty", authMiddleware, (req, res) => {
db.prepare("DELETE FROM notes WHERE user_id = ? AND deleted_at IS NOT NULL").run(req.userId);
res.json({ success: true });
});
app.delete("/api/notes/:id", authMiddleware, (req, res) => {
db.prepare("UPDATE notes SET deleted_at = datetime('now') WHERE id = ? AND user_id = ?").run(req.params.id, req.userId);
broadcastToUser(req.userId, { type: "note_deleted", data: { id: req.params.id } }, req.deviceId);
res.json({ success: true });
});
/* ─── Trash endpoints ─── */
app.put("/api/notes/:id/restore", authMiddleware, (req, res) => {
const note = db.prepare("SELECT id FROM notes WHERE id = ? AND user_id = ?").get(req.params.id, req.userId);
if (!note) return res.status(404).json({ error: "Not found" });
db.prepare("UPDATE notes SET deleted_at = NULL, updated_at = datetime('now') WHERE id = ?").run(req.params.id);
broadcastToUser(req.userId, { type: "note_restored", data: { id: req.params.id } }, req.deviceId);
res.json({ success: true });
});
app.delete("/api/notes/:id/permanent", authMiddleware, (req, res) => {
db.prepare("DELETE FROM notes WHERE id = ? AND user_id = ?").run(req.params.id, req.userId);
res.json({ success: true });
});
/* ─── Drawing data route ─── */
app.put("/api/notes/:id/drawing", authMiddleware, (req, res) => {
const { drawingData } = req.body;
@@ -472,5 +536,5 @@ server.listen(PORT, () => {
console.log(`\n ✏️ InkNote Server running on http://localhost:${PORT}`);
console.log(` 📡 WebSocket available at ws://localhost:${PORT}/ws`);
console.log(` 🔒 JWT Secret: ${JWT_SECRET.slice(0, 12)}...`);
console.log(` 💾 Database: ${path.join(__dirname, "inknote.db")}\n`);
console.log(` 💾 Database: ${path.join(DATA_DIR, "inknote.db")}\n`);
});