Alpha Version of Syncing and Desktop/Anroid PWA
This commit is contained in:
1 parent
8cd8133f9e
commit
a8779d7be1
9 files changed
+1381
-454
No files matched your search
+66
-2
@@ -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`);
|
||||
});
|
||||
Reference in new issue
Block a user