Compare commits
No commits in common. "51be362dc49ae062db1f267bfc0a9baaf403e489" and "f03b758e183e62418a330980c8352c485f04c2c0" have entirely different histories.
51be362dc4
...
f03b758e18
7 changed files with 222 additions and 455 deletions
|
|
@ -9,50 +9,13 @@ const router = Router();
|
|||
|
||||
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) => {
|
||||
try {
|
||||
const parsed = changeRequestSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({
|
||||
error: 'Invalid input data',
|
||||
details: parsed.error.flatten()
|
||||
});
|
||||
}
|
||||
|
||||
// Check if user already has an address
|
||||
try {
|
||||
const [existingRows] = await pool.query(
|
||||
`SELECT id FROM addresses WHERE user_id = ? LIMIT 1`,
|
||||
[req.user.userId]
|
||||
);
|
||||
|
||||
if (existingRows.length === 0) {
|
||||
return res.status(400).json({
|
||||
error: 'User must have an existing address to request a change'
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error checking existing address:', err);
|
||||
return res.status(500).json({
|
||||
error: 'Internal server error while checking existing address'
|
||||
});
|
||||
}
|
||||
const parsed = z.object({ newAddress: z.string().min(10) }).safeParse(req.body);
|
||||
if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
|
||||
|
||||
const verificationCode = String(randomInt(100000, 999999));
|
||||
const verificationCodeHash = hashCode(verificationCode);
|
||||
|
||||
try {
|
||||
const [result] = await pool.query(
|
||||
`INSERT INTO address_change_requests (user_id, new_address_encrypted, verification_code_hash)
|
||||
VALUES (?, ?, ?)`,
|
||||
|
|
@ -65,33 +28,14 @@ router.post('/change-request', requireAuth, async (req, res) => {
|
|||
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) => {
|
||||
try {
|
||||
const parsed = verifyRequestSchema.safeParse(req.body);
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({
|
||||
error: 'Invalid input data',
|
||||
details: parsed.error.flatten()
|
||||
});
|
||||
}
|
||||
const parsed = z.object({ requestId: z.number().int().positive(), code: z.string().regex(/^\d{6}$/) }).safeParse(req.body);
|
||||
if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
|
||||
|
||||
const { requestId, code } = parsed.data;
|
||||
|
||||
try {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT id, user_id, new_address_encrypted, verification_code_hash, status
|
||||
FROM address_change_requests
|
||||
|
|
@ -128,25 +72,12 @@ router.post('/verify', requireAuth, async (req, res) => {
|
|||
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'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
@ -12,51 +12,14 @@ const registerSchema = z.object({
|
|||
displayName: z.string().min(2).max(120)
|
||||
});
|
||||
|
||||
const loginSchema = z.object({
|
||||
email: z.string().email(),
|
||||
password: z.string().min(1)
|
||||
});
|
||||
|
||||
// Middleware für Validierung
|
||||
const validateRegister = (req, res, next) => {
|
||||
try {
|
||||
router.post('/register', async (req, res) => {
|
||||
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' });
|
||||
}
|
||||
};
|
||||
if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
|
||||
|
||||
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 { email, password, displayName } = parsed.data;
|
||||
const passwordHash = await bcrypt.hash(password, 12);
|
||||
|
||||
try {
|
||||
const [result] = await pool.query(
|
||||
'INSERT INTO users (email, password_hash, display_name) VALUES (?, ?, ?)',
|
||||
[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' });
|
||||
return res.status(201).json({ token });
|
||||
} 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' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/login', validateLogin, async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.validatedData;
|
||||
router.post('/login', async (req, res) => {
|
||||
const parsed = z.object({ email: z.string().email(), password: z.string().min(1) }).safeParse(req.body);
|
||||
if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
|
||||
|
||||
const { email, password } = parsed.data;
|
||||
const [rows] = await pool.query('SELECT id, email, password_hash FROM users WHERE email = ? LIMIT 1', [email]);
|
||||
const user = rows[0];
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({ error: 'Invalid credentials' });
|
||||
}
|
||||
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
|
||||
|
||||
const ok = await bcrypt.compare(password, user.password_hash);
|
||||
if (!ok) {
|
||||
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' });
|
||||
return res.status(200).json({ token });
|
||||
} catch (err) {
|
||||
console.error('Login error:', err);
|
||||
return res.status(500).json({ error: 'Login failed' });
|
||||
}
|
||||
return res.json({ token });
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
@ -6,7 +6,6 @@ import { requireAuth } from '../middleware/auth.js';
|
|||
const router = Router();
|
||||
|
||||
const getDealParticipants = async (dealId) => {
|
||||
try {
|
||||
const [rows] = await pool.query(
|
||||
`SELECT d.id, hr.requester_id, o.helper_id
|
||||
FROM deals d
|
||||
|
|
@ -17,31 +16,17 @@ const getDealParticipants = async (dealId) => {
|
|||
);
|
||||
|
||||
return rows[0] || null;
|
||||
} catch (error) {
|
||||
throw new Error('Database error while fetching deal participants');
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: 'Invalid input data', details: parsed.error.flatten() });
|
||||
}
|
||||
const parsed = z.object({ dealId: z.number().int().positive(), targetUserId: z.number().int().positive() }).safeParse(req.body);
|
||||
if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
|
||||
|
||||
const { dealId, targetUserId } = parsed.data;
|
||||
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];
|
||||
|
||||
if (!participants.includes(req.user.userId) || !participants.includes(targetUserId) || req.user.userId === targetUserId) {
|
||||
return res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
|
|
@ -51,10 +36,7 @@ router.post('/request', requireAuth, async (req, res) => {
|
|||
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' });
|
||||
}
|
||||
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)
|
||||
|
|
@ -63,22 +45,11 @@ router.post('/request', requireAuth, async (req, res) => {
|
|||
);
|
||||
|
||||
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' });
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/respond', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const parsed = z.object({
|
||||
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 parsed = z.object({ requestId: z.number().int().positive(), accept: z.boolean() }).safeParse(req.body);
|
||||
if (!parsed.success) return res.status(400).json({ error: parsed.error.flatten() });
|
||||
|
||||
const { requestId, accept } = parsed.data;
|
||||
const [rows] = await pool.query(
|
||||
|
|
@ -87,14 +58,8 @@ router.post('/respond', requireAuth, async (req, res) => {
|
|||
);
|
||||
|
||||
const row = rows[0];
|
||||
|
||||
if (!row) {
|
||||
return res.status(404).json({ error: 'Request not found' });
|
||||
}
|
||||
|
||||
if (row.target_id !== req.user.userId) {
|
||||
return res.status(403).json({ error: 'Forbidden' });
|
||||
}
|
||||
if (!row) 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) {
|
||||
await pool.query('UPDATE contact_exchange_requests SET accepted = TRUE WHERE id = ?', [requestId]);
|
||||
|
|
@ -103,31 +68,17 @@ router.post('/respond', requireAuth, async (req, res) => {
|
|||
|
||||
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' });
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/deal/:dealId', requireAuth, async (req, res) => {
|
||||
try {
|
||||
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);
|
||||
|
||||
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];
|
||||
|
||||
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(
|
||||
`SELECT cer.id, cer.requester_id, cer.target_id, cer.accepted,
|
||||
|
|
@ -141,10 +92,6 @@ router.get('/deal/:dealId', requireAuth, async (req, res) => {
|
|||
);
|
||||
|
||||
res.json(rows);
|
||||
} catch (error) {
|
||||
console.error('Error in contacts deal route:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
@ -6,7 +6,6 @@ import { requireAuth } from '../middleware/auth.js';
|
|||
const router = Router();
|
||||
|
||||
router.get('/', async (_req, res) => {
|
||||
try {
|
||||
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
|
||||
FROM help_requests hr
|
||||
|
|
@ -14,14 +13,9 @@ router.get('/', async (_req, res) => {
|
|||
ORDER BY hr.created_at DESC`
|
||||
);
|
||||
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) => {
|
||||
try {
|
||||
const parsed = z.object({
|
||||
title: z.string().min(3).max(180),
|
||||
description: z.string().min(5),
|
||||
|
|
@ -37,10 +31,6 @@ router.post('/', requireAuth, async (req, res) => {
|
|||
);
|
||||
|
||||
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;
|
||||
|
|
@ -6,20 +6,9 @@ import { requireAuth } from '../middleware/auth.js';
|
|||
const router = Router();
|
||||
|
||||
router.post('/:requestId', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const requestId = Number(req.params.requestId);
|
||||
if (Number.isNaN(requestId)) {
|
||||
return res.status(400).json({ error: 'Invalid requestId' });
|
||||
}
|
||||
|
||||
const parsed = z.object({
|
||||
amountChf: z.number().positive(),
|
||||
message: z.string().max(2000).optional()
|
||||
}).safeParse(req.body);
|
||||
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: 'Invalid payload' });
|
||||
}
|
||||
const parsed = z.object({ amountChf: z.number().positive(), message: z.string().max(2000).optional() }).safeParse(req.body);
|
||||
if (!parsed.success || Number.isNaN(requestId)) return res.status(400).json({ error: 'Invalid payload' });
|
||||
|
||||
const { amountChf, message } = parsed.data;
|
||||
const [result] = await pool.query(
|
||||
|
|
@ -31,27 +20,12 @@ router.post('/:requestId', requireAuth, async (req, res) => {
|
|||
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) => {
|
||||
try {
|
||||
const offerId = Number(req.params.offerId);
|
||||
if (Number.isNaN(offerId)) {
|
||||
return res.status(400).json({ error: 'Invalid offerId' });
|
||||
}
|
||||
|
||||
const parsed = z.object({
|
||||
amountChf: z.number().positive(),
|
||||
message: z.string().max(2000).optional()
|
||||
}).safeParse(req.body);
|
||||
|
||||
if (!parsed.success) {
|
||||
return res.status(400).json({ error: 'Invalid payload' });
|
||||
}
|
||||
const parsed = z.object({ amountChf: z.number().positive(), message: z.string().max(2000).optional() }).safeParse(req.body);
|
||||
if (!parsed.success || Number.isNaN(offerId)) return res.status(400).json({ error: 'Invalid payload' });
|
||||
|
||||
const { amountChf, message } = parsed.data;
|
||||
const [result] = await pool.query(
|
||||
|
|
@ -63,27 +37,18 @@ router.post('/negotiation/:offerId', requireAuth, async (req, res) => {
|
|||
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) => {
|
||||
try {
|
||||
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(
|
||||
'SELECT id, request_id, amount_chf FROM offers WHERE id = ? LIMIT 1',
|
||||
[offerId]
|
||||
);
|
||||
const offer = offers[0];
|
||||
if (!offer) {
|
||||
return res.status(404).json({ error: 'Offer not found' });
|
||||
}
|
||||
if (!offer) return res.status(404).json({ error: 'Offer not found' });
|
||||
|
||||
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]);
|
||||
|
|
@ -94,10 +59,6 @@ router.post('/accept/:offerId', requireAuth, async (req, res) => {
|
|||
);
|
||||
|
||||
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;
|
||||
|
|
@ -7,18 +7,13 @@ import { encryptText } from '../services/encryption.js';
|
|||
const router = Router();
|
||||
|
||||
router.post('/phone', requireAuth, async (req, res) => {
|
||||
try {
|
||||
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() });
|
||||
|
||||
const encryptedPhone = encryptText(parsed.data.phone);
|
||||
await pool.query('UPDATE users SET phone_encrypted = ? WHERE id = ?', [encryptedPhone, req.user.userId]);
|
||||
|
||||
res.status(200).json({ status: 'updated' });
|
||||
} catch (error) {
|
||||
console.error('Error updating phone:', error);
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
res.json({ status: 'updated' });
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
@ -6,17 +6,10 @@ import { requireAuth } from '../middleware/auth.js';
|
|||
const router = Router();
|
||||
|
||||
router.post('/:dealId', requireAuth, async (req, res) => {
|
||||
try {
|
||||
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)) {
|
||||
return res.status(400).json({ error: 'Invalid payload' });
|
||||
}
|
||||
if (!parsed.success || Number.isNaN(dealId)) return res.status(400).json({ error: 'Invalid payload' });
|
||||
|
||||
const now = new Date();
|
||||
const earliest = new Date(now.getTime() + 2 * 24 * 60 * 60 * 1000);
|
||||
|
|
@ -31,10 +24,6 @@ router.post('/:dealId', requireAuth, async (req, res) => {
|
|||
);
|
||||
|
||||
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;
|
||||
Loading…
Add table
Add a link
Reference in a new issue