@actual-app/core 26.3.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 (358) hide show
  1. package/.swcrc +11 -0
  2. package/bin/build-browser +40 -0
  3. package/bin/copy-migrations +9 -0
  4. package/db.sqlite +0 -0
  5. package/default-db.sqlite +0 -0
  6. package/migrations/.force-copy-windows +0 -0
  7. package/migrations/1548957970627_remove-db-version.sql +5 -0
  8. package/migrations/1550601598648_payees.sql +23 -0
  9. package/migrations/1555786194328_remove_category_group_unique.sql +25 -0
  10. package/migrations/1561751833510_indexes.sql +7 -0
  11. package/migrations/1567699552727_budget.sql +38 -0
  12. package/migrations/1582384163573_cleared.sql +6 -0
  13. package/migrations/1597756566448_rules.sql +10 -0
  14. package/migrations/1608652596043_parent_field.sql +13 -0
  15. package/migrations/1608652596044_trans_views.sql +56 -0
  16. package/migrations/1612625548236_optimize.sql +7 -0
  17. package/migrations/1614782639336_trans_views2.sql +33 -0
  18. package/migrations/1615745967948_meta.sql +10 -0
  19. package/migrations/1616167010796_accounts_order.sql +5 -0
  20. package/migrations/1618975177358_schedules.sql +28 -0
  21. package/migrations/1632571489012_remove_cache.js +136 -0
  22. package/migrations/1679728867040_rules_conditions.sql +5 -0
  23. package/migrations/1681115033845_add_schedule_name.sql +5 -0
  24. package/migrations/1682974838138_remove_payee_rules.sql +5 -0
  25. package/migrations/1685007876842_add_category_hidden.sql +6 -0
  26. package/migrations/1686139660866_remove_account_type.sql +5 -0
  27. package/migrations/1688749527273_transaction_filters.sql +10 -0
  28. package/migrations/1688841238000_add_account_type.sql +5 -0
  29. package/migrations/1691233396000_add_schedule_next_date_tombstone.sql +5 -0
  30. package/migrations/1694438752000_add_goal_targets.sql +7 -0
  31. package/migrations/1697046240000_add_reconciled.sql +5 -0
  32. package/migrations/1704572023730_add_account_sync_source.sql +5 -0
  33. package/migrations/1704572023731_add_missing_goCardless_sync_source.sql +9 -0
  34. package/migrations/1707267033000_reports.sql +28 -0
  35. package/migrations/1712784523000_unhide_input_group.sql +8 -0
  36. package/migrations/1716359441000_include_current.sql +5 -0
  37. package/migrations/1720310586000_link_transfer_schedules.sql +19 -0
  38. package/migrations/1720664867241_add_payee_favorite.sql +5 -0
  39. package/migrations/1720665000000_goal_context.sql +6 -0
  40. package/migrations/1722717601000_reports_move_selected_categories.js +55 -0
  41. package/migrations/1722804019000_create_dashboard_table.js +69 -0
  42. package/migrations/1723665565000_prefs.js +59 -0
  43. package/migrations/1730744182000_fix_dashboard_table.sql +7 -0
  44. package/migrations/1736640000000_custom_report_sorting.sql +7 -0
  45. package/migrations/1737158400000_add_learn_categories_to_payees.sql +5 -0
  46. package/migrations/1738491452000_sorting_rename.sql +13 -0
  47. package/migrations/1739139550000_bank_sync_page.sql +7 -0
  48. package/migrations/1740506588539_add_last_reconciled_at.sql +5 -0
  49. package/migrations/1745425408000_update_budgetType_pref.sql +7 -0
  50. package/migrations/1749799110000_add_tags.sql +10 -0
  51. package/migrations/1749799110001_tags_tombstone.sql +5 -0
  52. package/migrations/1754611200000_add_category_template_settings.sql +5 -0
  53. package/migrations/1759260219000_add_trim_interval_report_setting.sql +6 -0
  54. package/migrations/1759842823172_add_isGlobal_to_preferences.sql +1 -0
  55. package/migrations/1762178745667_rename_csv_skip_lines_pref.sql +8 -0
  56. package/migrations/1765518577215_multiple_dashboards.js +30 -0
  57. package/migrations/1768872504000_add_payee_locations.sql +21 -0
  58. package/package.json +128 -0
  59. package/src/mocks/arbitrary-schema.ts +162 -0
  60. package/src/mocks/budget.ts +901 -0
  61. package/src/mocks/files/8859-1.qfx +63 -0
  62. package/src/mocks/files/best.data-ever$.QFX +124 -0
  63. package/src/mocks/files/big.data.QiF +91 -0
  64. package/src/mocks/files/budgets/.commit-to-git +0 -0
  65. package/src/mocks/files/camt/camt.053.payee-memo.xml +127 -0
  66. package/src/mocks/files/camt/camt.053.xml +463 -0
  67. package/src/mocks/files/credit-card.ofx +11 -0
  68. package/src/mocks/files/data-multi-decimal.ofx +64 -0
  69. package/src/mocks/files/data-payee-memo.ofx +75 -0
  70. package/src/mocks/files/data-payee-memo.qif +17 -0
  71. package/src/mocks/files/data.ofx +124 -0
  72. package/src/mocks/files/data.qfx +124 -0
  73. package/src/mocks/files/data.qif +91 -0
  74. package/src/mocks/files/default-budget-template/db.sqlite +0 -0
  75. package/src/mocks/files/default-budget-template/metadata.json +6 -0
  76. package/src/mocks/files/html-vals.qfx +17 -0
  77. package/src/mocks/index.ts +221 -0
  78. package/src/mocks/migrations/1508717984291_up_add-poop.sql +13 -0
  79. package/src/mocks/migrations/1508718036311_up_modify-poop.sql +2 -0
  80. package/src/mocks/migrations/1508727787513_remove-is_income.sql +15 -0
  81. package/src/mocks/random.ts +16 -0
  82. package/src/mocks/setup.ts +180 -0
  83. package/src/mocks/spreadsheet.ts +101 -0
  84. package/src/mocks/util.ts +82 -0
  85. package/src/platform/client/connection/README.md +3 -0
  86. package/src/platform/client/connection/__mocks__/index.ts +67 -0
  87. package/src/platform/client/connection/index-types.ts +95 -0
  88. package/src/platform/client/connection/index.browser.ts +213 -0
  89. package/src/platform/client/connection/index.ts +155 -0
  90. package/src/platform/client/undo/index.ts +59 -0
  91. package/src/platform/exceptions/__mocks__/index.ts +7 -0
  92. package/src/platform/exceptions/index.ts +9 -0
  93. package/src/platform/server/asyncStorage/__mocks__/index.ts +50 -0
  94. package/src/platform/server/asyncStorage/index-types.ts +35 -0
  95. package/src/platform/server/asyncStorage/index.api.ts +2 -0
  96. package/src/platform/server/asyncStorage/index.electron.ts +88 -0
  97. package/src/platform/server/asyncStorage/index.ts +126 -0
  98. package/src/platform/server/connection/README.md +3 -0
  99. package/src/platform/server/connection/__mocks__/index.ts +15 -0
  100. package/src/platform/server/connection/index-types.ts +20 -0
  101. package/src/platform/server/connection/index.api.ts +13 -0
  102. package/src/platform/server/connection/index.electron.ts +102 -0
  103. package/src/platform/server/connection/index.ts +154 -0
  104. package/src/platform/server/fetch/__mocks__/index.ts +3 -0
  105. package/src/platform/server/fetch/index.api.ts +1 -0
  106. package/src/platform/server/fetch/index.electron.ts +18 -0
  107. package/src/platform/server/fetch/index.ts +20 -0
  108. package/src/platform/server/fs/index.api.ts +198 -0
  109. package/src/platform/server/fs/index.electron.ts +208 -0
  110. package/src/platform/server/fs/index.test.ts +117 -0
  111. package/src/platform/server/fs/index.ts +416 -0
  112. package/src/platform/server/fs/path-join.api.ts +1 -0
  113. package/src/platform/server/fs/path-join.electron.ts +1 -0
  114. package/src/platform/server/fs/path-join.ts +97 -0
  115. package/src/platform/server/fs/shared.ts +33 -0
  116. package/src/platform/server/indexeddb/index.ts +115 -0
  117. package/src/platform/server/log/index.ts +43 -0
  118. package/src/platform/server/sqlite/index.api.ts +2 -0
  119. package/src/platform/server/sqlite/index.electron.ts +134 -0
  120. package/src/platform/server/sqlite/index.test.ts +108 -0
  121. package/src/platform/server/sqlite/index.ts +241 -0
  122. package/src/platform/server/sqlite/normalise.ts +9 -0
  123. package/src/platform/server/sqlite/unicodeLike.test.ts +58 -0
  124. package/src/platform/server/sqlite/unicodeLike.ts +31 -0
  125. package/src/server/__mocks__/post.ts +9 -0
  126. package/src/server/__snapshots__/main.test.ts.snap +199 -0
  127. package/src/server/__snapshots__/sheet.test.ts.snap +9 -0
  128. package/src/server/accounts/__snapshots__/sync.test.ts.snap +136 -0
  129. package/src/server/accounts/app-bank-sync.test.ts +136 -0
  130. package/src/server/accounts/app.ts +1294 -0
  131. package/src/server/accounts/link.ts +25 -0
  132. package/src/server/accounts/payees.ts +36 -0
  133. package/src/server/accounts/sync.test.ts +679 -0
  134. package/src/server/accounts/sync.ts +1168 -0
  135. package/src/server/accounts/title/index.ts +60 -0
  136. package/src/server/accounts/title/lower-case.ts +93 -0
  137. package/src/server/accounts/title/specials.ts +21 -0
  138. package/src/server/admin/app.ts +241 -0
  139. package/src/server/api-models.ts +244 -0
  140. package/src/server/api.test.ts +36 -0
  141. package/src/server/api.ts +1030 -0
  142. package/src/server/app.ts +91 -0
  143. package/src/server/aql/compiler.test.ts +966 -0
  144. package/src/server/aql/compiler.ts +1222 -0
  145. package/src/server/aql/exec.test.ts +289 -0
  146. package/src/server/aql/exec.ts +128 -0
  147. package/src/server/aql/index.ts +41 -0
  148. package/src/server/aql/schema/executors.test.ts +420 -0
  149. package/src/server/aql/schema/executors.ts +345 -0
  150. package/src/server/aql/schema/index.test.ts +67 -0
  151. package/src/server/aql/schema/index.ts +409 -0
  152. package/src/server/aql/schema-helpers.test.ts +242 -0
  153. package/src/server/aql/schema-helpers.ts +208 -0
  154. package/src/server/aql/views.test.ts +62 -0
  155. package/src/server/aql/views.ts +57 -0
  156. package/src/server/auth/app.ts +387 -0
  157. package/src/server/bench.ts +29 -0
  158. package/src/server/budget/actions.ts +686 -0
  159. package/src/server/budget/app.ts +469 -0
  160. package/src/server/budget/base.test.ts +340 -0
  161. package/src/server/budget/base.ts +339 -0
  162. package/src/server/budget/category-template-context.test.ts +1658 -0
  163. package/src/server/budget/category-template-context.ts +862 -0
  164. package/src/server/budget/cleanup-template.pegjs +27 -0
  165. package/src/server/budget/cleanup-template.ts +408 -0
  166. package/src/server/budget/envelope.ts +403 -0
  167. package/src/server/budget/goal-template.pegjs +110 -0
  168. package/src/server/budget/goal-template.ts +309 -0
  169. package/src/server/budget/report.ts +308 -0
  170. package/src/server/budget/schedule-template.test.ts +184 -0
  171. package/src/server/budget/schedule-template.ts +351 -0
  172. package/src/server/budget/statements.ts +60 -0
  173. package/src/server/budget/template-notes.test.ts +393 -0
  174. package/src/server/budget/template-notes.ts +323 -0
  175. package/src/server/budget/util.ts +25 -0
  176. package/src/server/budgetfiles/__snapshots__/backups.test.ts.snap +101 -0
  177. package/src/server/budgetfiles/app.ts +672 -0
  178. package/src/server/budgetfiles/backups.test.ts +79 -0
  179. package/src/server/budgetfiles/backups.ts +251 -0
  180. package/src/server/cloud-storage.ts +467 -0
  181. package/src/server/dashboard/app.ts +373 -0
  182. package/src/server/db/__snapshots__/index.test.ts.snap +271 -0
  183. package/src/server/db/index.test.ts +300 -0
  184. package/src/server/db/index.ts +855 -0
  185. package/src/server/db/mappings.ts +59 -0
  186. package/src/server/db/sort.ts +58 -0
  187. package/src/server/db/types/index.ts +342 -0
  188. package/src/server/db/util.ts +36 -0
  189. package/src/server/encryption/app.ts +133 -0
  190. package/src/server/encryption/encryption-internals.api.ts +2 -0
  191. package/src/server/encryption/encryption-internals.electron.ts +89 -0
  192. package/src/server/encryption/encryption-internals.ts +109 -0
  193. package/src/server/encryption/encryption.test.ts +19 -0
  194. package/src/server/encryption/index.test.ts +19 -0
  195. package/src/server/encryption/index.ts +89 -0
  196. package/src/server/errors.ts +110 -0
  197. package/src/server/filters/app.ts +191 -0
  198. package/src/server/importers/actual.ts +49 -0
  199. package/src/server/importers/index.ts +58 -0
  200. package/src/server/importers/ynab4-types.ts +163 -0
  201. package/src/server/importers/ynab4.ts +470 -0
  202. package/src/server/importers/ynab5-types.ts +290 -0
  203. package/src/server/importers/ynab5.ts +1193 -0
  204. package/src/server/main-app.ts +25 -0
  205. package/src/server/main.test.ts +392 -0
  206. package/src/server/main.ts +336 -0
  207. package/src/server/migrate/__snapshots__/migrations.test.ts.snap +17 -0
  208. package/src/server/migrate/cli.ts +100 -0
  209. package/src/server/migrate/migrations.test.ts +81 -0
  210. package/src/server/migrate/migrations.ts +192 -0
  211. package/src/server/models.ts +184 -0
  212. package/src/server/mutators.ts +139 -0
  213. package/src/server/notes/app.ts +18 -0
  214. package/src/server/payees/app.ts +351 -0
  215. package/src/server/polyfills.ts +26 -0
  216. package/src/server/post.ts +219 -0
  217. package/src/server/preferences/app.ts +249 -0
  218. package/src/server/prefs.ts +91 -0
  219. package/src/server/reports/app.ts +187 -0
  220. package/src/server/rules/action.ts +344 -0
  221. package/src/server/rules/app.ts +193 -0
  222. package/src/server/rules/condition.ts +436 -0
  223. package/src/server/rules/customFunctions.ts +61 -0
  224. package/src/server/rules/formula-action.test.ts +175 -0
  225. package/src/server/rules/handlebars-helpers.ts +131 -0
  226. package/src/server/rules/index.test.ts +1095 -0
  227. package/src/server/rules/index.ts +22 -0
  228. package/src/server/rules/rule-indexer.ts +89 -0
  229. package/src/server/rules/rule-utils.ts +274 -0
  230. package/src/server/rules/rule.ts +193 -0
  231. package/src/server/schedules/app.test.ts +502 -0
  232. package/src/server/schedules/app.ts +644 -0
  233. package/src/server/schedules/find-schedules.ts +391 -0
  234. package/src/server/server-config.ts +59 -0
  235. package/src/server/sheet.test.ts +101 -0
  236. package/src/server/sheet.ts +280 -0
  237. package/src/server/spreadsheet/__snapshots__/spreadsheet.test.ts.snap +5 -0
  238. package/src/server/spreadsheet/app.ts +54 -0
  239. package/src/server/spreadsheet/globals.ts +13 -0
  240. package/src/server/spreadsheet/graph-data-structure.ts +165 -0
  241. package/src/server/spreadsheet/scratch +60 -0
  242. package/src/server/spreadsheet/spreadsheet.test.ts +191 -0
  243. package/src/server/spreadsheet/spreadsheet.ts +523 -0
  244. package/src/server/spreadsheet/util.ts +15 -0
  245. package/src/server/sql/init.sql +88 -0
  246. package/src/server/sync/__snapshots__/sync.test.ts.snap +31 -0
  247. package/src/server/sync/app.ts +29 -0
  248. package/src/server/sync/encoder.ts +129 -0
  249. package/src/server/sync/index.ts +820 -0
  250. package/src/server/sync/make-test-message.ts +19 -0
  251. package/src/server/sync/migrate.test.ts +169 -0
  252. package/src/server/sync/migrate.ts +48 -0
  253. package/src/server/sync/repair.ts +39 -0
  254. package/src/server/sync/reset.ts +91 -0
  255. package/src/server/sync/sync.property.test.ts +385 -0
  256. package/src/server/sync/sync.test.ts +349 -0
  257. package/src/server/sync/utils.ts +3 -0
  258. package/src/server/tags/app.ts +101 -0
  259. package/src/server/tests/mockData.json +9352 -0
  260. package/src/server/tests/mockSyncServer.ts +119 -0
  261. package/src/server/tools/app.ts +152 -0
  262. package/src/server/transactions/__snapshots__/transaction-rules.test.ts.snap +173 -0
  263. package/src/server/transactions/__snapshots__/transfer.test.ts.snap +655 -0
  264. package/src/server/transactions/app.ts +136 -0
  265. package/src/server/transactions/export/export-to-csv.ts +132 -0
  266. package/src/server/transactions/import/__snapshots__/parse-file.test.ts.snap +1582 -0
  267. package/src/server/transactions/import/ofx2json.test.ts +33 -0
  268. package/src/server/transactions/import/ofx2json.ts +157 -0
  269. package/src/server/transactions/import/parse-file.test.ts +224 -0
  270. package/src/server/transactions/import/parse-file.ts +286 -0
  271. package/src/server/transactions/import/qif2json.ts +110 -0
  272. package/src/server/transactions/import/xmlcamt2json.ts +168 -0
  273. package/src/server/transactions/index.ts +196 -0
  274. package/src/server/transactions/merge.test.ts +370 -0
  275. package/src/server/transactions/merge.ts +139 -0
  276. package/src/server/transactions/transaction-rules.test.ts +994 -0
  277. package/src/server/transactions/transaction-rules.ts +1038 -0
  278. package/src/server/transactions/transfer.test.ts +221 -0
  279. package/src/server/transactions/transfer.ts +173 -0
  280. package/src/server/undo.ts +271 -0
  281. package/src/server/update.ts +37 -0
  282. package/src/server/util/budget-name.ts +61 -0
  283. package/src/server/util/custom-sync-mapping.ts +48 -0
  284. package/src/server/util/rschedule.ts +9 -0
  285. package/src/shared/__mocks__/platform.ts +7 -0
  286. package/src/shared/__snapshots__/months.test.ts.snap +21 -0
  287. package/src/shared/arithmetic.test.ts +112 -0
  288. package/src/shared/arithmetic.ts +170 -0
  289. package/src/shared/async.test.ts +135 -0
  290. package/src/shared/async.ts +76 -0
  291. package/src/shared/constants.ts +5 -0
  292. package/src/shared/currencies.ts +70 -0
  293. package/src/shared/dashboard.ts +260 -0
  294. package/src/shared/environment.ts +18 -0
  295. package/src/shared/errors.ts +195 -0
  296. package/src/shared/locale.ts +27 -0
  297. package/src/shared/location-utils.test.ts +69 -0
  298. package/src/shared/location-utils.ts +49 -0
  299. package/src/shared/months.test.ts +5 -0
  300. package/src/shared/months.ts +485 -0
  301. package/src/shared/normalisation.ts +6 -0
  302. package/src/shared/platform.electron.ts +21 -0
  303. package/src/shared/platform.ts +20 -0
  304. package/src/shared/query.ts +176 -0
  305. package/src/shared/rules.test.ts +56 -0
  306. package/src/shared/rules.ts +371 -0
  307. package/src/shared/schedules.test.ts +570 -0
  308. package/src/shared/schedules.ts +560 -0
  309. package/src/shared/test-helpers.ts +156 -0
  310. package/src/shared/transactions.test.ts +275 -0
  311. package/src/shared/transactions.ts +433 -0
  312. package/src/shared/transfer.test.ts +75 -0
  313. package/src/shared/transfer.ts +16 -0
  314. package/src/shared/user.ts +4 -0
  315. package/src/shared/util.test.ts +240 -0
  316. package/src/shared/util.ts +633 -0
  317. package/src/types/api-handlers.ts +287 -0
  318. package/src/types/budget.ts +8 -0
  319. package/src/types/file.ts +47 -0
  320. package/src/types/handlers.ts +46 -0
  321. package/src/types/models/account.ts +24 -0
  322. package/src/types/models/bank-sync.ts +23 -0
  323. package/src/types/models/bank.ts +6 -0
  324. package/src/types/models/category-group.ts +11 -0
  325. package/src/types/models/category.ts +13 -0
  326. package/src/types/models/dashboard.ts +199 -0
  327. package/src/types/models/gocardless.ts +84 -0
  328. package/src/types/models/import-transaction.ts +56 -0
  329. package/src/types/models/index.ts +23 -0
  330. package/src/types/models/nearby-payee.ts +7 -0
  331. package/src/types/models/note.ts +4 -0
  332. package/src/types/models/openid.ts +8 -0
  333. package/src/types/models/payee-location.ts +8 -0
  334. package/src/types/models/payee.ts +10 -0
  335. package/src/types/models/pluggyai.ts +19 -0
  336. package/src/types/models/reports.ts +144 -0
  337. package/src/types/models/rule.ts +174 -0
  338. package/src/types/models/schedule.ts +49 -0
  339. package/src/types/models/simplefin.ts +28 -0
  340. package/src/types/models/tags.ts +6 -0
  341. package/src/types/models/templates.ts +135 -0
  342. package/src/types/models/transaction-filter.ts +9 -0
  343. package/src/types/models/transaction.ts +39 -0
  344. package/src/types/models/user-access.ts +10 -0
  345. package/src/types/models/user.ts +25 -0
  346. package/src/types/prefs.ts +167 -0
  347. package/src/types/server-events.ts +86 -0
  348. package/src/types/server-handlers.ts +27 -0
  349. package/src/types/util.ts +26 -0
  350. package/tsconfig.json +34 -0
  351. package/typings/pegjs.ts +1 -0
  352. package/typings/process-worker.ts +12 -0
  353. package/typings/vite-plugin-peggy-loader.ts +1 -0
  354. package/typings/window.ts +62 -0
  355. package/vite.config.ts +109 -0
  356. package/vite.desktop.config.ts +59 -0
  357. package/vitest.config.ts +43 -0
  358. package/vitest.web.config.ts +38 -0
@@ -0,0 +1,249 @@
1
+ import * as asyncStorage from '../../platform/server/asyncStorage';
2
+ import * as fs from '../../platform/server/fs';
3
+ import { stringToInteger } from '../../shared/util';
4
+ import type {
5
+ GlobalPrefs,
6
+ MetadataPrefs,
7
+ SyncedPrefs,
8
+ } from '../../types/prefs';
9
+ import { createApp } from '../app';
10
+ import * as db from '../db';
11
+ import { PostError } from '../errors';
12
+ import { getDefaultDocumentDir } from '../main';
13
+ import { mutator } from '../mutators';
14
+ import { post } from '../post';
15
+ import {
16
+ getPrefs as _getMetadataPrefs,
17
+ savePrefs as _saveMetadataPrefs,
18
+ } from '../prefs';
19
+ import { getServer } from '../server-config';
20
+ import { undoable } from '../undo';
21
+
22
+ export type PreferencesHandlers = {
23
+ 'preferences/save': typeof saveSyncedPrefs;
24
+ 'preferences/get': typeof getSyncedPrefs;
25
+ 'save-global-prefs': typeof saveGlobalPrefs;
26
+ 'load-global-prefs': typeof loadGlobalPrefs;
27
+ 'save-prefs': typeof saveMetadataPrefs;
28
+ 'load-prefs': typeof loadMetadataPrefs;
29
+ 'save-server-prefs': typeof saveServerPrefs;
30
+ };
31
+
32
+ export const app = createApp<PreferencesHandlers>();
33
+
34
+ app.method('preferences/save', mutator(undoable(saveSyncedPrefs)));
35
+ app.method('preferences/get', getSyncedPrefs);
36
+ app.method('save-global-prefs', saveGlobalPrefs);
37
+ app.method('load-global-prefs', loadGlobalPrefs);
38
+ app.method('save-prefs', saveMetadataPrefs);
39
+ app.method('load-prefs', loadMetadataPrefs);
40
+ app.method('save-server-prefs', saveServerPrefs);
41
+
42
+ async function saveSyncedPrefs({
43
+ id,
44
+ value,
45
+ }: {
46
+ id: keyof SyncedPrefs;
47
+ value: string | undefined;
48
+ }) {
49
+ if (!id) {
50
+ return;
51
+ }
52
+
53
+ await db.update('preferences', {
54
+ id,
55
+ value,
56
+ });
57
+ }
58
+
59
+ async function getSyncedPrefs(): Promise<SyncedPrefs> {
60
+ const prefs = await db.all<Pick<db.DbPreference, 'id' | 'value'>>(
61
+ 'SELECT id, value FROM preferences',
62
+ );
63
+
64
+ return prefs.reduce<SyncedPrefs>((carry, { value, id }) => {
65
+ carry[id as keyof SyncedPrefs] = value;
66
+ return carry;
67
+ }, {});
68
+ }
69
+
70
+ async function saveGlobalPrefs(prefs: GlobalPrefs) {
71
+ if (!prefs) {
72
+ return 'ok';
73
+ }
74
+
75
+ if (prefs.maxMonths !== undefined) {
76
+ await asyncStorage.setItem('max-months', '' + prefs.maxMonths);
77
+ }
78
+ if (prefs.categoryExpandedState !== undefined) {
79
+ await asyncStorage.setItem(
80
+ 'category-expanded-state',
81
+ '' + prefs.categoryExpandedState,
82
+ );
83
+ }
84
+ if (prefs.documentDir !== undefined && (await fs.exists(prefs.documentDir))) {
85
+ await asyncStorage.setItem('document-dir', prefs.documentDir);
86
+ }
87
+ if (prefs.floatingSidebar !== undefined) {
88
+ await asyncStorage.setItem('floating-sidebar', '' + prefs.floatingSidebar);
89
+ }
90
+ if (prefs.language !== undefined) {
91
+ await asyncStorage.setItem('language', prefs.language);
92
+ }
93
+ if (prefs.theme !== undefined) {
94
+ await asyncStorage.setItem('theme', prefs.theme);
95
+ }
96
+ if (prefs.preferredDarkTheme !== undefined) {
97
+ await asyncStorage.setItem(
98
+ 'preferred-dark-theme',
99
+ prefs.preferredDarkTheme,
100
+ );
101
+ }
102
+ if (prefs.installedCustomLightTheme !== undefined) {
103
+ await asyncStorage.setItem(
104
+ 'installed-custom-theme',
105
+ prefs.installedCustomLightTheme,
106
+ );
107
+ }
108
+ if (prefs.installedCustomDarkTheme !== undefined) {
109
+ await asyncStorage.setItem(
110
+ 'installed-custom-dark-theme',
111
+ prefs.installedCustomDarkTheme,
112
+ );
113
+ }
114
+ if (prefs.serverSelfSignedCert !== undefined) {
115
+ await asyncStorage.setItem(
116
+ 'server-self-signed-cert',
117
+ prefs.serverSelfSignedCert,
118
+ );
119
+ }
120
+ if (prefs.syncServerConfig !== undefined) {
121
+ await asyncStorage.setItem('syncServerConfig', prefs.syncServerConfig);
122
+ }
123
+ if (prefs.notifyWhenUpdateIsAvailable !== undefined) {
124
+ await asyncStorage.setItem(
125
+ 'notifyWhenUpdateIsAvailable',
126
+ prefs.notifyWhenUpdateIsAvailable,
127
+ );
128
+ }
129
+ return 'ok';
130
+ }
131
+
132
+ async function loadGlobalPrefs(): Promise<GlobalPrefs> {
133
+ const {
134
+ 'floating-sidebar': floatingSidebar,
135
+ 'category-expanded-state': categoryExpandedState,
136
+ 'max-months': maxMonths,
137
+ 'document-dir': documentDir,
138
+ 'encrypt-key': encryptKey,
139
+ language,
140
+ theme,
141
+ 'preferred-dark-theme': preferredDarkTheme,
142
+ 'installed-custom-theme': installedCustomLightTheme,
143
+ 'installed-custom-dark-theme': installedCustomDarkTheme,
144
+ 'server-self-signed-cert': serverSelfSignedCert,
145
+ syncServerConfig,
146
+ notifyWhenUpdateIsAvailable,
147
+ } = await asyncStorage.multiGet([
148
+ 'floating-sidebar',
149
+ 'category-expanded-state',
150
+ 'max-months',
151
+ 'document-dir',
152
+ 'encrypt-key',
153
+ 'language',
154
+ 'theme',
155
+ 'preferred-dark-theme',
156
+ 'installed-custom-theme',
157
+ 'installed-custom-dark-theme',
158
+ 'server-self-signed-cert',
159
+ 'syncServerConfig',
160
+ 'notifyWhenUpdateIsAvailable',
161
+ ] as const);
162
+ return {
163
+ floatingSidebar: floatingSidebar === 'true',
164
+ categoryExpandedState: stringToInteger(categoryExpandedState || '') || 0,
165
+ maxMonths: stringToInteger(maxMonths || '') || 1,
166
+ documentDir: documentDir || getDefaultDocumentDir(),
167
+ keyId: encryptKey && JSON.parse(encryptKey).id,
168
+ language,
169
+ theme:
170
+ theme === 'light' ||
171
+ theme === 'dark' ||
172
+ theme === 'auto' ||
173
+ theme === 'development' ||
174
+ theme === 'midnight'
175
+ ? theme
176
+ : 'auto',
177
+ preferredDarkTheme:
178
+ preferredDarkTheme === 'dark' || preferredDarkTheme === 'midnight'
179
+ ? preferredDarkTheme
180
+ : 'dark',
181
+ installedCustomLightTheme: installedCustomLightTheme || undefined,
182
+ installedCustomDarkTheme: installedCustomDarkTheme || undefined,
183
+ serverSelfSignedCert: serverSelfSignedCert || undefined,
184
+ syncServerConfig: syncServerConfig || undefined,
185
+ notifyWhenUpdateIsAvailable:
186
+ notifyWhenUpdateIsAvailable === undefined
187
+ ? true
188
+ : notifyWhenUpdateIsAvailable, // default to true
189
+ };
190
+ }
191
+
192
+ async function saveMetadataPrefs(prefsToSet: MetadataPrefs) {
193
+ if (!prefsToSet) {
194
+ return 'ok';
195
+ }
196
+
197
+ const { cloudFileId } = _getMetadataPrefs();
198
+
199
+ // Need to sync the budget name on the server as well
200
+ if (prefsToSet.budgetName && cloudFileId) {
201
+ const userToken = await asyncStorage.getItem('user-token');
202
+
203
+ const syncServer = getServer()?.SYNC_SERVER;
204
+ if (!syncServer) {
205
+ throw new Error('No sync server set');
206
+ }
207
+
208
+ await post(syncServer + '/update-user-filename', {
209
+ token: userToken,
210
+ fileId: cloudFileId,
211
+ name: prefsToSet.budgetName,
212
+ });
213
+ }
214
+
215
+ await _saveMetadataPrefs(prefsToSet);
216
+ return 'ok';
217
+ }
218
+
219
+ async function loadMetadataPrefs(): Promise<MetadataPrefs> {
220
+ return _getMetadataPrefs();
221
+ }
222
+
223
+ async function saveServerPrefs({ prefs }: { prefs: Record<string, string> }) {
224
+ const userToken = await asyncStorage.getItem('user-token');
225
+ if (!userToken) {
226
+ return { error: 'not-logged-in' };
227
+ }
228
+
229
+ try {
230
+ const serverConfig = getServer();
231
+ if (!serverConfig) {
232
+ throw new Error('No sync server configured.');
233
+ }
234
+ await post(serverConfig.SIGNUP_SERVER + '/server-prefs', {
235
+ token: userToken,
236
+ prefs,
237
+ });
238
+ } catch (err) {
239
+ if (err instanceof PostError) {
240
+ return {
241
+ error: err.reason || 'network-failure',
242
+ };
243
+ }
244
+
245
+ throw err;
246
+ }
247
+
248
+ return {};
249
+ }
@@ -0,0 +1,91 @@
1
+ // @ts-strict-ignore
2
+ import { Timestamp } from '@actual-app/crdt';
3
+
4
+ import * as fs from '../platform/server/fs';
5
+ import type { MetadataPrefs } from '../types/prefs';
6
+
7
+ import { sendMessages } from './sync';
8
+ import type { Message } from './sync';
9
+
10
+ export const BUDGET_TYPES = ['tracking', 'envelope'] as const;
11
+ export type BudgetType = (typeof BUDGET_TYPES)[number];
12
+
13
+ export const UPCOMING_SCHEDULED_TRANSACTION_LENGTHS = [
14
+ '1',
15
+ '7',
16
+ '14',
17
+ '30',
18
+ ] as const;
19
+ export type UpcomingScheduledTransactionLength =
20
+ (typeof UPCOMING_SCHEDULED_TRANSACTION_LENGTHS)[number];
21
+
22
+ let prefs: MetadataPrefs = null;
23
+
24
+ export async function loadPrefs(id?: string): Promise<MetadataPrefs> {
25
+ if (process.env.NODE_ENV === 'test' && !id) {
26
+ prefs = getDefaultPrefs('test', 'test_LocalPrefs');
27
+ return prefs;
28
+ }
29
+
30
+ const fullpath = fs.join(fs.getBudgetDir(id), 'metadata.json');
31
+
32
+ try {
33
+ prefs = JSON.parse(await fs.readFile(fullpath));
34
+ } catch {
35
+ // If the user messed something up, be flexible and allow them to
36
+ // still load the budget database. Default the budget name to the
37
+ // id.
38
+ prefs = { id, budgetName: id };
39
+ }
40
+
41
+ // No matter what is in `id` field, force it to be the current id.
42
+ // This makes it resilient to users moving around folders, etc
43
+ prefs.id = id;
44
+ return prefs;
45
+ }
46
+
47
+ export async function savePrefs(
48
+ prefsToSet: MetadataPrefs,
49
+ { avoidSync = false } = {},
50
+ ): Promise<void> {
51
+ Object.assign(prefs, prefsToSet);
52
+
53
+ if (!avoidSync) {
54
+ // Sync whitelisted prefs
55
+ const messages: Message[] = Object.keys(prefsToSet)
56
+ .map(key => {
57
+ if (key === 'budgetName') {
58
+ return {
59
+ dataset: 'prefs',
60
+ row: key,
61
+ column: 'value',
62
+ value: prefsToSet[key],
63
+ timestamp: Timestamp.send(),
64
+ };
65
+ }
66
+ return null;
67
+ })
68
+ .filter(x => x);
69
+
70
+ if (messages.length > 0) {
71
+ await sendMessages(messages);
72
+ }
73
+ }
74
+
75
+ if (process.env.NODE_ENV !== 'test') {
76
+ const prefsPath = fs.join(fs.getBudgetDir(prefs.id), 'metadata.json');
77
+ await fs.writeFile(prefsPath, JSON.stringify(prefs));
78
+ }
79
+ }
80
+
81
+ export function unloadPrefs(): void {
82
+ prefs = null;
83
+ }
84
+
85
+ export function getPrefs(): MetadataPrefs {
86
+ return prefs;
87
+ }
88
+
89
+ export function getDefaultPrefs(id: string, budgetName: string) {
90
+ return { id, budgetName };
91
+ }
@@ -0,0 +1,187 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+
3
+ import { q } from '#shared/query';
4
+ import type { CustomReportData, CustomReportEntity } from '../../types/models';
5
+ import { createApp } from '../app';
6
+ import { aqlQuery } from '../aql';
7
+ import * as db from '../db';
8
+ import { ValidationError } from '../errors';
9
+ import { requiredFields } from '../models';
10
+ import { mutator } from '../mutators';
11
+ import { undoable } from '../undo';
12
+
13
+ export const reportModel = {
14
+ validate(
15
+ report: Omit<CustomReportEntity, 'tombstone'>,
16
+ { update }: { update?: boolean } = {},
17
+ ) {
18
+ requiredFields('Report', report, ['conditionsOp'], update);
19
+
20
+ if (!update || 'conditionsOp' in report) {
21
+ if (!['and', 'or'].includes(report.conditionsOp)) {
22
+ throw new ValidationError(
23
+ 'Invalid filter conditionsOp: ' + report.conditionsOp,
24
+ );
25
+ }
26
+ }
27
+
28
+ return report;
29
+ },
30
+
31
+ toJS(row: CustomReportData): CustomReportEntity {
32
+ return {
33
+ id: row.id,
34
+ name: row.name,
35
+ startDate: row.start_date,
36
+ endDate: row.end_date,
37
+ isDateStatic: row.date_static === 1,
38
+ dateRange: row.date_range,
39
+ mode: row.mode,
40
+ groupBy: row.group_by,
41
+ sortBy: row.sort_by,
42
+ interval: row.interval,
43
+ balanceType: row.balance_type,
44
+ showEmpty: row.show_empty === 1,
45
+ showOffBudget: row.show_offbudget === 1,
46
+ showHiddenCategories: row.show_hidden === 1,
47
+ showUncategorized: row.show_uncategorized === 1,
48
+ trimIntervals: row.trim_intervals === 1,
49
+ includeCurrentInterval: row.include_current === 1,
50
+ graphType: row.graph_type,
51
+ conditions: row.conditions ?? [],
52
+ conditionsOp: row.conditions_op ?? 'and',
53
+ metadata: row.metadata,
54
+ };
55
+ },
56
+
57
+ fromJS(report: CustomReportEntity): CustomReportData {
58
+ return {
59
+ id: report.id,
60
+ name: report.name,
61
+ start_date: report.startDate,
62
+ end_date: report.endDate,
63
+ date_static: report.isDateStatic ? 1 : 0,
64
+ date_range: report.dateRange,
65
+ mode: report.mode,
66
+ group_by: report.groupBy,
67
+ sort_by: report.sortBy ?? 'desc',
68
+ interval: report.interval,
69
+ balance_type: report.balanceType,
70
+ show_empty: report.showEmpty ? 1 : 0,
71
+ show_offbudget: report.showOffBudget ? 1 : 0,
72
+ show_hidden: report.showHiddenCategories ? 1 : 0,
73
+ show_uncategorized: report.showUncategorized ? 1 : 0,
74
+ trim_intervals: report.trimIntervals ? 1 : 0,
75
+ include_current: report.includeCurrentInterval ? 1 : 0,
76
+ graph_type: report.graphType,
77
+ conditions: report.conditions,
78
+ conditions_op: report.conditionsOp,
79
+ };
80
+ },
81
+ };
82
+
83
+ // Sort reports by alphabetical order
84
+ function sort(reports: CustomReportEntity[]) {
85
+ return reports.sort((a, b) =>
86
+ a.name && b.name
87
+ ? a.name.trim().localeCompare(b.name.trim(), undefined, {
88
+ ignorePunctuation: true,
89
+ })
90
+ : 0,
91
+ );
92
+ }
93
+
94
+ async function getReports() {
95
+ // Use aql because it auto deserialized json columns e.g. conditions
96
+ const { data }: { data: CustomReportData[] } = await aqlQuery(
97
+ q('custom_reports').select('*'),
98
+ );
99
+ return sort(data.map(r => reportModel.toJS(r)));
100
+ }
101
+
102
+ async function reportNameExists(
103
+ name: string,
104
+ reportId: string,
105
+ newItem: boolean,
106
+ ) {
107
+ const idForName = await db.first<Pick<db.DbCustomReport, 'id'>>(
108
+ 'SELECT id from custom_reports WHERE tombstone = 0 AND name = ?',
109
+ [name],
110
+ );
111
+
112
+ //no existing name found
113
+ if (idForName === null) {
114
+ return false;
115
+ }
116
+
117
+ //for update/rename
118
+ if (!newItem) {
119
+ /*
120
+ -if the found item is the same as the existing item
121
+ then no name change was made.
122
+ -if they are not the same then there is another
123
+ item with that name already.
124
+ */
125
+ return idForName.id !== reportId;
126
+ }
127
+
128
+ //default return: item was found but does not match current name
129
+ return true;
130
+ }
131
+
132
+ async function createReport(report: CustomReportEntity) {
133
+ const reportId = uuidv4();
134
+ const item: CustomReportEntity = {
135
+ ...report,
136
+ id: reportId,
137
+ };
138
+ if (!item.name) {
139
+ throw new Error('Report name is required');
140
+ }
141
+
142
+ const nameExists = await reportNameExists(item.name, item.id ?? '', true);
143
+ if (nameExists) {
144
+ throw new Error('There is already a report named ' + item.name);
145
+ }
146
+
147
+ // Create the report here based on the info
148
+ await db.insertWithSchema('custom_reports', reportModel.fromJS(item));
149
+
150
+ return reportId;
151
+ }
152
+
153
+ async function updateReport(item: CustomReportEntity) {
154
+ if (!item.name) {
155
+ throw new Error('Report name is required');
156
+ }
157
+
158
+ if (!item.id) {
159
+ throw new Error('Report recall error');
160
+ }
161
+
162
+ const nameExists = await reportNameExists(item.name, item.id, false);
163
+ if (nameExists) {
164
+ throw new Error('There is already a report named ' + item.name);
165
+ }
166
+
167
+ await db.updateWithSchema('custom_reports', reportModel.fromJS(item));
168
+ }
169
+
170
+ async function deleteReport(id: CustomReportEntity['id']) {
171
+ await db.delete_('custom_reports', id);
172
+ }
173
+
174
+ export type ReportsHandlers = {
175
+ 'report/get': typeof getReports;
176
+ 'report/create': typeof createReport;
177
+ 'report/update': typeof updateReport;
178
+ 'report/delete': typeof deleteReport;
179
+ };
180
+
181
+ // Expose functions to the client
182
+ export const app = createApp<ReportsHandlers>();
183
+
184
+ app.method('report/get', getReports);
185
+ app.method('report/create', mutator(undoable(createReport)));
186
+ app.method('report/update', mutator(undoable(updateReport)));
187
+ app.method('report/delete', mutator(undoable(deleteReport)));