Add a new sync service and frontend integration to allow syncing favorites across devices using a token. - Configure `sync-service` in docker-compose.yml on port 7482 - Add sync token input and manual sync button to SideDrawer - Implement auto-sync logic to persist favorites to the KV store - Add logic to merge cloud favorites with local data on initialization
85 lines
2.8 KiB
JavaScript
85 lines
2.8 KiB
JavaScript
const http = require('http');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const url = require('url');
|
|
|
|
const DATA_DIR = process.env.DATA_DIR || './data';
|
|
const PORT = process.env.PORT || 3001;
|
|
|
|
// Ensure data directory exists
|
|
if (!fs.existsSync(DATA_DIR)) {
|
|
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
}
|
|
|
|
const server = http.createServer((req, res) => {
|
|
// CORS headers
|
|
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
|
|
if (req.method === 'OPTIONS') {
|
|
res.writeHead(204);
|
|
res.end();
|
|
return;
|
|
}
|
|
|
|
const parsedUrl = url.parse(req.url, true);
|
|
const pathname = parsedUrl.pathname;
|
|
|
|
// Path format: /kv/:key?token=...
|
|
const match = pathname.match(/^\/kv\/([a-zA-Z0-9_-]+)$/);
|
|
|
|
if (!match) {
|
|
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ error: 'Invalid path' }));
|
|
return;
|
|
}
|
|
|
|
const key = match[1];
|
|
const token = parsedUrl.query.token;
|
|
|
|
if (!token) {
|
|
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ error: 'Token required' }));
|
|
return;
|
|
}
|
|
|
|
// Simple file-based storage: data/{token}_{key}.json
|
|
// Using token in filename ensures isolation between users
|
|
const safeToken = token.replace(/[^a-zA-Z0-9]/g, '');
|
|
const filePath = path.join(DATA_DIR, `${safeToken}_${key}.json`);
|
|
|
|
if (req.method === 'GET') {
|
|
if (fs.existsSync(filePath)) {
|
|
const data = fs.readFileSync(filePath);
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(data);
|
|
} else {
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify(null)); // Key not found returns null
|
|
}
|
|
} else if (req.method === 'POST') {
|
|
let body = '';
|
|
req.on('data', chunk => body += chunk.toString());
|
|
req.on('end', () => {
|
|
try {
|
|
// Validate JSON
|
|
JSON.parse(body);
|
|
fs.writeFileSync(filePath, body);
|
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ success: true }));
|
|
} catch (e) {
|
|
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ error: 'Invalid JSON' }));
|
|
}
|
|
});
|
|
} else {
|
|
res.writeHead(405);
|
|
res.end();
|
|
}
|
|
});
|
|
|
|
server.listen(PORT, () => {
|
|
console.log(`Sync Server running on port ${PORT}`);
|
|
console.log(`Data directory: ${DATA_DIR}`);
|
|
}); |