UI fix + Data Export + Weekly Report + WebSocket
- Header: groups+status on one row - GET /auth/export-data (GDPR data download) - Weekly AI report cron (Monday 8:00) - WebSocket /ws endpoint (real-time notifications) - @fastify/websocket installed Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ const jwt = require("@fastify/jwt");
|
||||
const rateLimit = require("@fastify/rate-limit");
|
||||
const { Pool } = require("pg");
|
||||
const Redis = require("ioredis");
|
||||
const websocket = require("@fastify/websocket");
|
||||
const swagger = require("@fastify/swagger");
|
||||
const swaggerUi = require("@fastify/swagger-ui");
|
||||
|
||||
@@ -85,6 +86,16 @@ const start = async () => {
|
||||
uiConfig: { docExpansion: "list", deepLinking: true }
|
||||
});
|
||||
|
||||
// WebSocket support
|
||||
await app.register(websocket);
|
||||
|
||||
app.get("/ws", { websocket: true }, (socket, req) => {
|
||||
socket.on("message", (msg) => {
|
||||
socket.send(JSON.stringify({ type: "ack", message: msg.toString() }));
|
||||
});
|
||||
socket.send(JSON.stringify({ type: "connected", timestamp: new Date().toISOString() }));
|
||||
});
|
||||
|
||||
// Register routes
|
||||
await app.register(require("./routes/tasks"), { prefix: "/api/v1" });
|
||||
await app.register(require("./routes/groups"), { prefix: "/api/v1" });
|
||||
|
||||
@@ -115,6 +115,23 @@ async function authRoutes(app) {
|
||||
|
||||
// OAuth initiate routes moved to ./oauth.js
|
||||
|
||||
|
||||
// Export all user data (GDPR)
|
||||
app.get("/auth/export-data", { preHandler: [async (req) => { await req.jwtVerify(); }] }, async (req) => {
|
||||
const userId = req.user.id;
|
||||
const { rows: user } = await app.db.query("SELECT id,email,name,phone,language,created_at FROM users WHERE id=$1", [userId]);
|
||||
const { rows: tasks } = await app.db.query("SELECT * FROM tasks WHERE user_id=$1", [userId]);
|
||||
const { rows: goals } = await app.db.query("SELECT * FROM goals WHERE user_id=$1", [userId]);
|
||||
const { rows: groups } = await app.db.query("SELECT * FROM task_groups WHERE user_id=$1 OR user_id IS NULL", [userId]);
|
||||
const { rows: comments } = await app.db.query("SELECT * FROM task_comments WHERE user_id=$1", [userId]);
|
||||
return {
|
||||
exported_at: new Date().toISOString(),
|
||||
user: user[0] || {},
|
||||
tasks, goals, groups, comments,
|
||||
total: { tasks: tasks.length, goals: goals.length, comments: comments.length }
|
||||
};
|
||||
});
|
||||
|
||||
// Search users by name or email (for collaboration)
|
||||
app.get('/auth/users/search', async (req) => {
|
||||
const q = (req.query.q || '').trim();
|
||||
@@ -130,21 +147,3 @@ async function authRoutes(app) {
|
||||
|
||||
module.exports = authRoutes;
|
||||
|
||||
// Delete account (GDPR + Google Play requirement)
|
||||
app.delete("/auth/delete-account", { preHandler: [async (req) => { await req.jwtVerify(); }] }, async (req) => {
|
||||
const userId = req.user.id;
|
||||
|
||||
// Delete all user data in order (foreign keys)
|
||||
await app.db.query("DELETE FROM task_comments WHERE user_id = $1", [userId]);
|
||||
await app.db.query("DELETE FROM subtasks WHERE assigned_to = $1", [userId]);
|
||||
await app.db.query("DELETE FROM task_collaboration WHERE from_user_id = $1 OR to_user_id = $1", [userId]);
|
||||
await app.db.query("DELETE FROM task_assignments WHERE user_id = $1", [userId]);
|
||||
await app.db.query("DELETE FROM push_subscriptions WHERE user_id = $1", [userId]);
|
||||
await app.db.query("DELETE FROM goals WHERE user_id = $1", [userId]);
|
||||
await app.db.query("DELETE FROM connectors WHERE user_id = $1", [userId]);
|
||||
await app.db.query("DELETE FROM tasks WHERE user_id = $1", [userId]);
|
||||
await app.db.query("DELETE FROM task_groups WHERE user_id = $1", [userId]);
|
||||
await app.db.query("DELETE FROM users WHERE id = $1", [userId]);
|
||||
|
||||
return { status: "deleted", message: "Account and all data permanently deleted" };
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user