Compare commits

..

No commits in common. "51be362dc49ae062db1f267bfc0a9baaf403e489" and "f03b758e183e62418a330980c8352c485f04c2c0" have entirely different histories.

7 changed files with 222 additions and 455 deletions

View file

@ -9,144 +9,75 @@ const router = Router();
const hashCode = (code) => createHash('sha256').update(code).digest('hex'); const hashCode = (code) => createHash('sha256').update(code).digest('hex');
// Schema for change request validation
const changeRequestSchema = z.object({
newAddress: z.string().min(10).max(500)
});
// Schema for verification request validation
const verifyRequestSchema = z.object({
requestId: z.number().int().positive(),
code: z.string().regex(/^\d{6}$/)
});
router.post('/change-request', requireAuth, async (req, res) => { router.post('/change-request', requireAuth, async (req, res) => {
try { const parsed = z.object({ newAddress: z.string().min(10) }).safeParse(req.body);
const parsed = changeRequestSchema.safeParse(req.body); if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
if (!parsed.success) {
return res.status(400).json({
error: 'Invalid input data',
details: parsed.error.flatten()
});
}
// Check if user already has an address const verificationCode = String(randomInt(100000, 999999));
try { const verificationCodeHash = hashCode(verificationCode);
const [existingRows] = await pool.query(
`SELECT id FROM addresses WHERE user_id = ? LIMIT 1`,
[req.user.userId]
);
if (existingRows.length === 0) { const [result] = await pool.query(
return res.status(400).json({ `INSERT INTO address_change_requests (user_id, new_address_encrypted, verification_code_hash)
error: 'User must have an existing address to request a change' VALUES (?, ?, ?)`,
}); [req.user.userId, encryptText(parsed.data.newAddress), verificationCodeHash]
} );
} catch (err) {
console.error('Error checking existing address:', err);
return res.status(500).json({
error: 'Internal server error while checking existing address'
});
}
const verificationCode = String(randomInt(100000, 999999)); res.status(201).json({
const verificationCodeHash = hashCode(verificationCode); requestId: result.insertId,
postalDispatch: 'pending_letter',
try { note: 'Verification code generated for postal letter dispatch.',
const [result] = await pool.query( verificationCode
`INSERT INTO address_change_requests (user_id, new_address_encrypted, verification_code_hash) });
VALUES (?, ?, ?)`,
[req.user.userId, encryptText(parsed.data.newAddress), verificationCodeHash]
);
res.status(201).json({
requestId: result.insertId,
postalDispatch: 'pending_letter',
note: 'Verification code generated for postal letter dispatch.',
verificationCode
});
} catch (err) {
console.error('Error in address change request:', err);
return res.status(500).json({
error: 'Internal server error while processing address change request'
});
}
} catch (err) {
console.error('Unexpected error in change-request route:', err);
return res.status(500).json({
error: 'Unexpected internal server error'
});
}
}); });
router.post('/verify', requireAuth, async (req, res) => { router.post('/verify', requireAuth, async (req, res) => {
try { const parsed = z.object({ requestId: z.number().int().positive(), code: z.string().regex(/^\d{6}$/) }).safeParse(req.body);
const parsed = verifyRequestSchema.safeParse(req.body); if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
if (!parsed.success) {
return res.status(400).json({
error: 'Invalid input data',
details: parsed.error.flatten()
});
}
const { requestId, code } = parsed.data; const { requestId, code } = parsed.data;
try { const [rows] = await pool.query(
const [rows] = await pool.query( `SELECT id, user_id, new_address_encrypted, verification_code_hash, status
`SELECT id, user_id, new_address_encrypted, verification_code_hash, status FROM address_change_requests
FROM address_change_requests WHERE id = ? LIMIT 1`,
WHERE id = ? LIMIT 1`, [requestId]
[requestId] );
);
const request = rows[0]; const request = rows[0];
if (!request) return res.status(404).json({ error: 'Request not found' }); if (!request) return res.status(404).json({ error: 'Request not found' });
if (request.user_id !== req.user.userId) return res.status(403).json({ error: 'Forbidden' }); if (request.user_id !== req.user.userId) return res.status(403).json({ error: 'Forbidden' });
if (request.status !== 'pending_letter') return res.status(409).json({ error: 'Request not pending' }); if (request.status !== 'pending_letter') return res.status(409).json({ error: 'Request not pending' });
if (hashCode(code) !== request.verification_code_hash) { if (hashCode(code) !== request.verification_code_hash) {
return res.status(400).json({ error: 'Invalid verification code' }); return res.status(400).json({ error: 'Invalid verification code' });
}
const conn = await pool.getConnection();
try {
await conn.beginTransaction();
await conn.query(
`UPDATE address_change_requests
SET status = 'verified', verified_at = CURRENT_TIMESTAMP
WHERE id = ?`,
[requestId]
);
await conn.query(
`INSERT INTO addresses (user_id, address_encrypted, postal_verified_at)
VALUES (?, ?, CURRENT_TIMESTAMP)`,
[req.user.userId, request.new_address_encrypted]
);
await conn.commit();
} catch (err) {
await conn.rollback();
console.error('Error in address verification transaction:', err);
throw err;
} finally {
conn.release();
}
res.json({ status: 'verified' });
} catch (err) {
console.error('Error in address verification:', err);
return res.status(500).json({
error: 'Internal server error while verifying address'
});
}
} catch (err) {
console.error('Unexpected error in verify route:', err);
return res.status(500).json({
error: 'Unexpected internal server error'
});
} }
const conn = await pool.getConnection();
try {
await conn.beginTransaction();
await conn.query(
`UPDATE address_change_requests
SET status = 'verified', verified_at = CURRENT_TIMESTAMP
WHERE id = ?`,
[requestId]
);
await conn.query(
`INSERT INTO addresses (user_id, address_encrypted, postal_verified_at)
VALUES (?, ?, CURRENT_TIMESTAMP)`,
[req.user.userId, request.new_address_encrypted]
);
await conn.commit();
} catch (err) {
await conn.rollback();
throw err;
} finally {
conn.release();
}
res.json({ status: 'verified' });
}); });
export default router; export default router;

View file

@ -12,51 +12,14 @@ const registerSchema = z.object({
displayName: z.string().min(2).max(120) displayName: z.string().min(2).max(120)
}); });
const loginSchema = z.object({ router.post('/register', async (req, res) => {
email: z.string().email(), const parsed = registerSchema.safeParse(req.body);
password: z.string().min(1) if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
});
const { email, password, displayName } = parsed.data;
const passwordHash = await bcrypt.hash(password, 12);
// Middleware für Validierung
const validateRegister = (req, res, next) => {
try { try {
const parsed = registerSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({
error: 'Validation failed',
details: parsed.error.flatten()
});
}
req.validatedData = parsed.data;
next();
} catch (err) {
console.error('Validation error:', err);
return res.status(500).json({ error: 'Internal server error during validation' });
}
};
const validateLogin = (req, res, next) => {
try {
const parsed = loginSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({
error: 'Validation failed',
details: parsed.error.flatten()
});
}
req.validatedData = parsed.data;
next();
} catch (err) {
console.error('Validation error:', err);
return res.status(500).json({ error: 'Internal server error during validation' });
}
};
router.post('/register', validateRegister, async (req, res) => {
try {
const { email, password, displayName } = req.validatedData;
const passwordHash = await bcrypt.hash(password, 12);
const [result] = await pool.query( const [result] = await pool.query(
'INSERT INTO users (email, password_hash, display_name) VALUES (?, ?, ?)', 'INSERT INTO users (email, password_hash, display_name) VALUES (?, ?, ?)',
[email, passwordHash, displayName] [email, passwordHash, displayName]
@ -65,35 +28,26 @@ router.post('/register', validateRegister, async (req, res) => {
const token = jwt.sign({ userId: result.insertId, email }, process.env.JWT_SECRET, { expiresIn: '7d' }); const token = jwt.sign({ userId: result.insertId, email }, process.env.JWT_SECRET, { expiresIn: '7d' });
return res.status(201).json({ token }); return res.status(201).json({ token });
} catch (err) { } catch (err) {
console.error('Registration error:', err); if (err.code === 'ER_DUP_ENTRY') return res.status(409).json({ error: 'Email already exists' });
if (err.code === 'ER_DUP_ENTRY') {
return res.status(409).json({ error: 'Email already exists' });
}
return res.status(500).json({ error: 'Registration failed' }); return res.status(500).json({ error: 'Registration failed' });
} }
}); });
router.post('/login', validateLogin, async (req, res) => { router.post('/login', async (req, res) => {
try { const parsed = z.object({ email: z.string().email(), password: z.string().min(1) }).safeParse(req.body);
const { email, password } = req.validatedData; if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
const [rows] = await pool.query('SELECT id, email, password_hash FROM users WHERE email = ? LIMIT 1', [email]);
const user = rows[0];
if (!user) { const { email, password } = parsed.data;
return res.status(401).json({ error: 'Invalid credentials' }); const [rows] = await pool.query('SELECT id, email, password_hash FROM users WHERE email = ? LIMIT 1', [email]);
} const user = rows[0];
const ok = await bcrypt.compare(password, user.password_hash); if (!user) return res.status(401).json({ error: 'Invalid credentials' });
if (!ok) {
return res.status(401).json({ error: 'Invalid credentials' });
}
const token = jwt.sign({ userId: user.id, email: user.email }, process.env.JWT_SECRET, { expiresIn: '7d' }); const ok = await bcrypt.compare(password, user.password_hash);
return res.status(200).json({ token }); if (!ok) return res.status(401).json({ error: 'Invalid credentials' });
} catch (err) {
console.error('Login error:', err); const token = jwt.sign({ userId: user.id, email: user.email }, process.env.JWT_SECRET, { expiresIn: '7d' });
return res.status(500).json({ error: 'Login failed' }); return res.json({ token });
}
}); });
export default router; export default router;

View file

@ -6,145 +6,92 @@ import { requireAuth } from '../middleware/auth.js';
const router = Router(); const router = Router();
const getDealParticipants = async (dealId) => { const getDealParticipants = async (dealId) => {
try { const [rows] = await pool.query(
const [rows] = await pool.query( `SELECT d.id, hr.requester_id, o.helper_id
`SELECT d.id, hr.requester_id, o.helper_id FROM deals d
FROM deals d JOIN help_requests hr ON hr.id = d.request_id
JOIN help_requests hr ON hr.id = d.request_id JOIN offers o ON o.id = d.offer_id
JOIN offers o ON o.id = d.offer_id WHERE d.id = ? LIMIT 1`,
WHERE d.id = ? LIMIT 1`, [dealId]
[dealId] );
);
return rows[0] || null; return rows[0] || null;
} catch (error) {
throw new Error('Database error while fetching deal participants');
}
}; };
router.post('/request', requireAuth, async (req, res) => { router.post('/request', requireAuth, async (req, res) => {
try { const parsed = z.object({ dealId: z.number().int().positive(), targetUserId: z.number().int().positive() }).safeParse(req.body);
const parsed = z.object({ if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
dealId: z.number().int().positive(),
targetUserId: z.number().int().positive()
}).safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ error: 'Invalid input data', details: parsed.error.flatten() });
}
const { dealId, targetUserId } = parsed.data; const { dealId, targetUserId } = parsed.data;
const deal = await getDealParticipants(dealId); const deal = await getDealParticipants(dealId);
if (!deal) return res.status(404).json({ error: 'Deal not found' });
if (!deal) {
return res.status(404).json({ error: 'Deal not found' });
}
const participants = [deal.requester_id, deal.helper_id]; const participants = [deal.requester_id, deal.helper_id];
if (!participants.includes(req.user.userId) || !participants.includes(targetUserId) || req.user.userId === targetUserId) {
if (!participants.includes(req.user.userId) || !participants.includes(targetUserId) || req.user.userId === targetUserId) { return res.status(403).json({ error: 'Forbidden' });
return res.status(403).json({ error: 'Forbidden' });
}
const [existing] = await pool.query(
`SELECT id FROM contact_exchange_requests
WHERE deal_id = ? AND requester_id = ? AND target_id = ? LIMIT 1`,
[dealId, req.user.userId, targetUserId]
);
if (existing.length) {
return res.status(409).json({ error: 'Request already exists' });
}
const [result] = await pool.query(
`INSERT INTO contact_exchange_requests (deal_id, requester_id, target_id, accepted)
VALUES (?, ?, ?, FALSE)`,
[dealId, req.user.userId, targetUserId]
);
res.status(201).json({ id: result.insertId });
} catch (error) {
console.error('Error in contacts request route:', error);
res.status(500).json({ error: 'Internal server error' });
} }
const [existing] = await pool.query(
`SELECT id FROM contact_exchange_requests
WHERE deal_id = ? AND requester_id = ? AND target_id = ? LIMIT 1`,
[dealId, req.user.userId, targetUserId]
);
if (existing.length) return res.status(409).json({ error: 'Request already exists' });
const [result] = await pool.query(
`INSERT INTO contact_exchange_requests (deal_id, requester_id, target_id, accepted)
VALUES (?, ?, ?, FALSE)`,
[dealId, req.user.userId, targetUserId]
);
res.status(201).json({ id: result.insertId });
}); });
router.post('/respond', requireAuth, async (req, res) => { router.post('/respond', requireAuth, async (req, res) => {
try { const parsed = z.object({ requestId: z.number().int().positive(), accept: z.boolean() }).safeParse(req.body);
const parsed = z.object({ if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
requestId: z.number().int().positive(),
accept: z.boolean()
}).safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ error: 'Invalid input data', details: parsed.error.flatten() });
}
const { requestId, accept } = parsed.data; const { requestId, accept } = parsed.data;
const [rows] = await pool.query( const [rows] = await pool.query(
`SELECT id, target_id FROM contact_exchange_requests WHERE id = ? LIMIT 1`, `SELECT id, target_id FROM contact_exchange_requests WHERE id = ? LIMIT 1`,
[requestId] [requestId]
); );
const row = rows[0]; const row = rows[0];
if (!row) return res.status(404).json({ error: 'Request not found' });
if (!row) { if (row.target_id !== req.user.userId) return res.status(403).json({ error: 'Forbidden' });
return res.status(404).json({ error: 'Request not found' });
}
if (row.target_id !== req.user.userId) {
return res.status(403).json({ error: 'Forbidden' });
}
if (accept) { if (accept) {
await pool.query('UPDATE contact_exchange_requests SET accepted = TRUE WHERE id = ?', [requestId]); await pool.query('UPDATE contact_exchange_requests SET accepted = TRUE WHERE id = ?', [requestId]);
return res.json({ status: 'accepted' }); return res.json({ status: 'accepted' });
}
await pool.query('DELETE FROM contact_exchange_requests WHERE id = ?', [requestId]);
res.json({ status: 'rejected' });
} catch (error) {
console.error('Error in contacts respond route:', error);
res.status(500).json({ error: 'Internal server error' });
} }
await pool.query('DELETE FROM contact_exchange_requests WHERE id = ?', [requestId]);
res.json({ status: 'rejected' });
}); });
router.get('/deal/:dealId', requireAuth, async (req, res) => { router.get('/deal/:dealId', requireAuth, async (req, res) => {
try { const dealId = Number(req.params.dealId);
const dealId = Number(req.params.dealId); if (Number.isNaN(dealId)) return res.status(400).json({ error: 'Invalid dealId' });
if (Number.isNaN(dealId)) {
return res.status(400).json({ error: 'Invalid dealId' });
}
const deal = await getDealParticipants(dealId); const deal = await getDealParticipants(dealId);
if (!deal) return res.status(404).json({ error: 'Deal not found' });
if (!deal) {
return res.status(404).json({ error: 'Deal not found' });
}
const participants = [deal.requester_id, deal.helper_id]; const participants = [deal.requester_id, deal.helper_id];
if (!participants.includes(req.user.userId)) return res.status(403).json({ error: 'Forbidden' });
if (!participants.includes(req.user.userId)) {
return res.status(403).json({ error: 'Forbidden' });
}
const [rows] = await pool.query( const [rows] = await pool.query(
`SELECT cer.id, cer.requester_id, cer.target_id, cer.accepted, `SELECT cer.id, cer.requester_id, cer.target_id, cer.accepted,
ru.phone_encrypted AS requester_phone_encrypted, ru.phone_encrypted AS requester_phone_encrypted,
tu.phone_encrypted AS target_phone_encrypted tu.phone_encrypted AS target_phone_encrypted
FROM contact_exchange_requests cer FROM contact_exchange_requests cer
JOIN users ru ON ru.id = cer.requester_id JOIN users ru ON ru.id = cer.requester_id
JOIN users tu ON tu.id = cer.target_id JOIN users tu ON tu.id = cer.target_id
WHERE cer.deal_id = ? AND cer.accepted = TRUE`, WHERE cer.deal_id = ? AND cer.accepted = TRUE`,
[dealId] [dealId]
); );
res.json(rows); res.json(rows);
} catch (error) {
console.error('Error in contacts deal route:', error);
res.status(500).json({ error: 'Internal server error' });
}
}); });
export default router; export default router;

View file

@ -6,41 +6,31 @@ import { requireAuth } from '../middleware/auth.js';
const router = Router(); const router = Router();
router.get('/', async (_req, res) => { router.get('/', async (_req, res) => {
try { const [rows] = await pool.query(
const [rows] = await pool.query( `SELECT hr.id, hr.title, hr.description, hr.value_chf, hr.status, hr.created_at, u.display_name requester_name
`SELECT hr.id, hr.title, hr.description, hr.value_chf, hr.status, hr.created_at, u.display_name requester_name FROM help_requests hr
FROM help_requests hr JOIN users u ON u.id = hr.requester_id
JOIN users u ON u.id = hr.requester_id ORDER BY hr.created_at DESC`
ORDER BY hr.created_at DESC` );
); res.json(rows);
res.json(rows);
} catch (error) {
console.error('Error fetching help requests:', error);
res.status(500).json({ error: 'Internal server error' });
}
}); });
router.post('/', requireAuth, async (req, res) => { router.post('/', requireAuth, async (req, res) => {
try { const parsed = z.object({
const parsed = z.object({ title: z.string().min(3).max(180),
title: z.string().min(3).max(180), description: z.string().min(5),
description: z.string().min(5), valueChf: z.number().positive()
valueChf: z.number().positive() }).safeParse(req.body);
}).safeParse(req.body);
if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() }); if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
const { title, description, valueChf } = parsed.data; const { title, description, valueChf } = parsed.data;
const [result] = await pool.query( const [result] = await pool.query(
'INSERT INTO help_requests (requester_id, title, description, value_chf) VALUES (?, ?, ?, ?)', 'INSERT INTO help_requests (requester_id, title, description, value_chf) VALUES (?, ?, ?, ?)',
[req.user.userId, title, description, valueChf] [req.user.userId, title, description, valueChf]
); );
res.status(201).json({ id: result.insertId }); res.status(201).json({ id: result.insertId });
} catch (error) {
console.error('Error creating help request:', error);
res.status(500).json({ error: 'Internal server error' });
}
}); });
export default router; export default router;

View file

@ -6,98 +6,59 @@ import { requireAuth } from '../middleware/auth.js';
const router = Router(); const router = Router();
router.post('/:requestId', requireAuth, async (req, res) => { router.post('/:requestId', requireAuth, async (req, res) => {
try { const requestId = Number(req.params.requestId);
const requestId = Number(req.params.requestId); const parsed = z.object({ amountChf: z.number().positive(), message: z.string().max(2000).optional() }).safeParse(req.body);
if (Number.isNaN(requestId)) { if (!parsed.success || Number.isNaN(requestId)) return res.status(400).json({ error: 'Invalid payload' });
return res.status(400).json({ error: 'Invalid requestId' });
}
const parsed = z.object({ const { amountChf, message } = parsed.data;
amountChf: z.number().positive(), const [result] = await pool.query(
message: z.string().max(2000).optional() `INSERT INTO offers (request_id, helper_id, amount_chf, message)
}).safeParse(req.body); VALUES (?, ?, ?, ?)`,
[requestId, req.user.userId, amountChf, message || null]
);
if (!parsed.success) { await pool.query('UPDATE help_requests SET status = ? WHERE id = ?', ['negotiating', requestId]);
return res.status(400).json({ error: 'Invalid payload' });
}
const { amountChf, message } = parsed.data; res.status(201).json({ id: result.insertId });
const [result] = await pool.query(
`INSERT INTO offers (request_id, helper_id, amount_chf, message)
VALUES (?, ?, ?, ?)`,
[requestId, req.user.userId, amountChf, message || null]
);
await pool.query('UPDATE help_requests SET status = ? WHERE id = ?', ['negotiating', requestId]);
res.status(201).json({ id: result.insertId });
} catch (error) {
console.error('Error in POST /offers/:requestId:', error);
res.status(500).json({ error: 'Internal server error' });
}
}); });
router.post('/negotiation/:offerId', requireAuth, async (req, res) => { router.post('/negotiation/:offerId', requireAuth, async (req, res) => {
try { const offerId = Number(req.params.offerId);
const offerId = Number(req.params.offerId); const parsed = z.object({ amountChf: z.number().positive(), message: z.string().max(2000).optional() }).safeParse(req.body);
if (Number.isNaN(offerId)) { if (!parsed.success || Number.isNaN(offerId)) return res.status(400).json({ error: 'Invalid payload' });
return res.status(400).json({ error: 'Invalid offerId' });
}
const parsed = z.object({ const { amountChf, message } = parsed.data;
amountChf: z.number().positive(), const [result] = await pool.query(
message: z.string().max(2000).optional() `INSERT INTO negotiations (offer_id, sender_id, amount_chf, message)
}).safeParse(req.body); VALUES (?, ?, ?, ?)`,
[offerId, req.user.userId, amountChf, message || null]
);
if (!parsed.success) { await pool.query('UPDATE offers SET status = ? WHERE id = ?', ['countered', offerId]);
return res.status(400).json({ error: 'Invalid payload' });
}
const { amountChf, message } = parsed.data; res.status(201).json({ id: result.insertId });
const [result] = await pool.query(
`INSERT INTO negotiations (offer_id, sender_id, amount_chf, message)
VALUES (?, ?, ?, ?)`,
[offerId, req.user.userId, amountChf, message || null]
);
await pool.query('UPDATE offers SET status = ? WHERE id = ?', ['countered', offerId]);
res.status(201).json({ id: result.insertId });
} catch (error) {
console.error('Error in POST /offers/negotiation/:offerId:', error);
res.status(500).json({ error: 'Internal server error' });
}
}); });
router.post('/accept/:offerId', requireAuth, async (req, res) => { router.post('/accept/:offerId', requireAuth, async (req, res) => {
try { const offerId = Number(req.params.offerId);
const offerId = Number(req.params.offerId); if (Number.isNaN(offerId)) return res.status(400).json({ error: 'Invalid offerId' });
if (Number.isNaN(offerId)) {
return res.status(400).json({ error: 'Invalid offerId' });
}
const [offers] = await pool.query( const [offers] = await pool.query(
'SELECT id, request_id, amount_chf FROM offers WHERE id = ? LIMIT 1', 'SELECT id, request_id, amount_chf FROM offers WHERE id = ? LIMIT 1',
[offerId] [offerId]
); );
const offer = offers[0]; const offer = offers[0];
if (!offer) { if (!offer) return res.status(404).json({ error: 'Offer not found' });
return res.status(404).json({ error: 'Offer not found' });
}
await pool.query('UPDATE offers SET status = ? WHERE id = ?', ['accepted', offerId]); await pool.query('UPDATE offers SET status = ? WHERE id = ?', ['accepted', offerId]);
await pool.query('UPDATE help_requests SET status = ? WHERE id = ?', ['agreed', offer.request_id]); await pool.query('UPDATE help_requests SET status = ? WHERE id = ?', ['agreed', offer.request_id]);
const [dealResult] = await pool.query( const [dealResult] = await pool.query(
'INSERT INTO deals (request_id, offer_id, agreed_amount_chf) VALUES (?, ?, ?)', 'INSERT INTO deals (request_id, offer_id, agreed_amount_chf) VALUES (?, ?, ?)',
[offer.request_id, offer.id, offer.amount_chf] [offer.request_id, offer.id, offer.amount_chf]
); );
res.status(201).json({ dealId: dealResult.insertId }); res.status(201).json({ dealId: dealResult.insertId });
} catch (error) {
console.error('Error in POST /offers/accept/:offerId:', error);
res.status(500).json({ error: 'Internal server error' });
}
}); });
export default router; export default router;

View file

@ -7,18 +7,13 @@ import { encryptText } from '../services/encryption.js';
const router = Router(); const router = Router();
router.post('/phone', requireAuth, async (req, res) => { router.post('/phone', requireAuth, async (req, res) => {
try { const parsed = z.object({ phone: z.string().min(6).max(40) }).safeParse(req.body);
const parsed = z.object({ phone: z.string().min(6).max(40) }).safeParse(req.body); if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
const encryptedPhone = encryptText(parsed.data.phone); const encryptedPhone = encryptText(parsed.data.phone);
await pool.query('UPDATE users SET phone_encrypted = ? WHERE id = ?', [encryptedPhone, req.user.userId]); await pool.query('UPDATE users SET phone_encrypted = ? WHERE id = ?', [encryptedPhone, req.user.userId]);
res.status(200).json({ status: 'updated' }); res.json({ status: 'updated' });
} catch (error) {
console.error('Error updating phone:', error);
res.status(500).json({ error: 'Internal server error' });
}
}); });
export default router; export default router;

View file

@ -6,35 +6,24 @@ import { requireAuth } from '../middleware/auth.js';
const router = Router(); const router = Router();
router.post('/:dealId', requireAuth, async (req, res) => { router.post('/:dealId', requireAuth, async (req, res) => {
try { const dealId = Number(req.params.dealId);
const dealId = Number(req.params.dealId); const parsed = z.object({ revieweeId: z.number().int().positive(), rating: z.number().int().min(1).max(5), comment: z.string().max(2000).optional() }).safeParse(req.body);
const parsed = z.object({
revieweeId: z.number().int().positive(),
rating: z.number().int().min(1).max(5),
comment: z.string().max(2000).optional()
}).safeParse(req.body);
if (!parsed.success || Number.isNaN(dealId)) { if (!parsed.success || Number.isNaN(dealId)) return res.status(400).json({ error: 'Invalid payload' });
return res.status(400).json({ error: 'Invalid payload' });
}
const now = new Date(); const now = new Date();
const earliest = new Date(now.getTime() + 2 * 24 * 60 * 60 * 1000); const earliest = new Date(now.getTime() + 2 * 24 * 60 * 60 * 1000);
const latest = new Date(now.getTime() + 14 * 24 * 60 * 60 * 1000); const latest = new Date(now.getTime() + 14 * 24 * 60 * 60 * 1000);
const { revieweeId, rating, comment } = parsed.data; const { revieweeId, rating, comment } = parsed.data;
const [result] = await pool.query( const [result] = await pool.query(
`INSERT INTO reviews (deal_id, reviewer_id, reviewee_id, rating, comment, earliest_prompt_at, latest_prompt_at) `INSERT INTO reviews (deal_id, reviewer_id, reviewee_id, rating, comment, earliest_prompt_at, latest_prompt_at)
VALUES (?, ?, ?, ?, ?, ?, ?)`, VALUES (?, ?, ?, ?, ?, ?, ?)`,
[dealId, req.user.userId, revieweeId, rating, comment || null, earliest, latest] [dealId, req.user.userId, revieweeId, rating, comment || null, earliest, latest]
); );
res.status(201).json({ id: result.insertId }); res.status(201).json({ id: result.insertId });
} catch (error) {
console.error('Error creating review:', error);
res.status(500).json({ error: 'Internal server error' });
}
}); });
export default router; export default router;