Commit c7f091e2 authored by Benjamin Bouvier's avatar Benjamin Bouvier
Browse files

Server: document models a bit more.

parent 0408d563
Pipeline #10285 passed with stage
in 3 minutes and 15 seconds
......@@ -239,9 +239,9 @@ merging as per request`);
operation.date = operation.date || now;
operation.dateImport = now;
let operationType = OperationType.getNameFromWeboobId(sourceOp.type);
let operationType = OperationType.idToName(sourceOp.type);
// The default value of the type is set directly by the operation model
// The default type's value is directly set by the operation model.
if (operationType !== null)
operation.type = operationType;
......
import Emailer from './emailer';
import Notifications from './notifications';
import Account from '../models/account';
import Alert from '../models/alert';
import Config from '../models/config';
import Account from '../models/account';
import Alert from '../models/alert';
import Config from '../models/config';
import { makeLogger, translate as $t, currency } from '../helpers';
......@@ -53,7 +53,7 @@ ${$t('server.email.signature')}
for (let a of accounts) {
accountsMap.set(a.accountNumber, {
title: a.title,
formatter: currency.makeFormat(a.currency || defaultCurrency)
formatCurrency: currency.makeFormat(a.currency || defaultCurrency)
});
}
......@@ -65,8 +65,7 @@ ${$t('server.email.signature')}
// Memoize alerts by account
let alerts;
if (!alertsByAccount.has(operation.bankAccount)) {
alerts = await Alert.byAccountAndType(operation.bankAccount,
'transaction');
alerts = await Alert.byAccountAndType(operation.bankAccount, 'transaction');
alertsByAccount.set(operation.bankAccount, alerts);
} else {
alerts = alertsByAccount.get(operation.bankAccount);
......@@ -78,14 +77,13 @@ ${$t('server.email.signature')}
}
// Set the account information
let { title: accountName, formatter } = accountsMap.get(operation.bankAccount);
let { title: accountName, formatCurrency } = accountsMap.get(operation.bankAccount);
for (let alert of alerts) {
if (!alert.testTransaction(operation))
continue;
let text =
alert.formatOperationMessage(operation, accountName, formatter);
let text = alert.formatOperationMessage(operation, accountName, formatCurrency);
await this.send({
subject: $t('server.alert.operation.title'),
text
......@@ -114,9 +112,8 @@ ${$t('server.email.signature')}
// Set the currency formatter
let curr = account.currency || defaultCurrency;
let currencyFormatter = currency.makeFormat(curr);
let text =
alert.formatAccountMessage(account.title, balance, currencyFormatter);
let formatCurrency = currency.makeFormat(curr);
let text = alert.formatAccountMessage(account.title, balance, formatCurrency);
await this.send({
subject: $t('server.alert.balance.title'),
text
......
import * as americano from 'cozydb';
import { makeLogger, promisify, promisifyModel } from '../helpers';
import {
makeLogger,
promisify,
promisifyModel
} from '../helpers';
let log = makeLogger('models/access');
let Access = americano.getModel('bankaccess', {
// Weboob module name
// External (backend) unique identifier.
bank: String,
// Credentials to connect to the bank's website.
login: String,
password: String,
// Any supplementary fields necessary to connect to the bank's website.
customFields: String,
// Text status indicating whether the last poll was successful or not.
fetchStatus: {
type: String,
default: 'OK'
},
// Don't use! Only used to migrate data
// ************************************************************************
// DEPRECATED.
// ************************************************************************
website: String,
_passwordStillEncrypted: Boolean
});
......@@ -27,8 +37,9 @@ Access = promisifyModel(Access);
let request = promisify(::Access.request);
Access.byBank = async function byBank(bank) {
if (typeof bank !== 'object' || typeof bank.uuid !== 'string')
if (typeof bank !== 'object' || typeof bank.uuid !== 'string') {
log.warn('Access.byBank misuse: bank must be a Bank instance.');
}
let params = {
key: bank.uuid
......@@ -52,18 +63,16 @@ Access.allLike = async function allLike(access) {
// Sync function
Access.prototype.hasPassword = function() {
return (typeof this._passwordStillEncrypted === 'undefined' ||
!this._passwordStillEncrypted) &&
// Can happen after import of kresus data
return (typeof this._passwordStillEncrypted === 'undefined' || !this._passwordStillEncrypted) &&
typeof this.password !== 'undefined';
};
// Can the access be polled
Access.prototype.canBePolled = function() {
return this.fetchStatus !== 'INVALID_PASSWORD' &&
this.fetchStatus !== 'EXPIRED_PASSWORD' &&
this.fetchStatus !== 'INVALID_PARAMETERS' &&
this.fetchStatus !== 'NO_PASSWORD';
this.fetchStatus !== 'EXPIRED_PASSWORD' &&
this.fetchStatus !== 'INVALID_PARAMETERS' &&
this.fetchStatus !== 'NO_PASSWORD';
};
module.exports = Access;
......@@ -6,32 +6,41 @@ import Operation from './operation';
let log = makeLogger('models/account');
let Account = americano.getModel('bankaccount', {
// Unique bank module identifier, which determines which source to use.
// ************************************************************************
// EXTERNAL LINKS
// ************************************************************************
// External (backend) bank module identifier, determining which source to use.
// TODO could be removed, since this is in the linked access?
bank: String,
// Primary key: id of the bankaccess instance.
// Id of the bankaccess instance.
bankAccess: String,
// Account number provided by the source. Acts as an id for other models.
accountNumber: String,
// ************************************************************************
// ACCOUNT INFORMATION
// ************************************************************************
// Date at which the account has been imported.
importDate: Date,
// Label provided by the source.
title: String,
// Amount on the account, at the date at which it has been imported.
initialAmount: Number,
// Account number provided by the source.
accountNumber: String,
// Date at which the account has been polled for the last time.
lastChecked: Date,
// Label describing the account provided by the source.
title: String,
// IBAN provided by the source (facultative).
iban: String,
// Currency used by the account.
currency: String,
// Initial amount on the account.
initialAmount: Number,
// Date at which the account has been polled for the last time.
lastChecked: Date
currency: String
});
Account = promisifyModel(Account);
......@@ -39,8 +48,9 @@ Account = promisifyModel(Account);
let request = promisify(::Account.request);
Account.byBank = async function byBank(bank) {
if (typeof bank !== 'object' || typeof bank.uuid !== 'string')
if (typeof bank !== 'object' || typeof bank.uuid !== 'string') {
log.warn('Account.byBank misuse: bank must be a Bank instance');
}
let params = {
key: bank.uuid
......@@ -49,10 +59,12 @@ Account.byBank = async function byBank(bank) {
};
Account.findMany = async function findMany(accountIds) {
if (!(accountIds instanceof Array))
if (!(accountIds instanceof Array)) {
log.warn('Account.findMany misuse: accountIds must be an Array');
if (accountIds.length && typeof accountIds[0] !== 'string')
}
if (accountIds.length && typeof accountIds[0] !== 'string') {
log.warn('Account.findMany misuse: accountIds must be a [String]');
}
let params = {
keys: accountIds.slice()
......@@ -61,8 +73,9 @@ Account.findMany = async function findMany(accountIds) {
};
Account.byAccountNumber = async function byAccountNumber(accountNumber) {
if (typeof accountNumber !== 'string')
if (typeof accountNumber !== 'string') {
log.warn('Account.byAccountNumber misuse: 1st param must be a string');
}
let params = {
key: accountNumber
......@@ -71,8 +84,9 @@ Account.byAccountNumber = async function byAccountNumber(accountNumber) {
};
Account.byAccess = async function byAccess(access) {
if (typeof access !== 'object' || typeof access.id !== 'string')
if (typeof access !== 'object' || typeof access.id !== 'string') {
log.warn('Account.byAccess misuse: access must be an Access instance');
}
let params = {
key: access.id
......
import * as americano from 'cozydb';
import { makeLogger, promisify, promisifyModel, translate as $t,
formatDateToLocaleString } from '../helpers';
import {
makeLogger,
promisify,
promisifyModel,
translate as $t,
formatDateToLocaleString
} from '../helpers';
let log = makeLogger('models/alert');
let Alert = americano.getModel('bankalert', {
// external (backend) account id
// external (backend) account id.
bankAccount: String,
// possible options are: report, balance, transaction
// possible options are: report, balance, transaction.
type: String,
// only for reports : daily, weekly, monthly
// only for reports : daily, weekly, monthly.
frequency: String,
// only for balance/transaction
// only for balance/transaction.
limit: Number,
// only for balance/transaction: gt, lt
// only for balance/transaction: gt, lt.
order: String,
// date of last alert
// when did the alert get triggered for the last time?
lastTriggeredDate: Date
});
......@@ -30,8 +36,9 @@ let request = promisify(::Alert.request);
let requestDestroy = promisify(::Alert.requestDestroy);
Alert.byAccount = async function byAccount(account) {
if (typeof account !== 'object' || typeof account.id !== 'string')
if (typeof account !== 'object' || typeof account.id !== 'string') {
log.warn('Alert.byAccount misuse: account must be an Account instance');
}
let params = {
key: account.id
......@@ -40,10 +47,12 @@ Alert.byAccount = async function byAccount(account) {
};
Alert.byAccountAndType = async function byAccountAndType(accountID, type) {
if (typeof accountID !== 'string')
if (typeof accountID !== 'string') {
log.warn('Alert.byAccountAndType misuse: accountID must be a string');
if (typeof type !== 'string')
}
if (typeof type !== 'string') {
log.warn('Alert.byAccountAndType misuse: type must be a string');
}
let params = {
key: [accountID, type]
......@@ -52,8 +61,9 @@ Alert.byAccountAndType = async function byAccountAndType(accountID, type) {
};
Alert.reportsByFrequency = async function reportsByFrequency(frequency) {
if (typeof frequency !== 'string')
if (typeof frequency !== 'string') {
log.warn('Alert.reportsByFrequency misuse: frequency must be a string');
}
let params = {
key: ['report', frequency]
......@@ -62,8 +72,9 @@ Alert.reportsByFrequency = async function reportsByFrequency(frequency) {
};
Alert.destroyByAccount = async function destroyByAccount(id) {
if (typeof id !== 'string')
if (typeof id !== 'string') {
log.warn("Alert.destroyByAccount API misuse: id isn't a string");
}
let params = {
key: id,
......@@ -75,8 +86,9 @@ Alert.destroyByAccount = async function destroyByAccount(id) {
// Sync function
Alert.prototype.testTransaction = function(operation) {
if (this.type !== 'transaction')
if (this.type !== 'transaction') {
return false;
}
let alertLimit = +this.limit;
let amount = Math.abs(operation.amount);
......@@ -86,42 +98,47 @@ Alert.prototype.testTransaction = function(operation) {
// Sync function
Alert.prototype.testBalance = function(balance) {
if (this.type !== 'balance')
if (this.type !== 'balance') {
return false;
}
let alertLimit = +this.limit;
return (this.order === 'lt' && balance <= alertLimit) ||
(this.order === 'gt' && balance >= alertLimit);
};
Alert.prototype.formatOperationMessage = function(operation, accountName, currencyFormatter) {
Alert.prototype.formatOperationMessage = function(operation, accountName, formatCurrency) {
let cmp = this.order === 'lt' ?
$t('server.alert.operation.lessThan') :
$t('server.alert.operation.greaterThan');
let amount = currencyFormatter(operation.amount);
let account = accountName;
let title = operation.title;
let amount = formatCurrency(operation.amount);
let date = formatDateToLocaleString(operation.date);
let limit = formatCurrency(this.limit);
return $t('server.alert.operation.content', {
title,
account,
title: operation.title,
account: accountName,
amount,
cmp,
date,
limit: currencyFormatter(this.limit)
limit
});
};
Alert.prototype.formatAccountMessage = function(title, balance, currencyFormatter) {
Alert.prototype.formatAccountMessage = function(title, balance, formatCurrency) {
let cmp = this.order === 'lt' ?
$t('server.alert.balance.lessThan') :
$t('server.alert.balance.greaterThan');
let limit = formatCurrency(this.limit);
let formattedBalance = formatCurrency(balance);
return $t('server.alert.balance.content', {
title,
cmp,
limit: currencyFormatter(this.limit),
balance: currencyFormatter(balance)
limit,
balance: formattedBalance
});
};
......
......@@ -2,15 +2,15 @@ import * as americano from 'cozydb';
import { promisifyModel } from '../helpers';
let Category = americano.getModel('bankcategory', {
// Internal category id.
parentId: String,
// Label of the category.
title: String,
// Hexadecimal RGB format.
color: String,
// Internal category id.
parentId: String,
// Threshold used in the budget section, defined by the user.
threshold: {
type: Number,
......
import * as americano from 'cozydb';
import { makeLogger, promisify, promisifyModel, KError } from '../helpers';
import { testInstall, getVersion as getWeboobVersion } from '../lib/sources/weboob';
import {
makeLogger,
promisify,
promisifyModel,
KError
} from '../helpers';
import {
testInstall,
getVersion as getWeboobVersion
} from '../lib/sources/weboob';
import DefaultSettings from '../shared/default-settings';
let log = makeLogger('models/config');
// A simple key/value configuration pair.
let Config = americano.getModel('kresusconfig', {
name: String,
value: String
......@@ -16,17 +26,20 @@ Config = promisifyModel(Config);
let request = promisify(::Config.request);
// Returns a pair {name, value}
// Returns a pair {name, value} or null if not found.
Config.byName = async function byName(name) {
if (typeof name !== 'string')
if (typeof name !== 'string') {
log.warn('Config.byName misuse: name must be a string');
}
let founds = await request('byName', { key: name });
if (founds && founds.length)
return founds[0];
return null;
};
// Returns a pair {name, value}
// Returns a pair {name, value} or the default value if not found.
async function findOrCreateByName(name, defaultValue) {
let found = await request('byName', { key: name });
if (!found || !found.length) {
......@@ -41,16 +54,18 @@ async function findOrCreateByName(name, defaultValue) {
}
Config.findOrCreateByName = findOrCreateByName;
// Returns a pair {name, value}
// Returns a pair {name, value} or the preset default value if not found.
async function findOrCreateDefault(name) {
if (!DefaultSettings.has(name))
if (!DefaultSettings.has(name)) {
throw new KError(`Setting ${name} has no default value!`);
}
let defaultValue = DefaultSettings.get(name);
return await findOrCreateByName(name, defaultValue);
}
Config.findOrCreateDefault = findOrCreateDefault;
// Returns the boolean value
// Returns a boolean value for a given key, or the preset default.
async function findOrCreateDefaultBooleanValue(name) {
let pair = await findOrCreateDefault(name);
return pair.value === 'true';
......
import * as americano from 'cozydb';
import { makeLogger, promisify, promisifyModel, UNKNOWN_OPERATION_TYPE } from '../helpers';
import {
makeLogger,
promisify,
promisifyModel,
UNKNOWN_OPERATION_TYPE
} from '../helpers';
let log = makeLogger('models/operations');
// Whenever you're adding something to the model, don't forget to modify
// Operation.prototype.mergeFrom. Also, this should be kept in sync with the
// merging of operations on the client side.
// Operation.prototype.mergeWith.
let Operation = americano.getModel('bankoperation', {
// external (backend) account id
// ************************************************************************
// EXTERNAL LINKS
// ************************************************************************
// external (backend) account id.
bankAccount: String,
// internal id
// internal category id.
categoryId: String,
// external (backend) type id or UNKNOWN_OPERATION_TYPE.
type: {
type: String,
default: UNKNOWN_OPERATION_TYPE
},
// ************************************************************************
// TEXT FIELDS
// ************************************************************************
// short summary of what the operation is about.
title: String,
date: Date,
// long description of what the operation is about.
raw: String,
amount: Number,
// description entered by the user.
customLabel: String,
raw: String,
// ************************************************************************
// DATE FIELDS
// ************************************************************************
// date at which the operation has been processed by the backend.
date: Date,
// date at which the operation has been imported into kresus.
dateImport: Date,
customLabel: String,
// ************************************************************************
// OTHER TRANSACTION FIELDS
// ************************************************************************
// Tell if the user has created the operation by itself, or if weboob did.
// amount of the operation, in a certain currency.
amount: Number,
// whether the user has created the operation by itself, or if the backend
// did.
createdByUser: Boolean,
// ************************************************************************
// ATTACHMENTS
// ************************************************************************
// TODO: remove linkPlainEnglish?
// {linkTranslationKey: String, linkPlainEnglish: String, url: String}
attachments: Object,
// TODO merge with attachments?
// Binary is an object containing one field (file) that links to a binary
// document via an id. The binary document has a binary file
// as attachment.
binary: x => x,
// internal id
// Kept for backward compatibility
// ************************************************************************
// DEPRECATED
// ************************************************************************
// internal operation type id.
operationTypeID: String
});
......@@ -53,8 +91,7 @@ let request = promisify(::Operation.request);
let requestDestroy = promisify(::Operation.requestDestroy);
Operation.byAccount = async function byAccount(account) {
if (typeof account !== 'object' ||
typeof account.accountNumber !== 'string') {
if (typeof account !== 'object' || typeof account.accountNumber !== 'string') {
log.warn('Operation.byAccount misuse: account must be an Account');
}
......@@ -65,8 +102,9 @@ Operation.byAccount = async function byAccount(account) {
};
Operation.byAccounts = async function byAccounts(accountNums) {
if (!(accountNums instanceof Array))
if (!(accountNums instanceof Array)) {
log.warn('Operation.byAccounts misuse: accountNums must be an array');
}
let params = {
keys: accountNums
......@@ -75,10 +113,8 @@ Operation.byAccounts = async function byAccounts(accountNums) {
};
Operation.byBankSortedByDate = async function byBankSortedByDate(account) {
if (typeof account !== 'object' ||
typeof account.accountNumber !== 'string') {
log.warn(`Operation.byBankSortedByDate misuse: account must be an
Account`);
if (typeof account !== 'object' || typeof account.accountNumber !== 'string') {
log.warn('Operation.byBankSortedByDate misuse: account must be an Account');
}