server/controllers/accounts.ts
Rama main · réplica de lectura del repositorio original.
import type express from 'express';
import { DEFAULT_ACCOUNT_ID } from '../../shared/settings';
import { asyncErr, KError, makeLogger, unwrap } from '../helpers';
import accountManager from '../lib/accounts-manager';
import { Access, type AccessField, Account, Setting } from '../models';
import { hasForbiddenField } from '../shared/validators';
import { extractUserActionFields } from './accesses';
import { isDemoEnabled } from './instance';
import type { IdentifiedRequest, PreloadedRequest } from './routes';
const log = makeLogger('controllers/accounts');
// Prefills the @account field with a queried bank account.
export async function preloadAccount(
req: IdentifiedRequest<Account>,
res: express.Response,
nextHandler: () => void,
accountId: number
) {
try {
const { id: userId } = req.user;
const account = await Account.find(userId, accountId);
if (!account) {
throw new KError('Bank account not found', 404);
}
if (!req.preloaded) {
req.preloaded = {};
}
req.preloaded.account = account;
nextHandler();
} catch (err) {
asyncErr(res, err, 'when preloading a bank account');
}
}
// Prefills the @targetAccount field with a queried bank account.
export async function preloadTargetAccount(
req: IdentifiedRequest<Account>,
res: express.Response,
nextHandler: () => void,
targetAccountId: number
) {
try {
const { id: userId } = req.user;
const account = await Account.find(userId, targetAccountId);
if (!account) {
throw new KError('Bank account not found', 404);
}
if (!req.preloaded) {
req.preloaded = {};
}
req.preloaded.targetAccount = account;
nextHandler();
} catch (err) {
asyncErr(res, err, 'when preloading a target bank account');
}
}
export async function fixupDefaultAccount(userId: number) {
const found = await Setting.findOrCreateDefault(userId, DEFAULT_ACCOUNT_ID);
if (found && found.value !== '') {
const accountId = parseInt(found.value, 10);
const accountFound = await Account.find(userId, accountId);
if (!accountFound) {
log.info(
"-> Removing the default account setting since the account doesn't exist anymore."
);
await Setting.update(userId, found.id, { value: '' });
}
}
}
// Destroy an account and all its transactions, alerts, and accesses if no other
// accounts are bound to this access.
export async function destroyWithTransactions(userId: number, account: Account) {
log.info(`Removing account ${account.label} from database...`);
log.info(`\t-> Destroy account ${account.label}`);
await Account.destroy(userId, account.id);
await fixupDefaultAccount(userId);
const accounts = await Account.byAccess(userId, { id: account.accessId });
if (accounts && accounts.length === 0) {
log.info('\t-> No other accounts bound: destroying access.');
await Access.destroy(userId, account.accessId);
}
}
export async function update(req: PreloadedRequest<Account>, res: express.Response) {
try {
const { id: userId } = req.user;
const newFields = req.body;
const error = hasForbiddenField(newFields, [
'excludeFromBalance',
'customLabel',
'balance',
'gracePeriod',
]);
if (error) {
throw new KError(`when updating an account: ${error}`, 400);
}
const account = req.preloaded.account;
const newAccount = await Account.update(userId, account.id, newFields);
res.status(200).json(newAccount);
} catch (err) {
asyncErr(res, err, 'when updating an account');
}
}
// Delete account, transactions and alerts.
export async function destroy(req: PreloadedRequest<Account>, res: express.Response) {
try {
const { id: userId } = req.user;
if (await isDemoEnabled(userId)) {
throw new KError("account deletion isn't allowed in demo mode", 400);
}
await destroyWithTransactions(userId, req.preloaded.account);
res.status(204).end();
} catch (err) {
asyncErr(res, err, 'when destroying an account');
}
}
export async function resyncBalance(req: PreloadedRequest<Account>, res: express.Response) {
try {
const { id: userId } = req.user;
const account = req.preloaded.account;
const userActionFields = extractUserActionFields(req.body);
const access = unwrap(await Access.find(userId, account.accessId));
const providedFields: AccessField[] | undefined = req.body.fields;
const isAccessEnabled = access.isEnabled();
if (!isAccessEnabled && typeof providedFields === 'undefined') {
throw new KError(
'when resyncing balance: access disabled and no fields provided by user',
403
);
}
if (typeof providedFields !== 'undefined') {
access.fields = providedFields;
}
const response = await accountManager.resyncAccountBalance(
userId,
account,
access,
/* interactive */ true,
userActionFields
);
if (response.kind === 'user_action') {
res.status(200).json(response);
} else {
const updatedAccount = response.value;
res.status(200).json(updatedAccount);
}
} catch (err) {
asyncErr(res, err, 'when getting balance of a bank account');
}
}
export async function mergeInto(req: PreloadedRequest<Account>, res: express.Response) {
try {
const { id: userId } = req.user;
const account = req.preloaded.account;
const targetAccount = req.preloaded.targetAccount;
if (!account || !targetAccount || account.id === targetAccount.id) {
throw new KError('invalid accounts');
}
// Check that both account are owned by the same access.
if (account.accessId !== targetAccount.accessId) {
throw new KError('accounts merge is only possible for accounts of a same access', 400);
}
const success = await accountManager.mergeExistingAccounts(userId, account, targetAccount);
if (!success) {
throw new KError('accounts could not be merged');
}
res.status(200).json({ newId: targetAccount.id });
} catch (err) {
asyncErr(res, err, 'when merging accounts');
}
}