This commit is contained in:
2026-06-06 09:49:43 +05:30
parent cb81d476a8
commit 6e5ce9eb86
10 changed files with 496 additions and 15 deletions

View File

@@ -129,6 +129,143 @@ app.post('/api/verify-token', authenticateToken, (req, res) => {
res.json({ valid: true, user: req.user });
});
// Sync data (beans and brew logs)
app.post('/api/sync', authenticateToken, async (req, res) => {
const userId = req.user.id;
const { lastSyncedAt, beans = [], brewLogs = [] } = req.body;
const serverTime = new Date().toISOString();
const client = await pool.connect();
try {
await client.query('BEGIN');
// 1. Process incoming beans
for (const bean of beans) {
await client.query(`
INSERT INTO beans (id, user_id, name, roastery, tasting_notes, roast_type, roast_date, image, updated_at, is_deleted)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
ON CONFLICT (id) DO UPDATE SET
name = EXCLUDED.name,
roastery = EXCLUDED.roastery,
tasting_notes = EXCLUDED.tasting_notes,
roast_type = EXCLUDED.roast_type,
roast_date = EXCLUDED.roast_date,
image = EXCLUDED.image,
updated_at = EXCLUDED.updated_at,
is_deleted = EXCLUDED.is_deleted
WHERE EXCLUDED.updated_at > beans.updated_at OR beans.user_id IS NULL
`, [
bean.id,
userId,
bean.name || '',
bean.roastery || '',
bean.tastingNotes || '',
bean.roastType || '',
bean.roastDate || '',
bean.image || '',
bean.updatedAt ? new Date(bean.updatedAt) : new Date(),
bean.isDeleted || false
]);
}
// 2. Process incoming brew logs
for (const log of brewLogs) {
await client.query(`
INSERT INTO brew_logs (id, user_id, bean_id, method, grind, water_temp, ratio, yield, time, notes, rating, created_at, updated_at, is_deleted)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
ON CONFLICT (id) DO UPDATE SET
bean_id = EXCLUDED.bean_id,
method = EXCLUDED.method,
grind = EXCLUDED.grind,
water_temp = EXCLUDED.water_temp,
ratio = EXCLUDED.ratio,
yield = EXCLUDED.yield,
time = EXCLUDED.time,
notes = EXCLUDED.notes,
rating = EXCLUDED.rating,
created_at = EXCLUDED.created_at,
updated_at = EXCLUDED.updated_at,
is_deleted = EXCLUDED.is_deleted
WHERE EXCLUDED.updated_at > brew_logs.updated_at OR brew_logs.user_id IS NULL
`, [
log.id,
userId,
log.beanId,
log.method || '',
log.grind || '',
log.waterTemp || '',
log.ratio || '',
log.yield || '',
log.time || '',
log.notes || '',
log.rating || 0,
log.createdAt ? BigInt(log.createdAt) : BigInt(Date.now()),
log.updatedAt ? new Date(log.updatedAt) : new Date(),
log.isDeleted || false
]);
}
// 3. Fetch server changes since lastSyncedAt
let beansQuery = `SELECT * FROM beans WHERE user_id = $1`;
let logsQuery = `SELECT * FROM brew_logs WHERE user_id = $1`;
const params = [userId];
if (lastSyncedAt) {
beansQuery += ` AND updated_at > $2`;
logsQuery += ` AND updated_at > $2`;
params.push(new Date(lastSyncedAt));
}
const serverBeans = await client.query(beansQuery, params);
const serverLogs = await client.query(logsQuery, params);
await client.query('COMMIT');
// Map DB fields back to camelCase expected by the frontend
const mappedBeans = serverBeans.rows.map(b => ({
id: b.id,
name: b.name,
roastery: b.roastery,
tastingNotes: b.tasting_notes,
roastType: b.roast_type,
roastDate: b.roast_date,
image: b.image,
updatedAt: b.updated_at.toISOString(),
isDeleted: b.is_deleted
}));
const mappedLogs = serverLogs.rows.map(l => ({
id: l.id,
beanId: l.bean_id,
method: l.method,
grind: l.grind,
waterTemp: l.water_temp,
ratio: l.ratio,
yield: l.yield,
time: l.time,
notes: l.notes,
rating: l.rating,
createdAt: Number(l.created_at),
updatedAt: l.updated_at.toISOString(),
isDeleted: l.is_deleted
}));
res.json({
serverTime,
beans: mappedBeans,
brewLogs: mappedLogs
});
} catch (err) {
await client.query('ROLLBACK');
console.error('Sync failed', err);
res.status(500).json({ error: 'Sync failed' });
} finally {
client.release();
}
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});