@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.
- package/.swcrc +11 -0
- package/bin/build-browser +40 -0
- package/bin/copy-migrations +9 -0
- package/db.sqlite +0 -0
- package/default-db.sqlite +0 -0
- package/migrations/.force-copy-windows +0 -0
- package/migrations/1548957970627_remove-db-version.sql +5 -0
- package/migrations/1550601598648_payees.sql +23 -0
- package/migrations/1555786194328_remove_category_group_unique.sql +25 -0
- package/migrations/1561751833510_indexes.sql +7 -0
- package/migrations/1567699552727_budget.sql +38 -0
- package/migrations/1582384163573_cleared.sql +6 -0
- package/migrations/1597756566448_rules.sql +10 -0
- package/migrations/1608652596043_parent_field.sql +13 -0
- package/migrations/1608652596044_trans_views.sql +56 -0
- package/migrations/1612625548236_optimize.sql +7 -0
- package/migrations/1614782639336_trans_views2.sql +33 -0
- package/migrations/1615745967948_meta.sql +10 -0
- package/migrations/1616167010796_accounts_order.sql +5 -0
- package/migrations/1618975177358_schedules.sql +28 -0
- package/migrations/1632571489012_remove_cache.js +136 -0
- package/migrations/1679728867040_rules_conditions.sql +5 -0
- package/migrations/1681115033845_add_schedule_name.sql +5 -0
- package/migrations/1682974838138_remove_payee_rules.sql +5 -0
- package/migrations/1685007876842_add_category_hidden.sql +6 -0
- package/migrations/1686139660866_remove_account_type.sql +5 -0
- package/migrations/1688749527273_transaction_filters.sql +10 -0
- package/migrations/1688841238000_add_account_type.sql +5 -0
- package/migrations/1691233396000_add_schedule_next_date_tombstone.sql +5 -0
- package/migrations/1694438752000_add_goal_targets.sql +7 -0
- package/migrations/1697046240000_add_reconciled.sql +5 -0
- package/migrations/1704572023730_add_account_sync_source.sql +5 -0
- package/migrations/1704572023731_add_missing_goCardless_sync_source.sql +9 -0
- package/migrations/1707267033000_reports.sql +28 -0
- package/migrations/1712784523000_unhide_input_group.sql +8 -0
- package/migrations/1716359441000_include_current.sql +5 -0
- package/migrations/1720310586000_link_transfer_schedules.sql +19 -0
- package/migrations/1720664867241_add_payee_favorite.sql +5 -0
- package/migrations/1720665000000_goal_context.sql +6 -0
- package/migrations/1722717601000_reports_move_selected_categories.js +55 -0
- package/migrations/1722804019000_create_dashboard_table.js +69 -0
- package/migrations/1723665565000_prefs.js +59 -0
- package/migrations/1730744182000_fix_dashboard_table.sql +7 -0
- package/migrations/1736640000000_custom_report_sorting.sql +7 -0
- package/migrations/1737158400000_add_learn_categories_to_payees.sql +5 -0
- package/migrations/1738491452000_sorting_rename.sql +13 -0
- package/migrations/1739139550000_bank_sync_page.sql +7 -0
- package/migrations/1740506588539_add_last_reconciled_at.sql +5 -0
- package/migrations/1745425408000_update_budgetType_pref.sql +7 -0
- package/migrations/1749799110000_add_tags.sql +10 -0
- package/migrations/1749799110001_tags_tombstone.sql +5 -0
- package/migrations/1754611200000_add_category_template_settings.sql +5 -0
- package/migrations/1759260219000_add_trim_interval_report_setting.sql +6 -0
- package/migrations/1759842823172_add_isGlobal_to_preferences.sql +1 -0
- package/migrations/1762178745667_rename_csv_skip_lines_pref.sql +8 -0
- package/migrations/1765518577215_multiple_dashboards.js +30 -0
- package/migrations/1768872504000_add_payee_locations.sql +21 -0
- package/package.json +128 -0
- package/src/mocks/arbitrary-schema.ts +162 -0
- package/src/mocks/budget.ts +901 -0
- package/src/mocks/files/8859-1.qfx +63 -0
- package/src/mocks/files/best.data-ever$.QFX +124 -0
- package/src/mocks/files/big.data.QiF +91 -0
- package/src/mocks/files/budgets/.commit-to-git +0 -0
- package/src/mocks/files/camt/camt.053.payee-memo.xml +127 -0
- package/src/mocks/files/camt/camt.053.xml +463 -0
- package/src/mocks/files/credit-card.ofx +11 -0
- package/src/mocks/files/data-multi-decimal.ofx +64 -0
- package/src/mocks/files/data-payee-memo.ofx +75 -0
- package/src/mocks/files/data-payee-memo.qif +17 -0
- package/src/mocks/files/data.ofx +124 -0
- package/src/mocks/files/data.qfx +124 -0
- package/src/mocks/files/data.qif +91 -0
- package/src/mocks/files/default-budget-template/db.sqlite +0 -0
- package/src/mocks/files/default-budget-template/metadata.json +6 -0
- package/src/mocks/files/html-vals.qfx +17 -0
- package/src/mocks/index.ts +221 -0
- package/src/mocks/migrations/1508717984291_up_add-poop.sql +13 -0
- package/src/mocks/migrations/1508718036311_up_modify-poop.sql +2 -0
- package/src/mocks/migrations/1508727787513_remove-is_income.sql +15 -0
- package/src/mocks/random.ts +16 -0
- package/src/mocks/setup.ts +180 -0
- package/src/mocks/spreadsheet.ts +101 -0
- package/src/mocks/util.ts +82 -0
- package/src/platform/client/connection/README.md +3 -0
- package/src/platform/client/connection/__mocks__/index.ts +67 -0
- package/src/platform/client/connection/index-types.ts +95 -0
- package/src/platform/client/connection/index.browser.ts +213 -0
- package/src/platform/client/connection/index.ts +155 -0
- package/src/platform/client/undo/index.ts +59 -0
- package/src/platform/exceptions/__mocks__/index.ts +7 -0
- package/src/platform/exceptions/index.ts +9 -0
- package/src/platform/server/asyncStorage/__mocks__/index.ts +50 -0
- package/src/platform/server/asyncStorage/index-types.ts +35 -0
- package/src/platform/server/asyncStorage/index.api.ts +2 -0
- package/src/platform/server/asyncStorage/index.electron.ts +88 -0
- package/src/platform/server/asyncStorage/index.ts +126 -0
- package/src/platform/server/connection/README.md +3 -0
- package/src/platform/server/connection/__mocks__/index.ts +15 -0
- package/src/platform/server/connection/index-types.ts +20 -0
- package/src/platform/server/connection/index.api.ts +13 -0
- package/src/platform/server/connection/index.electron.ts +102 -0
- package/src/platform/server/connection/index.ts +154 -0
- package/src/platform/server/fetch/__mocks__/index.ts +3 -0
- package/src/platform/server/fetch/index.api.ts +1 -0
- package/src/platform/server/fetch/index.electron.ts +18 -0
- package/src/platform/server/fetch/index.ts +20 -0
- package/src/platform/server/fs/index.api.ts +198 -0
- package/src/platform/server/fs/index.electron.ts +208 -0
- package/src/platform/server/fs/index.test.ts +117 -0
- package/src/platform/server/fs/index.ts +416 -0
- package/src/platform/server/fs/path-join.api.ts +1 -0
- package/src/platform/server/fs/path-join.electron.ts +1 -0
- package/src/platform/server/fs/path-join.ts +97 -0
- package/src/platform/server/fs/shared.ts +33 -0
- package/src/platform/server/indexeddb/index.ts +115 -0
- package/src/platform/server/log/index.ts +43 -0
- package/src/platform/server/sqlite/index.api.ts +2 -0
- package/src/platform/server/sqlite/index.electron.ts +134 -0
- package/src/platform/server/sqlite/index.test.ts +108 -0
- package/src/platform/server/sqlite/index.ts +241 -0
- package/src/platform/server/sqlite/normalise.ts +9 -0
- package/src/platform/server/sqlite/unicodeLike.test.ts +58 -0
- package/src/platform/server/sqlite/unicodeLike.ts +31 -0
- package/src/server/__mocks__/post.ts +9 -0
- package/src/server/__snapshots__/main.test.ts.snap +199 -0
- package/src/server/__snapshots__/sheet.test.ts.snap +9 -0
- package/src/server/accounts/__snapshots__/sync.test.ts.snap +136 -0
- package/src/server/accounts/app-bank-sync.test.ts +136 -0
- package/src/server/accounts/app.ts +1294 -0
- package/src/server/accounts/link.ts +25 -0
- package/src/server/accounts/payees.ts +36 -0
- package/src/server/accounts/sync.test.ts +679 -0
- package/src/server/accounts/sync.ts +1168 -0
- package/src/server/accounts/title/index.ts +60 -0
- package/src/server/accounts/title/lower-case.ts +93 -0
- package/src/server/accounts/title/specials.ts +21 -0
- package/src/server/admin/app.ts +241 -0
- package/src/server/api-models.ts +244 -0
- package/src/server/api.test.ts +36 -0
- package/src/server/api.ts +1030 -0
- package/src/server/app.ts +91 -0
- package/src/server/aql/compiler.test.ts +966 -0
- package/src/server/aql/compiler.ts +1222 -0
- package/src/server/aql/exec.test.ts +289 -0
- package/src/server/aql/exec.ts +128 -0
- package/src/server/aql/index.ts +41 -0
- package/src/server/aql/schema/executors.test.ts +420 -0
- package/src/server/aql/schema/executors.ts +345 -0
- package/src/server/aql/schema/index.test.ts +67 -0
- package/src/server/aql/schema/index.ts +409 -0
- package/src/server/aql/schema-helpers.test.ts +242 -0
- package/src/server/aql/schema-helpers.ts +208 -0
- package/src/server/aql/views.test.ts +62 -0
- package/src/server/aql/views.ts +57 -0
- package/src/server/auth/app.ts +387 -0
- package/src/server/bench.ts +29 -0
- package/src/server/budget/actions.ts +686 -0
- package/src/server/budget/app.ts +469 -0
- package/src/server/budget/base.test.ts +340 -0
- package/src/server/budget/base.ts +339 -0
- package/src/server/budget/category-template-context.test.ts +1658 -0
- package/src/server/budget/category-template-context.ts +862 -0
- package/src/server/budget/cleanup-template.pegjs +27 -0
- package/src/server/budget/cleanup-template.ts +408 -0
- package/src/server/budget/envelope.ts +403 -0
- package/src/server/budget/goal-template.pegjs +110 -0
- package/src/server/budget/goal-template.ts +309 -0
- package/src/server/budget/report.ts +308 -0
- package/src/server/budget/schedule-template.test.ts +184 -0
- package/src/server/budget/schedule-template.ts +351 -0
- package/src/server/budget/statements.ts +60 -0
- package/src/server/budget/template-notes.test.ts +393 -0
- package/src/server/budget/template-notes.ts +323 -0
- package/src/server/budget/util.ts +25 -0
- package/src/server/budgetfiles/__snapshots__/backups.test.ts.snap +101 -0
- package/src/server/budgetfiles/app.ts +672 -0
- package/src/server/budgetfiles/backups.test.ts +79 -0
- package/src/server/budgetfiles/backups.ts +251 -0
- package/src/server/cloud-storage.ts +467 -0
- package/src/server/dashboard/app.ts +373 -0
- package/src/server/db/__snapshots__/index.test.ts.snap +271 -0
- package/src/server/db/index.test.ts +300 -0
- package/src/server/db/index.ts +855 -0
- package/src/server/db/mappings.ts +59 -0
- package/src/server/db/sort.ts +58 -0
- package/src/server/db/types/index.ts +342 -0
- package/src/server/db/util.ts +36 -0
- package/src/server/encryption/app.ts +133 -0
- package/src/server/encryption/encryption-internals.api.ts +2 -0
- package/src/server/encryption/encryption-internals.electron.ts +89 -0
- package/src/server/encryption/encryption-internals.ts +109 -0
- package/src/server/encryption/encryption.test.ts +19 -0
- package/src/server/encryption/index.test.ts +19 -0
- package/src/server/encryption/index.ts +89 -0
- package/src/server/errors.ts +110 -0
- package/src/server/filters/app.ts +191 -0
- package/src/server/importers/actual.ts +49 -0
- package/src/server/importers/index.ts +58 -0
- package/src/server/importers/ynab4-types.ts +163 -0
- package/src/server/importers/ynab4.ts +470 -0
- package/src/server/importers/ynab5-types.ts +290 -0
- package/src/server/importers/ynab5.ts +1193 -0
- package/src/server/main-app.ts +25 -0
- package/src/server/main.test.ts +392 -0
- package/src/server/main.ts +336 -0
- package/src/server/migrate/__snapshots__/migrations.test.ts.snap +17 -0
- package/src/server/migrate/cli.ts +100 -0
- package/src/server/migrate/migrations.test.ts +81 -0
- package/src/server/migrate/migrations.ts +192 -0
- package/src/server/models.ts +184 -0
- package/src/server/mutators.ts +139 -0
- package/src/server/notes/app.ts +18 -0
- package/src/server/payees/app.ts +351 -0
- package/src/server/polyfills.ts +26 -0
- package/src/server/post.ts +219 -0
- package/src/server/preferences/app.ts +249 -0
- package/src/server/prefs.ts +91 -0
- package/src/server/reports/app.ts +187 -0
- package/src/server/rules/action.ts +344 -0
- package/src/server/rules/app.ts +193 -0
- package/src/server/rules/condition.ts +436 -0
- package/src/server/rules/customFunctions.ts +61 -0
- package/src/server/rules/formula-action.test.ts +175 -0
- package/src/server/rules/handlebars-helpers.ts +131 -0
- package/src/server/rules/index.test.ts +1095 -0
- package/src/server/rules/index.ts +22 -0
- package/src/server/rules/rule-indexer.ts +89 -0
- package/src/server/rules/rule-utils.ts +274 -0
- package/src/server/rules/rule.ts +193 -0
- package/src/server/schedules/app.test.ts +502 -0
- package/src/server/schedules/app.ts +644 -0
- package/src/server/schedules/find-schedules.ts +391 -0
- package/src/server/server-config.ts +59 -0
- package/src/server/sheet.test.ts +101 -0
- package/src/server/sheet.ts +280 -0
- package/src/server/spreadsheet/__snapshots__/spreadsheet.test.ts.snap +5 -0
- package/src/server/spreadsheet/app.ts +54 -0
- package/src/server/spreadsheet/globals.ts +13 -0
- package/src/server/spreadsheet/graph-data-structure.ts +165 -0
- package/src/server/spreadsheet/scratch +60 -0
- package/src/server/spreadsheet/spreadsheet.test.ts +191 -0
- package/src/server/spreadsheet/spreadsheet.ts +523 -0
- package/src/server/spreadsheet/util.ts +15 -0
- package/src/server/sql/init.sql +88 -0
- package/src/server/sync/__snapshots__/sync.test.ts.snap +31 -0
- package/src/server/sync/app.ts +29 -0
- package/src/server/sync/encoder.ts +129 -0
- package/src/server/sync/index.ts +820 -0
- package/src/server/sync/make-test-message.ts +19 -0
- package/src/server/sync/migrate.test.ts +169 -0
- package/src/server/sync/migrate.ts +48 -0
- package/src/server/sync/repair.ts +39 -0
- package/src/server/sync/reset.ts +91 -0
- package/src/server/sync/sync.property.test.ts +385 -0
- package/src/server/sync/sync.test.ts +349 -0
- package/src/server/sync/utils.ts +3 -0
- package/src/server/tags/app.ts +101 -0
- package/src/server/tests/mockData.json +9352 -0
- package/src/server/tests/mockSyncServer.ts +119 -0
- package/src/server/tools/app.ts +152 -0
- package/src/server/transactions/__snapshots__/transaction-rules.test.ts.snap +173 -0
- package/src/server/transactions/__snapshots__/transfer.test.ts.snap +655 -0
- package/src/server/transactions/app.ts +136 -0
- package/src/server/transactions/export/export-to-csv.ts +132 -0
- package/src/server/transactions/import/__snapshots__/parse-file.test.ts.snap +1582 -0
- package/src/server/transactions/import/ofx2json.test.ts +33 -0
- package/src/server/transactions/import/ofx2json.ts +157 -0
- package/src/server/transactions/import/parse-file.test.ts +224 -0
- package/src/server/transactions/import/parse-file.ts +286 -0
- package/src/server/transactions/import/qif2json.ts +110 -0
- package/src/server/transactions/import/xmlcamt2json.ts +168 -0
- package/src/server/transactions/index.ts +196 -0
- package/src/server/transactions/merge.test.ts +370 -0
- package/src/server/transactions/merge.ts +139 -0
- package/src/server/transactions/transaction-rules.test.ts +994 -0
- package/src/server/transactions/transaction-rules.ts +1038 -0
- package/src/server/transactions/transfer.test.ts +221 -0
- package/src/server/transactions/transfer.ts +173 -0
- package/src/server/undo.ts +271 -0
- package/src/server/update.ts +37 -0
- package/src/server/util/budget-name.ts +61 -0
- package/src/server/util/custom-sync-mapping.ts +48 -0
- package/src/server/util/rschedule.ts +9 -0
- package/src/shared/__mocks__/platform.ts +7 -0
- package/src/shared/__snapshots__/months.test.ts.snap +21 -0
- package/src/shared/arithmetic.test.ts +112 -0
- package/src/shared/arithmetic.ts +170 -0
- package/src/shared/async.test.ts +135 -0
- package/src/shared/async.ts +76 -0
- package/src/shared/constants.ts +5 -0
- package/src/shared/currencies.ts +70 -0
- package/src/shared/dashboard.ts +260 -0
- package/src/shared/environment.ts +18 -0
- package/src/shared/errors.ts +195 -0
- package/src/shared/locale.ts +27 -0
- package/src/shared/location-utils.test.ts +69 -0
- package/src/shared/location-utils.ts +49 -0
- package/src/shared/months.test.ts +5 -0
- package/src/shared/months.ts +485 -0
- package/src/shared/normalisation.ts +6 -0
- package/src/shared/platform.electron.ts +21 -0
- package/src/shared/platform.ts +20 -0
- package/src/shared/query.ts +176 -0
- package/src/shared/rules.test.ts +56 -0
- package/src/shared/rules.ts +371 -0
- package/src/shared/schedules.test.ts +570 -0
- package/src/shared/schedules.ts +560 -0
- package/src/shared/test-helpers.ts +156 -0
- package/src/shared/transactions.test.ts +275 -0
- package/src/shared/transactions.ts +433 -0
- package/src/shared/transfer.test.ts +75 -0
- package/src/shared/transfer.ts +16 -0
- package/src/shared/user.ts +4 -0
- package/src/shared/util.test.ts +240 -0
- package/src/shared/util.ts +633 -0
- package/src/types/api-handlers.ts +287 -0
- package/src/types/budget.ts +8 -0
- package/src/types/file.ts +47 -0
- package/src/types/handlers.ts +46 -0
- package/src/types/models/account.ts +24 -0
- package/src/types/models/bank-sync.ts +23 -0
- package/src/types/models/bank.ts +6 -0
- package/src/types/models/category-group.ts +11 -0
- package/src/types/models/category.ts +13 -0
- package/src/types/models/dashboard.ts +199 -0
- package/src/types/models/gocardless.ts +84 -0
- package/src/types/models/import-transaction.ts +56 -0
- package/src/types/models/index.ts +23 -0
- package/src/types/models/nearby-payee.ts +7 -0
- package/src/types/models/note.ts +4 -0
- package/src/types/models/openid.ts +8 -0
- package/src/types/models/payee-location.ts +8 -0
- package/src/types/models/payee.ts +10 -0
- package/src/types/models/pluggyai.ts +19 -0
- package/src/types/models/reports.ts +144 -0
- package/src/types/models/rule.ts +174 -0
- package/src/types/models/schedule.ts +49 -0
- package/src/types/models/simplefin.ts +28 -0
- package/src/types/models/tags.ts +6 -0
- package/src/types/models/templates.ts +135 -0
- package/src/types/models/transaction-filter.ts +9 -0
- package/src/types/models/transaction.ts +39 -0
- package/src/types/models/user-access.ts +10 -0
- package/src/types/models/user.ts +25 -0
- package/src/types/prefs.ts +167 -0
- package/src/types/server-events.ts +86 -0
- package/src/types/server-handlers.ts +27 -0
- package/src/types/util.ts +26 -0
- package/tsconfig.json +34 -0
- package/typings/pegjs.ts +1 -0
- package/typings/process-worker.ts +12 -0
- package/typings/vite-plugin-peggy-loader.ts +1 -0
- package/typings/window.ts +62 -0
- package/vite.config.ts +109 -0
- package/vite.desktop.config.ts +59 -0
- package/vitest.config.ts +43 -0
- package/vitest.web.config.ts +38 -0
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
// @ts-strict-ignore
|
|
2
|
+
import * as CRDT from '@actual-app/crdt';
|
|
3
|
+
|
|
4
|
+
import { createTestBudget } from '../../mocks/budget';
|
|
5
|
+
import { captureBreadcrumb, captureException } from '../../platform/exceptions';
|
|
6
|
+
import * as asyncStorage from '../../platform/server/asyncStorage';
|
|
7
|
+
import * as connection from '../../platform/server/connection';
|
|
8
|
+
import * as fs from '../../platform/server/fs';
|
|
9
|
+
import { logger } from '../../platform/server/log';
|
|
10
|
+
import * as Platform from '../../shared/platform';
|
|
11
|
+
import type { Budget } from '../../types/budget';
|
|
12
|
+
import { createApp } from '../app';
|
|
13
|
+
import * as budget from '../budget/base';
|
|
14
|
+
import * as cloudStorage from '../cloud-storage';
|
|
15
|
+
import * as db from '../db';
|
|
16
|
+
import * as mappings from '../db/mappings';
|
|
17
|
+
import { handleBudgetImport } from '../importers';
|
|
18
|
+
import type { ImportableBudgetType } from '../importers';
|
|
19
|
+
import { app as mainApp } from '../main-app';
|
|
20
|
+
import { mutator } from '../mutators';
|
|
21
|
+
import * as prefs from '../prefs';
|
|
22
|
+
import { getServer } from '../server-config';
|
|
23
|
+
import * as sheet from '../sheet';
|
|
24
|
+
import { clearFullSyncTimeout, initialFullSync, setSyncingMode } from '../sync';
|
|
25
|
+
import * as syncMigrations from '../sync/migrate';
|
|
26
|
+
import * as rules from '../transactions/transaction-rules';
|
|
27
|
+
import { clearUndo } from '../undo';
|
|
28
|
+
import { updateVersion } from '../update';
|
|
29
|
+
import {
|
|
30
|
+
idFromBudgetName,
|
|
31
|
+
uniqueBudgetName,
|
|
32
|
+
validateBudgetName,
|
|
33
|
+
} from '../util/budget-name';
|
|
34
|
+
|
|
35
|
+
import {
|
|
36
|
+
loadBackup as _loadBackup,
|
|
37
|
+
makeBackup as _makeBackup,
|
|
38
|
+
getAvailableBackups,
|
|
39
|
+
startBackupService,
|
|
40
|
+
stopBackupService,
|
|
41
|
+
} from './backups';
|
|
42
|
+
|
|
43
|
+
const DEMO_BUDGET_ID = '_demo-budget';
|
|
44
|
+
const TEST_BUDGET_ID = '_test-budget';
|
|
45
|
+
|
|
46
|
+
export type BudgetFileHandlers = {
|
|
47
|
+
'validate-budget-name': typeof handleValidateBudgetName;
|
|
48
|
+
'unique-budget-name': typeof handleUniqueBudgetName;
|
|
49
|
+
'get-budgets': typeof getBudgets;
|
|
50
|
+
'get-remote-files': typeof getRemoteFiles;
|
|
51
|
+
'reset-budget-cache': typeof resetBudgetCache;
|
|
52
|
+
'upload-budget': typeof uploadBudget;
|
|
53
|
+
'download-budget': typeof downloadBudget;
|
|
54
|
+
'sync-budget': typeof syncBudget;
|
|
55
|
+
'load-budget': typeof loadBudget;
|
|
56
|
+
'create-demo-budget': typeof createDemoBudget;
|
|
57
|
+
'close-budget': typeof closeBudget;
|
|
58
|
+
'delete-budget': typeof deleteBudget;
|
|
59
|
+
'duplicate-budget': typeof duplicateBudget;
|
|
60
|
+
'create-budget': typeof createBudget;
|
|
61
|
+
'import-budget': typeof importBudget;
|
|
62
|
+
'export-budget': typeof exportBudget;
|
|
63
|
+
'upload-file-web': typeof uploadFileWeb;
|
|
64
|
+
'backups-get': typeof getBackups;
|
|
65
|
+
'backup-load': typeof loadBackup;
|
|
66
|
+
'backup-make': typeof makeBackup;
|
|
67
|
+
'get-last-opened-backup': typeof getLastOpenedBackup;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
export const app = createApp<BudgetFileHandlers>();
|
|
71
|
+
app.method('validate-budget-name', handleValidateBudgetName);
|
|
72
|
+
app.method('unique-budget-name', handleUniqueBudgetName);
|
|
73
|
+
app.method('get-budgets', getBudgets);
|
|
74
|
+
app.method('get-remote-files', getRemoteFiles);
|
|
75
|
+
app.method('reset-budget-cache', mutator(resetBudgetCache));
|
|
76
|
+
app.method('upload-budget', uploadBudget);
|
|
77
|
+
app.method('download-budget', downloadBudget);
|
|
78
|
+
app.method('sync-budget', syncBudget);
|
|
79
|
+
app.method('load-budget', loadBudget);
|
|
80
|
+
app.method('create-demo-budget', createDemoBudget);
|
|
81
|
+
app.method('close-budget', closeBudget);
|
|
82
|
+
app.method('delete-budget', deleteBudget);
|
|
83
|
+
app.method('duplicate-budget', duplicateBudget);
|
|
84
|
+
app.method('create-budget', createBudget);
|
|
85
|
+
app.method('import-budget', importBudget);
|
|
86
|
+
app.method('export-budget', exportBudget);
|
|
87
|
+
app.method('upload-file-web', uploadFileWeb);
|
|
88
|
+
app.method('backups-get', getBackups);
|
|
89
|
+
app.method('backup-load', loadBackup);
|
|
90
|
+
app.method('backup-make', makeBackup);
|
|
91
|
+
app.method('get-last-opened-backup', getLastOpenedBackup);
|
|
92
|
+
|
|
93
|
+
async function handleValidateBudgetName({ name }: { name: string }) {
|
|
94
|
+
return validateBudgetName(name);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async function handleUniqueBudgetName({ name }: { name: string }) {
|
|
98
|
+
return uniqueBudgetName(name);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function getBudgets() {
|
|
102
|
+
const paths = await fs.listDir(fs.getDocumentDir());
|
|
103
|
+
const budgets: (Budget | null)[] = await Promise.all(
|
|
104
|
+
paths.map(async name => {
|
|
105
|
+
const prefsPath = fs.join(fs.getDocumentDir(), name, 'metadata.json');
|
|
106
|
+
if (await fs.exists(prefsPath)) {
|
|
107
|
+
let prefs;
|
|
108
|
+
try {
|
|
109
|
+
prefs = JSON.parse(await fs.readFile(prefsPath));
|
|
110
|
+
} catch (e) {
|
|
111
|
+
logger.log('Error parsing metadata:', e.stack);
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// We treat the directory name as the canonical id so that if
|
|
116
|
+
// the user moves it around/renames/etc, nothing breaks. The
|
|
117
|
+
// id is stored in prefs just for convenience (and the prefs
|
|
118
|
+
// will always update to the latest given id)
|
|
119
|
+
if (name !== DEMO_BUDGET_ID) {
|
|
120
|
+
return {
|
|
121
|
+
id: name,
|
|
122
|
+
...(prefs.cloudFileId ? { cloudFileId: prefs.cloudFileId } : {}),
|
|
123
|
+
...(prefs.encryptKeyId ? { encryptKeyId: prefs.encryptKeyId } : {}),
|
|
124
|
+
...(prefs.groupId ? { groupId: prefs.groupId } : {}),
|
|
125
|
+
...(prefs.owner ? { owner: prefs.owner } : {}),
|
|
126
|
+
name: prefs.budgetName || '(no name)',
|
|
127
|
+
} satisfies Budget;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return null;
|
|
132
|
+
}),
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
return budgets.filter(Boolean) as Budget[];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function getRemoteFiles() {
|
|
139
|
+
return cloudStorage.listRemoteFiles();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function resetBudgetCache() {
|
|
143
|
+
// Recomputing everything will update the cache
|
|
144
|
+
await sheet.loadUserBudgets(db);
|
|
145
|
+
sheet.get().recomputeAll();
|
|
146
|
+
await sheet.waitOnSpreadsheet();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function uploadBudget({ id }: { id?: Budget['id'] } = {}): Promise<{
|
|
150
|
+
error?: { reason: string };
|
|
151
|
+
}> {
|
|
152
|
+
if (id) {
|
|
153
|
+
if (prefs.getPrefs()) {
|
|
154
|
+
throw new Error('upload-budget: id given but prefs already loaded');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
await prefs.loadPrefs(id);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
await cloudStorage.upload();
|
|
162
|
+
} catch (e) {
|
|
163
|
+
logger.log(e);
|
|
164
|
+
if (e.type === 'FileUploadError') {
|
|
165
|
+
return { error: e };
|
|
166
|
+
}
|
|
167
|
+
captureException(e);
|
|
168
|
+
return { error: { reason: 'internal' } };
|
|
169
|
+
} finally {
|
|
170
|
+
if (id) {
|
|
171
|
+
prefs.unloadPrefs();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return {};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function downloadBudget({
|
|
179
|
+
cloudFileId,
|
|
180
|
+
}: {
|
|
181
|
+
cloudFileId: Budget['cloudFileId'];
|
|
182
|
+
}): Promise<{ id?: Budget['id']; error?: { reason: string; meta?: unknown } }> {
|
|
183
|
+
let result;
|
|
184
|
+
try {
|
|
185
|
+
result = await cloudStorage.download(cloudFileId);
|
|
186
|
+
} catch (e) {
|
|
187
|
+
if (e.type === 'FileDownloadError') {
|
|
188
|
+
if (e.reason === 'file-exists' && e.meta.id) {
|
|
189
|
+
await prefs.loadPrefs(e.meta.id);
|
|
190
|
+
const name = prefs.getPrefs().budgetName;
|
|
191
|
+
prefs.unloadPrefs();
|
|
192
|
+
|
|
193
|
+
e.meta = { ...e.meta, name };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return { error: e };
|
|
197
|
+
} else {
|
|
198
|
+
captureException(e);
|
|
199
|
+
return { error: { reason: 'internal' } };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const id = result.id;
|
|
204
|
+
await closeBudget();
|
|
205
|
+
await loadBudget({ id });
|
|
206
|
+
result = await syncBudget();
|
|
207
|
+
|
|
208
|
+
if (result.error) {
|
|
209
|
+
return result;
|
|
210
|
+
}
|
|
211
|
+
return { id };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// open and sync, but don't close
|
|
215
|
+
async function syncBudget() {
|
|
216
|
+
setSyncingMode('enabled');
|
|
217
|
+
const result = await initialFullSync();
|
|
218
|
+
|
|
219
|
+
return result;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async function loadBudget({ id }: { id: Budget['id'] }) {
|
|
223
|
+
const currentPrefs = prefs.getPrefs();
|
|
224
|
+
|
|
225
|
+
if (currentPrefs) {
|
|
226
|
+
if (currentPrefs.id === id) {
|
|
227
|
+
// If it's already loaded, do nothing
|
|
228
|
+
return {};
|
|
229
|
+
} else {
|
|
230
|
+
// Otherwise, close the currently loaded budget
|
|
231
|
+
await closeBudget();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const res = await _loadBudget(id);
|
|
236
|
+
|
|
237
|
+
return res;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function createDemoBudget() {
|
|
241
|
+
// Make sure the read only flag isn't leftover (normally it's
|
|
242
|
+
// reset when signing in, but you don't have to sign in for the
|
|
243
|
+
// demo budget)
|
|
244
|
+
await asyncStorage.setItem('readOnly', '');
|
|
245
|
+
|
|
246
|
+
return createBudget({
|
|
247
|
+
budgetName: 'Demo Budget',
|
|
248
|
+
testMode: true,
|
|
249
|
+
testBudgetId: DEMO_BUDGET_ID,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function closeBudget() {
|
|
254
|
+
captureBreadcrumb({ message: 'Closing budget' });
|
|
255
|
+
|
|
256
|
+
// The spreadsheet may be running, wait for it to complete
|
|
257
|
+
await sheet.waitOnSpreadsheet();
|
|
258
|
+
sheet.unloadSpreadsheet();
|
|
259
|
+
|
|
260
|
+
clearFullSyncTimeout();
|
|
261
|
+
await mainApp.stopServices();
|
|
262
|
+
|
|
263
|
+
db.closeDatabase();
|
|
264
|
+
|
|
265
|
+
try {
|
|
266
|
+
await asyncStorage.setItem('lastBudget', '');
|
|
267
|
+
} catch {
|
|
268
|
+
// This might fail if we are shutting down after failing to load a
|
|
269
|
+
// budget. We want to unload whatever has already been loaded but
|
|
270
|
+
// be resilient to anything failing
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
prefs.unloadPrefs();
|
|
274
|
+
stopBackupService();
|
|
275
|
+
return 'ok';
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function deleteBudget({
|
|
279
|
+
id,
|
|
280
|
+
cloudFileId,
|
|
281
|
+
}: {
|
|
282
|
+
id?: Budget['id'];
|
|
283
|
+
cloudFileId?: Budget['cloudFileId'];
|
|
284
|
+
}) {
|
|
285
|
+
// If it's a cloud file, you can delete it from the server by
|
|
286
|
+
// passing its cloud id
|
|
287
|
+
if (cloudFileId) {
|
|
288
|
+
await cloudStorage.removeFile(cloudFileId).catch(() => {
|
|
289
|
+
// Ignore errors
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// If a local file exists, you can delete it by passing its local id
|
|
294
|
+
if (id) {
|
|
295
|
+
// opening and then closing the database is a hack to be able to delete
|
|
296
|
+
// the budget file if it hasn't been opened yet. This needs a better
|
|
297
|
+
// way, but works for now.
|
|
298
|
+
try {
|
|
299
|
+
await db.openDatabase(id);
|
|
300
|
+
db.closeDatabase();
|
|
301
|
+
const budgetDir = fs.getBudgetDir(id);
|
|
302
|
+
await fs.removeDirRecursively(budgetDir);
|
|
303
|
+
} catch {
|
|
304
|
+
return 'fail';
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return 'ok';
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async function duplicateBudget({
|
|
312
|
+
id,
|
|
313
|
+
newName,
|
|
314
|
+
cloudSync,
|
|
315
|
+
open,
|
|
316
|
+
}: {
|
|
317
|
+
id: Budget['id'];
|
|
318
|
+
newName: Budget['name'];
|
|
319
|
+
cloudSync: boolean;
|
|
320
|
+
open: 'none' | 'original' | 'copy';
|
|
321
|
+
}): Promise<Budget['id']> {
|
|
322
|
+
const { valid, message } = await validateBudgetName(newName);
|
|
323
|
+
if (!valid) throw new Error(message);
|
|
324
|
+
|
|
325
|
+
const budgetDir = fs.getBudgetDir(id);
|
|
326
|
+
|
|
327
|
+
const newId = await idFromBudgetName(newName);
|
|
328
|
+
|
|
329
|
+
// copy metadata from current budget
|
|
330
|
+
// replace id with new budget id and budgetName with new budget name
|
|
331
|
+
const metadataText = await fs.readFile(fs.join(budgetDir, 'metadata.json'));
|
|
332
|
+
const metadata = JSON.parse(metadataText);
|
|
333
|
+
metadata.id = newId;
|
|
334
|
+
metadata.budgetName = newName;
|
|
335
|
+
[
|
|
336
|
+
'cloudFileId',
|
|
337
|
+
'groupId',
|
|
338
|
+
'lastUploaded',
|
|
339
|
+
'encryptKeyId',
|
|
340
|
+
'lastSyncedTimestamp',
|
|
341
|
+
].forEach(item => {
|
|
342
|
+
if (metadata[item]) delete metadata[item];
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
try {
|
|
346
|
+
const newBudgetDir = fs.getBudgetDir(newId);
|
|
347
|
+
await fs.mkdir(newBudgetDir);
|
|
348
|
+
|
|
349
|
+
// write metadata for new budget
|
|
350
|
+
await fs.writeFile(
|
|
351
|
+
fs.join(newBudgetDir, 'metadata.json'),
|
|
352
|
+
JSON.stringify(metadata),
|
|
353
|
+
);
|
|
354
|
+
|
|
355
|
+
await fs.copyFile(
|
|
356
|
+
fs.join(budgetDir, 'db.sqlite'),
|
|
357
|
+
fs.join(newBudgetDir, 'db.sqlite'),
|
|
358
|
+
);
|
|
359
|
+
} catch (error) {
|
|
360
|
+
// Clean up any partially created files
|
|
361
|
+
try {
|
|
362
|
+
const newBudgetDir = fs.getBudgetDir(newId);
|
|
363
|
+
if (await fs.exists(newBudgetDir)) {
|
|
364
|
+
await fs.removeDirRecursively(newBudgetDir);
|
|
365
|
+
}
|
|
366
|
+
} catch {} // Ignore cleanup errors
|
|
367
|
+
throw new Error(`Failed to duplicate budget file: ${error.message}`);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// load in and validate
|
|
371
|
+
const { error } = await _loadBudget(newId);
|
|
372
|
+
if (error) {
|
|
373
|
+
logger.log('Error duplicating budget: ' + error);
|
|
374
|
+
return error;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (cloudSync) {
|
|
378
|
+
try {
|
|
379
|
+
await cloudStorage.upload();
|
|
380
|
+
} catch (error) {
|
|
381
|
+
logger.warn('Failed to sync duplicated budget to cloud:', error);
|
|
382
|
+
// Ignore any errors uploading. If they are offline they should
|
|
383
|
+
// still be able to create files.
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
await closeBudget();
|
|
388
|
+
if (open === 'original') await _loadBudget(id);
|
|
389
|
+
if (open === 'copy') await _loadBudget(newId);
|
|
390
|
+
|
|
391
|
+
return newId;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
async function createBudget({
|
|
395
|
+
budgetName,
|
|
396
|
+
avoidUpload,
|
|
397
|
+
testMode,
|
|
398
|
+
testBudgetId,
|
|
399
|
+
}: {
|
|
400
|
+
budgetName?: Budget['name'];
|
|
401
|
+
avoidUpload?: boolean;
|
|
402
|
+
testMode?: boolean;
|
|
403
|
+
testBudgetId?: Budget['name'];
|
|
404
|
+
} = {}) {
|
|
405
|
+
let id;
|
|
406
|
+
if (testMode) {
|
|
407
|
+
budgetName = budgetName || 'Test Budget';
|
|
408
|
+
id = testBudgetId || TEST_BUDGET_ID;
|
|
409
|
+
|
|
410
|
+
if (await fs.exists(fs.getBudgetDir(id))) {
|
|
411
|
+
await fs.removeDirRecursively(fs.getBudgetDir(id));
|
|
412
|
+
}
|
|
413
|
+
} else {
|
|
414
|
+
// Generate budget name if not given
|
|
415
|
+
if (!budgetName) {
|
|
416
|
+
budgetName = await uniqueBudgetName();
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
id = await idFromBudgetName(budgetName);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const budgetDir = fs.getBudgetDir(id);
|
|
423
|
+
await fs.mkdir(budgetDir);
|
|
424
|
+
|
|
425
|
+
// Create the initial database
|
|
426
|
+
await fs.copyFile(fs.bundledDatabasePath, fs.join(budgetDir, 'db.sqlite'));
|
|
427
|
+
|
|
428
|
+
// Create the initial prefs file
|
|
429
|
+
await fs.writeFile(
|
|
430
|
+
fs.join(budgetDir, 'metadata.json'),
|
|
431
|
+
JSON.stringify(prefs.getDefaultPrefs(id, budgetName)),
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
// Load it in
|
|
435
|
+
const { error } = await _loadBudget(id);
|
|
436
|
+
if (error) {
|
|
437
|
+
logger.log('Error creating budget: ' + error);
|
|
438
|
+
return { error };
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (!avoidUpload && !testMode) {
|
|
442
|
+
try {
|
|
443
|
+
await cloudStorage.upload();
|
|
444
|
+
} catch {
|
|
445
|
+
// Ignore any errors uploading. If they are offline they should
|
|
446
|
+
// still be able to create files.
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (testMode) {
|
|
451
|
+
await createTestBudget(mainApp.handlers);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return {};
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async function importBudget({
|
|
458
|
+
filepath,
|
|
459
|
+
type,
|
|
460
|
+
}: {
|
|
461
|
+
filepath: string;
|
|
462
|
+
type: ImportableBudgetType;
|
|
463
|
+
}): Promise<{ error?: string }> {
|
|
464
|
+
try {
|
|
465
|
+
if (!(await fs.exists(filepath))) {
|
|
466
|
+
throw new Error(`File not found at the provided path: ${filepath}`);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const buffer = Buffer.from(await fs.readFile(filepath, 'binary'));
|
|
470
|
+
const results = await handleBudgetImport(type, filepath, buffer);
|
|
471
|
+
return results || {};
|
|
472
|
+
} catch (err) {
|
|
473
|
+
err.message = 'Error importing budget: ' + err.message;
|
|
474
|
+
captureException(err);
|
|
475
|
+
return { error: 'internal-error' };
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async function exportBudget() {
|
|
480
|
+
try {
|
|
481
|
+
return {
|
|
482
|
+
data: await cloudStorage.exportBuffer(),
|
|
483
|
+
};
|
|
484
|
+
} catch (err) {
|
|
485
|
+
err.message = 'Error exporting budget: ' + err.message;
|
|
486
|
+
captureException(err);
|
|
487
|
+
return { error: 'internal-error' };
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function onSheetChange({ names }: { names: string[] }) {
|
|
492
|
+
const nodes = names.map(name => {
|
|
493
|
+
const node = sheet.get()._getNode(name);
|
|
494
|
+
return { name: node.name, value: node.value };
|
|
495
|
+
});
|
|
496
|
+
connection.send('cells-changed', nodes);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
async function _loadBudget(id: Budget['id']): Promise<{
|
|
500
|
+
error?:
|
|
501
|
+
| 'budget-not-found'
|
|
502
|
+
| 'loading-budget'
|
|
503
|
+
| 'out-of-sync-migrations'
|
|
504
|
+
| 'out-of-sync-data'
|
|
505
|
+
| 'opening-budget';
|
|
506
|
+
}> {
|
|
507
|
+
let dir: string;
|
|
508
|
+
try {
|
|
509
|
+
dir = fs.getBudgetDir(id);
|
|
510
|
+
} catch (e) {
|
|
511
|
+
captureException(
|
|
512
|
+
new Error('`getBudgetDir` failed in `loadBudget`: ' + e.message),
|
|
513
|
+
);
|
|
514
|
+
return { error: 'budget-not-found' };
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
captureBreadcrumb({ message: 'Loading budget ' + dir });
|
|
518
|
+
|
|
519
|
+
if (!(await fs.exists(dir))) {
|
|
520
|
+
captureException(new Error('budget directory does not exist'));
|
|
521
|
+
return { error: 'budget-not-found' };
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
try {
|
|
525
|
+
await prefs.loadPrefs(id);
|
|
526
|
+
await db.openDatabase(id);
|
|
527
|
+
} catch (e) {
|
|
528
|
+
captureBreadcrumb({ message: 'Error loading budget ' + id });
|
|
529
|
+
captureException(e);
|
|
530
|
+
await closeBudget();
|
|
531
|
+
return { error: 'opening-budget' };
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Older versions didn't tag the file with the current user, so do
|
|
535
|
+
// so now
|
|
536
|
+
if (!prefs.getPrefs().userId) {
|
|
537
|
+
const userId = await asyncStorage.getItem('user-token');
|
|
538
|
+
await prefs.savePrefs({ userId });
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
await updateVersion();
|
|
543
|
+
} catch (e) {
|
|
544
|
+
logger.warn('Error updating', e);
|
|
545
|
+
let result;
|
|
546
|
+
if (e.message.includes('out-of-sync-migrations')) {
|
|
547
|
+
result = { error: 'out-of-sync-migrations' };
|
|
548
|
+
} else if (e.message.includes('out-of-sync-data')) {
|
|
549
|
+
result = { error: 'out-of-sync-data' };
|
|
550
|
+
} else {
|
|
551
|
+
captureException(e);
|
|
552
|
+
logger.info('Error updating budget ' + id, e);
|
|
553
|
+
logger.log('Error updating budget', e);
|
|
554
|
+
result = { error: 'loading-budget' };
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
await closeBudget();
|
|
558
|
+
return result;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
await db.loadClock();
|
|
562
|
+
|
|
563
|
+
if (prefs.getPrefs().resetClock) {
|
|
564
|
+
// If we need to generate a fresh clock, we need to generate a new
|
|
565
|
+
// client id. This happens when the database is transferred to a
|
|
566
|
+
// new device.
|
|
567
|
+
//
|
|
568
|
+
// TODO: The client id should be stored elsewhere. It shouldn't
|
|
569
|
+
// work this way, but it's fine for now.
|
|
570
|
+
CRDT.getClock().timestamp.setNode(CRDT.makeClientId());
|
|
571
|
+
db.runQuery(
|
|
572
|
+
'INSERT OR REPLACE INTO messages_clock (id, clock) VALUES (1, ?)',
|
|
573
|
+
[CRDT.serializeClock(CRDT.getClock())],
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
await prefs.savePrefs({ resetClock: false });
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (!Platform.isBrowser && process.env.NODE_ENV !== 'test') {
|
|
580
|
+
startBackupService(id);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
try {
|
|
584
|
+
await sheet.loadSpreadsheet(db, onSheetChange);
|
|
585
|
+
} catch (e) {
|
|
586
|
+
captureException(e);
|
|
587
|
+
await closeBudget();
|
|
588
|
+
return { error: 'opening-budget' };
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// This is a bit leaky, but we need to set the initial budget type
|
|
592
|
+
const { value: budgetType = 'envelope' } =
|
|
593
|
+
(await db.first<Pick<db.DbPreference, 'value'>>(
|
|
594
|
+
'SELECT value from preferences WHERE id = ?',
|
|
595
|
+
['budgetType'],
|
|
596
|
+
)) ?? {};
|
|
597
|
+
sheet.get().meta().budgetType = budgetType as prefs.BudgetType;
|
|
598
|
+
await budget.createAllBudgets();
|
|
599
|
+
|
|
600
|
+
// Load all the in-memory state
|
|
601
|
+
await mappings.loadMappings();
|
|
602
|
+
await rules.loadRules();
|
|
603
|
+
syncMigrations.listen();
|
|
604
|
+
mainApp.startServices();
|
|
605
|
+
|
|
606
|
+
clearUndo();
|
|
607
|
+
|
|
608
|
+
// Ensure that syncing is enabled
|
|
609
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
610
|
+
if (id === DEMO_BUDGET_ID) {
|
|
611
|
+
setSyncingMode('disabled');
|
|
612
|
+
} else {
|
|
613
|
+
if (getServer()) {
|
|
614
|
+
setSyncingMode('enabled');
|
|
615
|
+
} else {
|
|
616
|
+
setSyncingMode('disabled');
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
await asyncStorage.setItem('lastBudget', id);
|
|
620
|
+
|
|
621
|
+
await cloudStorage.possiblyUpload();
|
|
622
|
+
}
|
|
623
|
+
} else {
|
|
624
|
+
// we're in a test - disable the sync
|
|
625
|
+
setSyncingMode('disabled');
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
app.events.emit('load-budget', { id });
|
|
629
|
+
|
|
630
|
+
return {};
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
async function uploadFileWeb({
|
|
634
|
+
filename,
|
|
635
|
+
contents,
|
|
636
|
+
}: {
|
|
637
|
+
filename: string;
|
|
638
|
+
contents: ArrayBuffer;
|
|
639
|
+
}) {
|
|
640
|
+
if (!Platform.isBrowser) {
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
await fs.writeFile('/uploads/' + filename, contents);
|
|
645
|
+
return {};
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
async function getBackups({ id }) {
|
|
649
|
+
return getAvailableBackups(id);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
async function loadBackup({ id, backupId }) {
|
|
653
|
+
await _loadBackup(id, backupId);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
async function makeBackup({ id }) {
|
|
657
|
+
await _makeBackup(id);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
async function getLastOpenedBackup() {
|
|
661
|
+
const id = await asyncStorage.getItem('lastBudget');
|
|
662
|
+
if (id && id !== '') {
|
|
663
|
+
const budgetDir = fs.getBudgetDir(id);
|
|
664
|
+
|
|
665
|
+
// We never want to give back a budget that does not exist on the
|
|
666
|
+
// filesystem anymore, so first check that it exists
|
|
667
|
+
if (await fs.exists(budgetDir)) {
|
|
668
|
+
return id;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
return null;
|
|
672
|
+
}
|