@actual-app/sync-server 25.5.0 → 25.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (244) hide show
  1. package/{app.js → build/app.js} +4 -5
  2. package/build/bin/actual-server.js +101 -0
  3. package/build/migrations/1694360000000-create-folders.js +21 -0
  4. package/{migrations → build/migrations}/1694360479680-create-account-db.js +2 -4
  5. package/{migrations → build/migrations}/1694362247011-create-secret-table.js +2 -4
  6. package/build/migrations/1702667624000-rename-nordigen-secrets.js +9 -0
  7. package/{migrations → build/migrations}/1718889148000-openid.js +4 -10
  8. package/{migrations → build/migrations}/1719409568000-multiuser.js +10 -26
  9. package/build/src/account-db.js +182 -0
  10. package/build/src/accounts/openid.js +287 -0
  11. package/build/src/accounts/password.js +98 -0
  12. package/build/src/app-account.js +125 -0
  13. package/build/src/app-admin.js +317 -0
  14. package/build/src/app-admin.test.js +303 -0
  15. package/build/src/app-gocardless/app-gocardless.js +193 -0
  16. package/build/src/app-gocardless/bank-factory.js +84 -0
  17. package/build/src/app-gocardless/banks/abanca_caglesmm.js +17 -0
  18. package/build/src/app-gocardless/banks/abnamro_abnanl2a.js +37 -0
  19. package/build/src/app-gocardless/banks/american_express_aesudef1.js +32 -0
  20. package/build/src/app-gocardless/banks/bancsabadell_bsabesbbb.js +22 -0
  21. package/build/src/app-gocardless/banks/bank.interface.js +1 -0
  22. package/build/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js +25 -0
  23. package/build/src/app-gocardless/banks/bankinter_bkbkesmm.js +18 -0
  24. package/build/src/app-gocardless/banks/belfius_gkccbebb.js +13 -0
  25. package/build/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js +48 -0
  26. package/build/src/app-gocardless/banks/bnp_be_gebabebb.js +64 -0
  27. package/build/src/app-gocardless/banks/boursobank_bousfrppxxx.js +73 -0
  28. package/build/src/app-gocardless/banks/cbc_cregbebb.js +27 -0
  29. package/build/src/app-gocardless/banks/commerzbank_cobadeff.js +43 -0
  30. package/build/src/app-gocardless/banks/danskebank_dabno22.js +26 -0
  31. package/build/src/app-gocardless/banks/direkt_heladef1822.js +13 -0
  32. package/build/src/app-gocardless/banks/easybank_bawaatww.js +42 -0
  33. package/build/src/app-gocardless/banks/entercard_swednokk.js +28 -0
  34. package/build/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js +34 -0
  35. package/build/src/app-gocardless/banks/hype_hyeeit22.js +63 -0
  36. package/build/src/app-gocardless/banks/ing_ingbrobu.js +56 -0
  37. package/build/src/app-gocardless/banks/ing_ingddeff.js +34 -0
  38. package/build/src/app-gocardless/banks/ing_pl_ingbplpw.js +29 -0
  39. package/build/src/app-gocardless/banks/integration-bank.js +78 -0
  40. package/build/src/app-gocardless/banks/isybank_itbbitmm.js +13 -0
  41. package/build/src/app-gocardless/banks/kbc_kredbebb.js +26 -0
  42. package/build/src/app-gocardless/banks/lhv-lhvbee22.js +24 -0
  43. package/build/src/app-gocardless/banks/mbank_retail_brexplpw.js +41 -0
  44. package/build/src/app-gocardless/banks/nationwide_naiagb21.js +32 -0
  45. package/build/src/app-gocardless/banks/nbg_ethngraaxxx.js +39 -0
  46. package/build/src/app-gocardless/banks/norwegian_xx_norwnok1.js +61 -0
  47. package/build/src/app-gocardless/banks/revolut_revolt21.js +20 -0
  48. package/build/src/app-gocardless/banks/sandboxfinance_sfin0000.js +21 -0
  49. package/build/src/app-gocardless/banks/seb_kort_bank_ab.js +63 -0
  50. package/build/src/app-gocardless/banks/seb_privat.js +19 -0
  51. package/build/src/app-gocardless/banks/sparnord_spnodk22.js +19 -0
  52. package/build/src/app-gocardless/banks/spk_karlsruhe_karsde66.js +48 -0
  53. package/build/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js +25 -0
  54. package/build/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js +14 -0
  55. package/build/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js +36 -0
  56. package/build/src/app-gocardless/banks/swedbank_habalv22.js +30 -0
  57. package/build/src/app-gocardless/banks/tests/abanca_caglesmm.spec.js +17 -0
  58. package/build/src/app-gocardless/banks/tests/abnamro_abnanl2a.spec.js +45 -0
  59. package/build/src/app-gocardless/banks/tests/bancsabadell_bsabesbbb.spec.js +41 -0
  60. package/build/src/app-gocardless/banks/tests/belfius_gkccbebb.spec.js +16 -0
  61. package/build/src/app-gocardless/banks/tests/boursobank_bousfrppxxx.spec.js +102 -0
  62. package/build/src/app-gocardless/banks/tests/cbc_cregbebb.spec.js +24 -0
  63. package/build/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js +105 -0
  64. package/build/src/app-gocardless/banks/tests/easybank_bawaatww.spec.js +36 -0
  65. package/build/src/app-gocardless/banks/tests/fortuneo_ftnofrp1xxx.spec.js +159 -0
  66. package/build/src/app-gocardless/banks/tests/ing_ingddeff.spec.js +267 -0
  67. package/build/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js +186 -0
  68. package/build/src/app-gocardless/banks/tests/integration_bank.spec.js +127 -0
  69. package/build/src/app-gocardless/banks/tests/kbc_kredbebb.spec.js +24 -0
  70. package/build/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js +50 -0
  71. package/build/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js +156 -0
  72. package/build/src/app-gocardless/banks/tests/nationwide_naiagb21.spec.js +64 -0
  73. package/build/src/app-gocardless/banks/tests/nbg_ethngraaxxx.spec.js +36 -0
  74. package/build/src/app-gocardless/banks/tests/revolut_revolt21.spec.js +30 -0
  75. package/build/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js +112 -0
  76. package/build/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js +214 -0
  77. package/build/src/app-gocardless/banks/tests/ssk_dusseldorf_dussdeddxxx.spec.js +60 -0
  78. package/build/src/app-gocardless/banks/tests/swedbank_habalv22.spec.js +45 -0
  79. package/build/src/app-gocardless/banks/tests/virgin_nrnbgb22.spec.js +36 -0
  80. package/{src → build/src}/app-gocardless/banks/util/escape-regexp.js +1 -1
  81. package/{src → build/src}/app-gocardless/banks/util/extract-payeeName-from-remittanceInfo.js +11 -16
  82. package/build/src/app-gocardless/banks/virgin_nrnbgb22.js +31 -0
  83. package/build/src/app-gocardless/errors.js +67 -0
  84. package/build/src/app-gocardless/gocardless-node.types.js +1 -0
  85. package/build/src/app-gocardless/gocardless.types.js +1 -0
  86. package/build/src/app-gocardless/services/gocardless-service.js +504 -0
  87. package/build/src/app-gocardless/services/tests/fixtures.js +165 -0
  88. package/build/src/app-gocardless/services/tests/gocardless-service.spec.js +387 -0
  89. package/build/src/app-gocardless/tests/bank-factory.spec.js +13 -0
  90. package/build/src/app-gocardless/tests/utils.spec.js +158 -0
  91. package/build/src/app-gocardless/util/handle-error.js +15 -0
  92. package/build/src/app-gocardless/utils.js +41 -0
  93. package/build/src/app-openid.js +83 -0
  94. package/build/src/app-pluggyai/app-pluggyai.js +164 -0
  95. package/build/src/app-pluggyai/pluggyai-service.js +97 -0
  96. package/build/src/app-secrets.js +48 -0
  97. package/build/src/app-simplefin/app-simplefin.js +335 -0
  98. package/build/src/app-sync/errors.js +12 -0
  99. package/build/src/app-sync/services/files-service.js +158 -0
  100. package/build/src/app-sync/tests/services/files-service.test.js +192 -0
  101. package/build/src/app-sync/validation.js +65 -0
  102. package/build/src/app-sync.js +302 -0
  103. package/build/src/app-sync.test.js +655 -0
  104. package/build/src/app.js +138 -0
  105. package/build/src/config-types.js +1 -0
  106. package/build/src/db.js +50 -0
  107. package/build/src/load-config.js +274 -0
  108. package/build/src/migrations.js +23 -0
  109. package/build/src/scripts/disable-openid.js +31 -0
  110. package/build/src/scripts/enable-openid.js +36 -0
  111. package/build/src/scripts/health-check.js +16 -0
  112. package/build/src/scripts/reset-password.js +40 -0
  113. package/build/src/scripts/run-migrations.js +6 -0
  114. package/build/src/secrets.test.js +68 -0
  115. package/build/src/services/secrets-service.js +79 -0
  116. package/build/src/services/user-service.js +201 -0
  117. package/build/src/sync-simple.js +68 -0
  118. package/{src → build/src}/util/hash.js +1 -2
  119. package/build/src/util/middlewares.js +49 -0
  120. package/{src → build/src}/util/paths.js +3 -6
  121. package/build/src/util/payee-name.js +37 -0
  122. package/build/src/util/prompt.js +70 -0
  123. package/build/src/util/title/index.js +43 -0
  124. package/build/src/util/title/lower-case.js +90 -0
  125. package/build/src/util/title/specials.js +21 -0
  126. package/build/src/util/validate-user.js +55 -0
  127. package/package.json +32 -36
  128. package/bin/actual-server.js +0 -117
  129. package/migrations/1694360000000-create-folders.js +0 -25
  130. package/migrations/1702667624000-rename-nordigen-secrets.js +0 -19
  131. package/src/account-db.js +0 -239
  132. package/src/accounts/openid.js +0 -368
  133. package/src/accounts/password.js +0 -149
  134. package/src/app-account.js +0 -155
  135. package/src/app-admin.js +0 -410
  136. package/src/app-admin.test.js +0 -381
  137. package/src/app-gocardless/app-gocardless.js +0 -274
  138. package/src/app-gocardless/bank-factory.js +0 -91
  139. package/src/app-gocardless/banks/abanca_caglesmm.js +0 -22
  140. package/src/app-gocardless/banks/abnamro_abnanl2a.js +0 -57
  141. package/src/app-gocardless/banks/american_express_aesudef1.js +0 -40
  142. package/src/app-gocardless/banks/bancsabadell_bsabesbbb.js +0 -31
  143. package/src/app-gocardless/banks/bank.interface.ts +0 -51
  144. package/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js +0 -39
  145. package/src/app-gocardless/banks/bankinter_bkbkesmm.js +0 -24
  146. package/src/app-gocardless/banks/belfius_gkccbebb.js +0 -17
  147. package/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js +0 -61
  148. package/src/app-gocardless/banks/bnp_be_gebabebb.js +0 -73
  149. package/src/app-gocardless/banks/cbc_cregbebb.js +0 -34
  150. package/src/app-gocardless/banks/commerzbank_cobadeff.js +0 -54
  151. package/src/app-gocardless/banks/danskebank_dabno22.js +0 -39
  152. package/src/app-gocardless/banks/direkt_heladef1822.js +0 -18
  153. package/src/app-gocardless/banks/easybank_bawaatww.js +0 -50
  154. package/src/app-gocardless/banks/entercard_swednokk.js +0 -40
  155. package/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js +0 -46
  156. package/src/app-gocardless/banks/hype_hyeeit22.js +0 -74
  157. package/src/app-gocardless/banks/ing_ingbrobu.js +0 -70
  158. package/src/app-gocardless/banks/ing_ingddeff.js +0 -47
  159. package/src/app-gocardless/banks/ing_pl_ingbplpw.js +0 -46
  160. package/src/app-gocardless/banks/integration-bank.js +0 -115
  161. package/src/app-gocardless/banks/isybank_itbbitmm.js +0 -18
  162. package/src/app-gocardless/banks/kbc_kredbebb.js +0 -33
  163. package/src/app-gocardless/banks/lhv-lhvbee22.js +0 -36
  164. package/src/app-gocardless/banks/mbank_retail_brexplpw.js +0 -56
  165. package/src/app-gocardless/banks/nationwide_naiagb21.js +0 -46
  166. package/src/app-gocardless/banks/nbg_ethngraaxxx.js +0 -51
  167. package/src/app-gocardless/banks/norwegian_xx_norwnok1.js +0 -74
  168. package/src/app-gocardless/banks/revolut_revolt21.js +0 -37
  169. package/src/app-gocardless/banks/sandboxfinance_sfin0000.js +0 -28
  170. package/src/app-gocardless/banks/seb_kort_bank_ab.js +0 -59
  171. package/src/app-gocardless/banks/seb_privat.js +0 -29
  172. package/src/app-gocardless/banks/sparnord_spnodk22.js +0 -24
  173. package/src/app-gocardless/banks/spk_karlsruhe_karsde66.js +0 -61
  174. package/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js +0 -30
  175. package/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js +0 -19
  176. package/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js +0 -50
  177. package/src/app-gocardless/banks/swedbank_habalv22.js +0 -47
  178. package/src/app-gocardless/banks/tests/abanca_caglesmm.spec.js +0 -21
  179. package/src/app-gocardless/banks/tests/abnamro_abnanl2a.spec.js +0 -61
  180. package/src/app-gocardless/banks/tests/bancsabadell_bsabesbbb.spec.js +0 -53
  181. package/src/app-gocardless/banks/tests/belfius_gkccbebb.spec.js +0 -22
  182. package/src/app-gocardless/banks/tests/cbc_cregbebb.spec.js +0 -34
  183. package/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js +0 -133
  184. package/src/app-gocardless/banks/tests/easybank_bawaatww.spec.js +0 -54
  185. package/src/app-gocardless/banks/tests/fortuneo_ftnofrp1xxx.spec.js +0 -206
  186. package/src/app-gocardless/banks/tests/ing_ingddeff.spec.js +0 -302
  187. package/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js +0 -202
  188. package/src/app-gocardless/banks/tests/integration_bank.spec.js +0 -156
  189. package/src/app-gocardless/banks/tests/kbc_kredbebb.spec.js +0 -38
  190. package/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js +0 -68
  191. package/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js +0 -171
  192. package/src/app-gocardless/banks/tests/nationwide_naiagb21.spec.js +0 -105
  193. package/src/app-gocardless/banks/tests/nbg_ethngraaxxx.spec.js +0 -48
  194. package/src/app-gocardless/banks/tests/revolut_revolt21.spec.js +0 -42
  195. package/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js +0 -133
  196. package/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js +0 -255
  197. package/src/app-gocardless/banks/tests/ssk_dusseldorf_dussdeddxxx.spec.js +0 -100
  198. package/src/app-gocardless/banks/tests/swedbank_habalv22.spec.js +0 -57
  199. package/src/app-gocardless/banks/tests/virgin_nrnbgb22.spec.js +0 -54
  200. package/src/app-gocardless/banks/virgin_nrnbgb22.js +0 -39
  201. package/src/app-gocardless/errors.js +0 -84
  202. package/src/app-gocardless/gocardless-node.types.ts +0 -497
  203. package/src/app-gocardless/gocardless.types.ts +0 -93
  204. package/src/app-gocardless/link.html +0 -18
  205. package/src/app-gocardless/services/gocardless-service.js +0 -620
  206. package/src/app-gocardless/services/tests/fixtures.js +0 -181
  207. package/src/app-gocardless/services/tests/gocardless-service.spec.js +0 -537
  208. package/src/app-gocardless/tests/bank-factory.spec.js +0 -20
  209. package/src/app-gocardless/tests/utils.spec.js +0 -162
  210. package/src/app-gocardless/util/handle-error.js +0 -16
  211. package/src/app-gocardless/utils.js +0 -45
  212. package/src/app-openid.js +0 -108
  213. package/src/app-pluggyai/app-pluggyai.js +0 -215
  214. package/src/app-pluggyai/pluggyai-service.js +0 -120
  215. package/src/app-secrets.js +0 -61
  216. package/src/app-simplefin/app-simplefin.js +0 -405
  217. package/src/app-sync/errors.js +0 -13
  218. package/src/app-sync/services/files-service.js +0 -243
  219. package/src/app-sync/tests/services/files-service.test.js +0 -247
  220. package/src/app-sync/validation.js +0 -77
  221. package/src/app-sync.js +0 -391
  222. package/src/app-sync.test.js +0 -877
  223. package/src/app.js +0 -149
  224. package/src/config-types.ts +0 -44
  225. package/src/db.js +0 -58
  226. package/src/load-config.js +0 -307
  227. package/src/migrations.js +0 -36
  228. package/src/run-migrations.js +0 -8
  229. package/src/scripts/disable-openid.js +0 -44
  230. package/src/scripts/enable-openid.js +0 -53
  231. package/src/scripts/health-check.js +0 -23
  232. package/src/scripts/reset-password.js +0 -51
  233. package/src/secrets.test.js +0 -83
  234. package/src/services/secrets-service.js +0 -94
  235. package/src/services/user-service.js +0 -272
  236. package/src/sync-simple.js +0 -95
  237. package/src/util/middlewares.js +0 -62
  238. package/src/util/payee-name.js +0 -45
  239. package/src/util/prompt.js +0 -88
  240. package/src/util/title/index.js +0 -59
  241. package/src/util/title/lower-case.js +0 -93
  242. package/src/util/title/specials.js +0 -21
  243. package/src/util/validate-user.js +0 -68
  244. /package/{src → build/src}/sql/messages.sql +0 -0
@@ -0,0 +1,83 @@
1
+ import express from 'express';
2
+ import { disableOpenID, enableOpenID, isAdmin } from './account-db.js';
3
+ import { isValidRedirectUrl, loginWithOpenIdFinalize, } from './accounts/openid.js';
4
+ import { checkPassword } from './accounts/password.js';
5
+ import * as UserService from './services/user-service.js';
6
+ import { errorMiddleware, requestLoggerMiddleware, validateSessionMiddleware, } from './util/middlewares.js';
7
+ const app = express();
8
+ app.use(express.json());
9
+ app.use(express.urlencoded({ extended: true }));
10
+ app.use(requestLoggerMiddleware);
11
+ export { app as handlers };
12
+ app.post('/enable', validateSessionMiddleware, async (req, res) => {
13
+ if (!isAdmin(res.locals.user_id)) {
14
+ res.status(403).send({
15
+ status: 'error',
16
+ reason: 'forbidden',
17
+ details: 'permission-not-found',
18
+ });
19
+ return;
20
+ }
21
+ const { error } = (await enableOpenID(req.body)) || {};
22
+ if (error) {
23
+ res.status(500).send({ status: 'error', reason: error });
24
+ return;
25
+ }
26
+ res.send({ status: 'ok' });
27
+ });
28
+ app.post('/disable', validateSessionMiddleware, async (req, res) => {
29
+ if (!isAdmin(res.locals.user_id)) {
30
+ res.status(403).send({
31
+ status: 'error',
32
+ reason: 'forbidden',
33
+ details: 'permission-not-found',
34
+ });
35
+ return;
36
+ }
37
+ const { error } = (await disableOpenID(req.body)) || {};
38
+ if (error) {
39
+ res.status(401).send({ status: 'error', reason: error });
40
+ return;
41
+ }
42
+ res.send({ status: 'ok' });
43
+ });
44
+ app.post('/config', async (req, res) => {
45
+ const { cnt: ownerCount } = UserService.getOwnerCount() || {};
46
+ if (ownerCount > 0) {
47
+ res.status(400).send({ status: 'error', reason: 'already-bootstraped' });
48
+ return;
49
+ }
50
+ if (!checkPassword(req.body.password)) {
51
+ res.status(400).send({ status: 'error', reason: 'invalid-password' });
52
+ return;
53
+ }
54
+ const auth = UserService.getOpenIDConfig();
55
+ if (!auth) {
56
+ res
57
+ .status(500)
58
+ .send({ status: 'error', reason: 'OpenID configuration not found' });
59
+ return;
60
+ }
61
+ try {
62
+ const openIdConfig = JSON.parse(auth.extra_data);
63
+ res.send({ status: 'ok', data: { openId: openIdConfig } });
64
+ }
65
+ catch (error) {
66
+ res
67
+ .status(500)
68
+ .send({ status: 'error', reason: 'Invalid OpenID configuration' });
69
+ }
70
+ });
71
+ app.get('/callback', async (req, res) => {
72
+ const { error, url } = await loginWithOpenIdFinalize(req.query);
73
+ if (error) {
74
+ res.status(400).send({ status: 'error', reason: error });
75
+ return;
76
+ }
77
+ if (!isValidRedirectUrl(url)) {
78
+ res.status(400).send({ status: 'error', reason: 'Invalid redirect URL' });
79
+ return;
80
+ }
81
+ res.redirect(url);
82
+ });
83
+ app.use(errorMiddleware);
@@ -0,0 +1,164 @@
1
+ import express from 'express';
2
+ import { handleError } from '../app-gocardless/util/handle-error.js';
3
+ import { SecretName, secretsService } from '../services/secrets-service.js';
4
+ import { requestLoggerMiddleware } from '../util/middlewares.js';
5
+ import { pluggyaiService } from './pluggyai-service.js';
6
+ const app = express();
7
+ export { app as handlers };
8
+ app.use(express.json());
9
+ app.use(requestLoggerMiddleware);
10
+ app.post('/status', handleError(async (req, res) => {
11
+ const clientId = secretsService.get(SecretName.pluggyai_clientId);
12
+ const configured = clientId != null;
13
+ res.send({
14
+ status: 'ok',
15
+ data: {
16
+ configured,
17
+ },
18
+ });
19
+ }));
20
+ app.post('/accounts', handleError(async (req, res) => {
21
+ try {
22
+ const itemIds = secretsService
23
+ .get(SecretName.pluggyai_itemIds)
24
+ .split(',')
25
+ .map(item => item.trim());
26
+ let accounts = [];
27
+ for (const item of itemIds) {
28
+ const partial = await pluggyaiService.getAccountsByItemId(item);
29
+ accounts = accounts.concat(partial.results);
30
+ }
31
+ res.send({
32
+ status: 'ok',
33
+ data: {
34
+ accounts,
35
+ },
36
+ });
37
+ }
38
+ catch (error) {
39
+ res.send({
40
+ status: 'ok',
41
+ data: {
42
+ error: error.message,
43
+ },
44
+ });
45
+ }
46
+ }));
47
+ app.post('/transactions', handleError(async (req, res) => {
48
+ const { accountId, startDate } = req.body || {};
49
+ try {
50
+ const transactions = await pluggyaiService.getTransactions(accountId, startDate);
51
+ const account = await pluggyaiService.getAccountById(accountId);
52
+ let startingBalance = parseInt(Math.round(account.balance * 100).toString());
53
+ if (account.type === 'CREDIT') {
54
+ startingBalance = -startingBalance;
55
+ }
56
+ const date = getDate(new Date(account.updatedAt));
57
+ const balances = [
58
+ {
59
+ balanceAmount: {
60
+ amount: startingBalance,
61
+ currency: account.currencyCode,
62
+ },
63
+ balanceType: 'expected',
64
+ referenceDate: date,
65
+ },
66
+ ];
67
+ const all = [];
68
+ const booked = [];
69
+ const pending = [];
70
+ for (const trans of transactions) {
71
+ const newTrans = {};
72
+ newTrans.booked = !(trans.status === 'PENDING');
73
+ const transactionDate = new Date(trans.date);
74
+ if (transactionDate < startDate && !trans.sandbox) {
75
+ continue;
76
+ }
77
+ newTrans.date = getDate(transactionDate);
78
+ newTrans.payeeName = getPayeeName(trans);
79
+ newTrans.notes = trans.descriptionRaw || trans.description;
80
+ if (account.type === 'CREDIT') {
81
+ if (trans.amountInAccountCurrency) {
82
+ trans.amountInAccountCurrency *= -1;
83
+ }
84
+ trans.amount *= -1;
85
+ }
86
+ let amountInCurrency = trans.amountInAccountCurrency ?? trans.amount;
87
+ amountInCurrency = Math.round(amountInCurrency * 100) / 100;
88
+ newTrans.transactionAmount = {
89
+ amount: amountInCurrency,
90
+ currency: trans.currencyCode,
91
+ };
92
+ newTrans.transactionId = trans.id;
93
+ newTrans.sortOrder = transactionDate.getTime();
94
+ delete trans.amount;
95
+ const finalTrans = { ...flattenObject(trans), ...newTrans };
96
+ if (newTrans.booked) {
97
+ booked.push(finalTrans);
98
+ }
99
+ else {
100
+ pending.push(finalTrans);
101
+ }
102
+ all.push(finalTrans);
103
+ }
104
+ const sortFunction = (a, b) => b.sortOrder - a.sortOrder;
105
+ const bookedSorted = booked.sort(sortFunction);
106
+ const pendingSorted = pending.sort(sortFunction);
107
+ const allSorted = all.sort(sortFunction);
108
+ res.send({
109
+ status: 'ok',
110
+ data: {
111
+ balances,
112
+ startingBalance,
113
+ transactions: {
114
+ all: allSorted,
115
+ booked: bookedSorted,
116
+ pending: pendingSorted,
117
+ },
118
+ },
119
+ });
120
+ }
121
+ catch (error) {
122
+ res.send({
123
+ status: 'ok',
124
+ data: {
125
+ error: error.message,
126
+ },
127
+ });
128
+ }
129
+ return;
130
+ }));
131
+ function getDate(date) {
132
+ return date.toISOString().split('T')[0];
133
+ }
134
+ function flattenObject(obj, prefix = '') {
135
+ const result = {};
136
+ for (const [key, value] of Object.entries(obj)) {
137
+ const newKey = prefix ? `${prefix}.${key}` : key;
138
+ if (value === null) {
139
+ continue;
140
+ }
141
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
142
+ Object.assign(result, flattenObject(value, newKey));
143
+ }
144
+ else {
145
+ result[newKey] = value;
146
+ }
147
+ }
148
+ return result;
149
+ }
150
+ function getPayeeName(trans) {
151
+ if (trans.merchant && (trans.merchant.name || trans.merchant.businessName)) {
152
+ return trans.merchant.name || trans.merchant.businessName || '';
153
+ }
154
+ if (trans.paymentData) {
155
+ const { receiver, payer } = trans.paymentData;
156
+ if (trans.type === 'DEBIT' && receiver) {
157
+ return receiver.name || receiver.documentNumber?.value || '';
158
+ }
159
+ if (trans.type === 'CREDIT' && payer) {
160
+ return payer.name || payer.documentNumber?.value || '';
161
+ }
162
+ }
163
+ return '';
164
+ }
@@ -0,0 +1,97 @@
1
+ import { PluggyClient } from 'pluggy-sdk';
2
+ import { SecretName, secretsService } from '../services/secrets-service.js';
3
+ let pluggyClient = null;
4
+ function getPluggyClient() {
5
+ if (!pluggyClient) {
6
+ const clientId = secretsService.get(SecretName.pluggyai_clientId);
7
+ const clientSecret = secretsService.get(SecretName.pluggyai_clientSecret);
8
+ pluggyClient = new PluggyClient({
9
+ clientId,
10
+ clientSecret,
11
+ });
12
+ }
13
+ return pluggyClient;
14
+ }
15
+ export const pluggyaiService = {
16
+ isConfigured: () => {
17
+ return !!(secretsService.get(SecretName.pluggyai_clientId) &&
18
+ secretsService.get(SecretName.pluggyai_clientSecret) &&
19
+ secretsService.get(SecretName.pluggyai_itemIds));
20
+ },
21
+ getAccountsByItemId: async (itemId) => {
22
+ try {
23
+ const client = getPluggyClient();
24
+ const { results, total, ...rest } = await client.fetchAccounts(itemId);
25
+ return {
26
+ results,
27
+ total,
28
+ ...rest,
29
+ hasError: false,
30
+ errors: {},
31
+ };
32
+ }
33
+ catch (error) {
34
+ console.error(`Error fetching accounts: ${error.message}`);
35
+ throw error;
36
+ }
37
+ },
38
+ getAccountById: async (accountId) => {
39
+ try {
40
+ const client = getPluggyClient();
41
+ const account = await client.fetchAccount(accountId);
42
+ return {
43
+ ...account,
44
+ hasError: false,
45
+ errors: {},
46
+ };
47
+ }
48
+ catch (error) {
49
+ console.error(`Error fetching account: ${error.message}`);
50
+ throw error;
51
+ }
52
+ },
53
+ getTransactionsByAccountId: async (accountId, startDate, pageSize, page) => {
54
+ try {
55
+ const client = getPluggyClient();
56
+ const account = await pluggyaiService.getAccountById(accountId);
57
+ // the sandbox data doesn't move the dates automatically so the
58
+ // transactions are often older than 90 days. The owner on one of the
59
+ // sandbox accounts is set to John Doe so in these cases we'll ignore
60
+ // the start date.
61
+ const sandboxAccount = account.owner === 'John Doe';
62
+ if (sandboxAccount)
63
+ startDate = '2000-01-01';
64
+ const transactions = await client.fetchTransactions(accountId, {
65
+ from: startDate,
66
+ pageSize,
67
+ page,
68
+ });
69
+ if (sandboxAccount) {
70
+ transactions.results = transactions.results.map(t => ({
71
+ ...t,
72
+ sandbox: true,
73
+ }));
74
+ }
75
+ return {
76
+ ...transactions,
77
+ hasError: false,
78
+ errors: {},
79
+ };
80
+ }
81
+ catch (error) {
82
+ console.error(`Error fetching transactions: ${error.message}`);
83
+ throw error;
84
+ }
85
+ },
86
+ getTransactions: async (accountId, startDate) => {
87
+ let transactions = [];
88
+ let result = await pluggyaiService.getTransactionsByAccountId(accountId, startDate, 500, 1);
89
+ transactions = transactions.concat(result.results);
90
+ const totalPages = result.totalPages;
91
+ while (result.page !== totalPages) {
92
+ result = await pluggyaiService.getTransactionsByAccountId(accountId, startDate, 500, result.page + 1);
93
+ transactions = transactions.concat(result.results);
94
+ }
95
+ return transactions;
96
+ },
97
+ };
@@ -0,0 +1,48 @@
1
+ import express from 'express';
2
+ import { getAccountDb, isAdmin } from './account-db.js';
3
+ import { secretsService } from './services/secrets-service.js';
4
+ import { requestLoggerMiddleware, validateSessionMiddleware, } from './util/middlewares.js';
5
+ const app = express();
6
+ export { app as handlers };
7
+ app.use(express.json());
8
+ app.use(requestLoggerMiddleware);
9
+ app.use(validateSessionMiddleware);
10
+ app.post('/', async (req, res) => {
11
+ let method;
12
+ try {
13
+ const result = getAccountDb().first('SELECT method FROM auth WHERE active = 1');
14
+ method = result?.method;
15
+ }
16
+ catch (error) {
17
+ console.error('Failed to fetch auth method:', error);
18
+ return res.status(500).send({
19
+ status: 'error',
20
+ reason: 'database-error',
21
+ details: 'Failed to validate authentication method',
22
+ });
23
+ }
24
+ const { name, value } = req.body || {};
25
+ if (method === 'openid') {
26
+ const canSaveSecrets = isAdmin(res.locals.user_id);
27
+ if (!canSaveSecrets) {
28
+ res.status(403).send({
29
+ status: 'error',
30
+ reason: 'not-admin',
31
+ details: 'You have to be admin to set secrets',
32
+ });
33
+ return;
34
+ }
35
+ }
36
+ secretsService.set(name, value);
37
+ res.status(200).send({ status: 'ok' });
38
+ });
39
+ app.get('/:name', async (req, res) => {
40
+ const name = req.params.name;
41
+ const keyExists = secretsService.exists(name);
42
+ if (keyExists) {
43
+ res.sendStatus(204);
44
+ }
45
+ else {
46
+ res.status(404).send('key not found');
47
+ }
48
+ });