pwa ops
This commit is contained in:
137
server/index.js
137
server/index.js
@@ -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}`);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user