import type express from 'express'; import { asyncErr, KError } from '../helpers'; import { Account, View } from '../models'; import { isDemoEnabled } from './instance'; import type { IdentifiedRequest, PreloadedRequest } from './routes'; // Prefills the @view field with a queried bank account. export async function preloadView( req: IdentifiedRequest, res: express.Response, nextHandler: () => void, viewId: number ) { try { const { id: userId } = req.user; const view = await View.find(userId, viewId); if (!view) { throw new KError('View not found', 404); } if (!req.preloaded) { req.preloaded = {}; } req.preloaded.view = view; nextHandler(); } catch (err) { asyncErr(res, err, 'when preloading a view'); } } async function checkViewAccounts(userId: number, accounts: any): Promise { const accountIds: number[] = []; if (accounts instanceof Array) { for (const viewAccount of accounts) { if (typeof viewAccount?.accountId !== 'number') { throw new KError('a view account must have a numeric accountId', 400); } if (accountIds.includes(viewAccount.accountId)) { throw new KError('a view cannot include an account several times', 400); } accountIds.push(viewAccount.accountId); } } if (!accountIds.length) { throw new KError('a view should have at least one account', 400); } const accountsFromIds = await Account.findMany(userId, accountIds); if (accountsFromIds.length !== accountIds.length) { throw new KError('some view accounts could not be found', 404); } const currencies = new Set( await Promise.all(accountsFromIds.map(account => account.getCurrency())) ); if (currencies.size > 1) { throw new KError('a view cannot contain accounts of different currencies', 400); } } export async function create(req: IdentifiedRequest, res: express.Response) { try { const { id: userId } = req.user; const newView = req.body; if (!newView || typeof newView.label !== 'string') { throw new KError('missing parameters', 400); } await checkViewAccounts(userId, newView.accounts); const view = await View.create(userId, newView); res.status(201).json(view); } catch (err) { asyncErr(res, err, 'when creating a view'); } } export async function update(req: PreloadedRequest, res: express.Response) { try { const { id: userId } = req.user; const newFields = req.body; const view = req.preloaded.view; // Always validate, even for a plain rename: a view which already mixes currencies (it // may predate this restriction, or an account's currency may have changed on the bank's // side) must be fixed by the user before it can be modified at all. const accounts = newFields && typeof newFields.accounts !== 'undefined' ? newFields.accounts : view.accounts; await checkViewAccounts(userId, accounts); const newView = await View.update(userId, view.id, newFields); res.status(200).json(newView); } catch (err) { asyncErr(res, err, 'when updating a view'); } } export async function destroy(req: PreloadedRequest, res: express.Response) { try { const { id: userId } = req.user; if (await isDemoEnabled(userId)) { throw new KError("view deletion isn't allowed in demo mode", 400); } const view = req.preloaded.view; await View.destroy(userId, view.id); res.status(204).end(); } catch (err) { asyncErr(res, err, 'when destroying a view'); } }