const express = require('express'); const { Op } = require('sequelize'); const { User, Customer, Car } = require('../models'); const { authenticate, requireAdmin } = require('../middleware/auth'); const api = require('../lib/api'); const router = express.Router(); const route = api.asyncRoute; router.use(route(authenticate)); router.post('/auth/login', route(async (req, res) => { await req.user.update({ last_login: new Date() }); return api.response(res, api.cleanUser(req.user)); })); router.route('/auth/me') .get((req, res) => api.response(res, api.cleanUser(req.user))) .put(route((req, res) => updateProfile(req, res))) .patch(route((req, res) => updateProfile(req, res))); async function updateProfile(req, res) { const data = api.pick(req.body, api.PROFILE_FIELDS); const errors = api.validateUser(data, false); if (Object.keys(errors).length) return api.validationResponse(res, errors); await req.user.update(data); return api.response(res, api.cleanUser(req.user)); } router.route('/users') .all(requireAdmin) .get(route(async (req, res) => { const options = api.pageOptions(req.query, ['id', ...api.USER_FIELDS.filter((field) => field !== 'password'), 'last_login', 'created', 'modified']); const where = {}; if (req.query.q) where[Op.or] = ['login', 'email', 'first_name', 'last_name'].map((field) => ({ [field]: { [Op.like]: `%${req.query.q}%` } })); if (req.query.role) where.role = req.query.role; if (req.query.active !== undefined) where.active = req.query.active === 'true' || req.query.active === '1'; const result = await User.findAndCountAll({ where, limit: options.limit, offset: options.offset, order: [[options.sort, options.direction]] }); const total = result.count; res.set('X-Total-Count', String(total)); return api.response(res, result.rows.map(api.cleanUser), 200, { meta: { page: options.page, limit: options.limit, total, pages: Math.ceil(total / options.limit), has_next_page: options.page * options.limit < total, has_prev_page: options.page > 1, sort: options.sort, direction: options.direction.toLowerCase() }, }); })) .post(route(async (req, res) => { const data = api.pick(req.body, api.USER_FIELDS); if (data.active === undefined) data.active = true; if (data.role === undefined) data.role = 'user'; const errors = api.validateUser(data, true); if (Object.keys(errors).length) return api.validationResponse(res, errors); try { const user = await User.create(data); res.location(`/api/users/${user.id}`); return api.response(res, api.cleanUser(user), 201); } catch (error) { return api.fail(res, error); } })); router.route('/users/:id') .all(requireAdmin) .get(route(async (req, res) => { const user = await User.findByPk(req.params.id); return user ? api.response(res, api.cleanUser(user)) : api.notFound(res, 'User'); })) .put(route((req, res) => updateUser(req, res))) .patch(route((req, res) => updateUser(req, res))) .delete(route(async (req, res) => { const user = await User.findByPk(req.params.id); if (!user) return api.notFound(res, 'User'); if (user.id === req.user.id) return res.status(400).json({ success: false, error: { code: 400, message: 'You cannot delete your own account.' } }); await user.destroy(); return api.response(res, null, 204); })); async function updateUser(req, res) { const user = await User.findByPk(req.params.id); if (!user) return api.notFound(res, 'User'); const data = api.pick(req.body, api.USER_FIELDS); if (user.id === req.user.id) { if (data.role !== undefined && data.role !== user.role) return res.status(400).json({ success: false, error: { code: 400, message: 'You cannot change your own role.' } }); if (data.active === false) return res.status(400).json({ success: false, error: { code: 400, message: 'You cannot deactivate your own account.' } }); } const errors = api.validateUser(data, false); if (Object.keys(errors).length) return api.validationResponse(res, errors); try { await user.update(data); return api.response(res, api.cleanUser(user)); } catch (error) { return api.fail(res, error); } } router.route('/customers') .get(route(async (req, res) => { const options = api.pageOptions(req.query, ['id', 'type', 'first_name', 'last_name', 'email', 'company_name', 'city', 'country', 'created', 'modified']); const where = {}; if (req.query.q) where[Op.or] = ['first_name', 'last_name', 'email', 'phone', 'company_name', 'city', 'tax_id'].map((field) => ({ [field]: { [Op.like]: `%${req.query.q}%` } })); for (const field of ['type', 'city', 'country']) if (req.query[field]) where[field] = req.query[field]; const include = String(req.query.with_cars).toLowerCase() === 'true' || req.query.with_cars === '1' ? [{ model: Car, as: 'cars' }] : undefined; return api.list(res, Customer, { ...options, where }, include); })) .post(route(async (req, res) => { const data = api.pick(req.body, api.CUSTOMER_FIELDS); if (data.type === undefined) data.type = 'individual'; if (data.country === undefined) data.country = 'PL'; const errors = api.validateCustomer(data, true); if (Object.keys(errors).length) return api.validationResponse(res, errors); try { const customer = await Customer.create({ ...data, created_by: req.user.id }); res.location(`/api/customers/${customer.id}`); return api.response(res, customer, 201); } catch (error) { return api.fail(res, error); } })); router.route('/customers/:id') .get(route(async (req, res) => { const customer = await Customer.findByPk(req.params.id, { include: [{ model: Car, as: 'cars' }] }); return customer ? api.response(res, customer) : api.notFound(res, 'Customer'); })) .put(route((req, res) => updateCustomer(req, res))) .patch(route((req, res) => updateCustomer(req, res))) .delete(route(async (req, res) => { const customer = await Customer.findByPk(req.params.id); if (!customer) return api.notFound(res, 'Customer'); await customer.destroy(); return api.response(res, null, 204); })); async function updateCustomer(req, res) { const customer = await Customer.findByPk(req.params.id); if (!customer) return api.notFound(res, 'Customer'); const data = api.pick(req.body, api.CUSTOMER_FIELDS); const errors = api.validateCustomer(data, false); if (Object.keys(errors).length) return api.validationResponse(res, errors); try { await customer.update(data); return api.response(res, customer); } catch (error) { return api.fail(res, error); } } router.route('/customers/:customer_id/cars') .get(route(async (req, res) => { const customer = await Customer.findByPk(req.params.customer_id); if (!customer) return api.notFound(res, 'Customer'); const options = api.pageOptions(req.query, ['id', 'customer_id', 'make', 'model', 'production_year', 'registration_number', 'fuel_type', 'mileage', 'power_hp', 'created', 'modified']); return api.list(res, Car, { ...options, where: { customer_id: customer.id } }); })) .post(route(async (req, res) => { const customer = await Customer.findByPk(req.params.customer_id); if (!customer) return api.notFound(res, 'Customer'); return createCar(req, res, customer.id); })); router.route('/cars') .get(route(async (req, res) => { const options = api.pageOptions(req.query, ['id', 'customer_id', 'make', 'model', 'production_year', 'registration_number', 'fuel_type', 'mileage', 'power_hp', 'created', 'modified']); const where = {}; if (req.query.customer_id) where.customer_id = req.query.customer_id; if (req.query.q) where[Op.or] = ['make', 'model', 'vin', 'registration_number'].map((field) => ({ [field]: { [Op.like]: `%${req.query.q}%` } })); for (const field of ['make', 'fuel_type', 'transmission']) if (req.query[field]) where[field] = req.query[field]; if (req.query.year_from) where.production_year = { ...(where.production_year || {}), [Op.gte]: Number(req.query.year_from) }; if (req.query.year_to) where.production_year = { ...(where.production_year || {}), [Op.lte]: Number(req.query.year_to) }; const include = String(req.query.with_customer).toLowerCase() === 'true' || req.query.with_customer === '1' ? [{ model: Customer, as: 'customer' }] : undefined; return api.list(res, Car, { ...options, where }, include); })) .post(route((req, res) => createCar(req, res))); async function createCar(req, res, customerId) { const data = api.pick(req.body, api.CAR_FIELDS); if (customerId !== undefined) data.customer_id = customerId; const errors = api.validateCar(data, true); if (Object.keys(errors).length) return api.validationResponse(res, errors); try { const car = await Car.create(data); res.location(`/api/cars/${car.id}`); return api.response(res, car, 201); } catch (error) { return api.fail(res, error); } } router.route('/cars/:id') .get(route(async (req, res) => { const car = await Car.findByPk(req.params.id, { include: [{ model: Customer, as: 'customer' }] }); return car ? api.response(res, car) : api.notFound(res, 'Car'); })) .put(route((req, res) => updateCar(req, res))) .patch(route((req, res) => updateCar(req, res))) .delete(route(async (req, res) => { const car = await Car.findByPk(req.params.id); if (!car) return api.notFound(res, 'Car'); await car.destroy(); return api.response(res, null, 204); })); async function updateCar(req, res) { const car = await Car.findByPk(req.params.id); if (!car) return api.notFound(res, 'Car'); const data = api.pick(req.body, api.CAR_FIELDS); const errors = api.validateCar(data, false); if (Object.keys(errors).length) return api.validationResponse(res, errors); try { await car.update(data); return api.response(res, car); } catch (error) { return api.fail(res, error); } } module.exports = router;