@actual-app/sync-server 25.4.0-alpha.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 (137) hide show
  1. package/.dockerignore +12 -0
  2. package/README.md +19 -0
  3. package/app.js +11 -0
  4. package/babel.config.json +3 -0
  5. package/bin/@actual-app/sync-server +55 -0
  6. package/docker/alpine.Dockerfile +62 -0
  7. package/docker/ubuntu.Dockerfile +63 -0
  8. package/docker-compose.yml +29 -0
  9. package/jest.config.json +19 -0
  10. package/jest.global-setup.js +101 -0
  11. package/jest.global-teardown.js +6 -0
  12. package/migrations/1694360000000-create-folders.js +25 -0
  13. package/migrations/1694360479680-create-account-db.js +30 -0
  14. package/migrations/1694362247011-create-secret-table.js +16 -0
  15. package/migrations/1702667624000-rename-nordigen-secrets.js +19 -0
  16. package/migrations/1718889148000-openid.js +41 -0
  17. package/migrations/1719409568000-multiuser.js +116 -0
  18. package/package.json +64 -0
  19. package/src/account-db.js +239 -0
  20. package/src/accounts/openid.js +361 -0
  21. package/src/accounts/password.js +149 -0
  22. package/src/app-account.js +155 -0
  23. package/src/app-admin.js +410 -0
  24. package/src/app-admin.test.js +381 -0
  25. package/src/app-gocardless/README.md +198 -0
  26. package/src/app-gocardless/app-gocardless.js +274 -0
  27. package/src/app-gocardless/bank-factory.js +91 -0
  28. package/src/app-gocardless/banks/abanca_caglesmm.js +22 -0
  29. package/src/app-gocardless/banks/abnamro_abnanl2a.js +57 -0
  30. package/src/app-gocardless/banks/american_express_aesudef1.js +40 -0
  31. package/src/app-gocardless/banks/bancsabadell_bsabesbbb.js +31 -0
  32. package/src/app-gocardless/banks/bank.interface.ts +51 -0
  33. package/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js +39 -0
  34. package/src/app-gocardless/banks/bankinter_bkbkesmm.js +24 -0
  35. package/src/app-gocardless/banks/belfius_gkccbebb.js +17 -0
  36. package/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js +61 -0
  37. package/src/app-gocardless/banks/bnp_be_gebabebb.js +73 -0
  38. package/src/app-gocardless/banks/cbc_cregbebb.js +34 -0
  39. package/src/app-gocardless/banks/commerzbank_cobadeff.js +51 -0
  40. package/src/app-gocardless/banks/danskebank_dabno22.js +39 -0
  41. package/src/app-gocardless/banks/direkt_heladef1822.js +18 -0
  42. package/src/app-gocardless/banks/easybank_bawaatww.js +50 -0
  43. package/src/app-gocardless/banks/entercard_swednokk.js +40 -0
  44. package/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js +46 -0
  45. package/src/app-gocardless/banks/hype_hyeeit22.js +74 -0
  46. package/src/app-gocardless/banks/ing_ingbrobu.js +70 -0
  47. package/src/app-gocardless/banks/ing_ingddeff.js +47 -0
  48. package/src/app-gocardless/banks/ing_pl_ingbplpw.js +46 -0
  49. package/src/app-gocardless/banks/integration-bank.js +115 -0
  50. package/src/app-gocardless/banks/isybank_itbbitmm.js +18 -0
  51. package/src/app-gocardless/banks/kbc_kredbebb.js +33 -0
  52. package/src/app-gocardless/banks/lhv-lhvbee22.js +36 -0
  53. package/src/app-gocardless/banks/mbank_retail_brexplpw.js +56 -0
  54. package/src/app-gocardless/banks/nationwide_naiagb21.js +46 -0
  55. package/src/app-gocardless/banks/nbg_ethngraaxxx.js +51 -0
  56. package/src/app-gocardless/banks/norwegian_xx_norwnok1.js +74 -0
  57. package/src/app-gocardless/banks/revolut_revolt21.js +37 -0
  58. package/src/app-gocardless/banks/sandboxfinance_sfin0000.js +28 -0
  59. package/src/app-gocardless/banks/seb_kort_bank_ab.js +58 -0
  60. package/src/app-gocardless/banks/seb_privat.js +29 -0
  61. package/src/app-gocardless/banks/sparnord_spnodk22.js +24 -0
  62. package/src/app-gocardless/banks/spk_karlsruhe_karsde66.js +61 -0
  63. package/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js +30 -0
  64. package/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js +19 -0
  65. package/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js +50 -0
  66. package/src/app-gocardless/banks/swedbank_habalv22.js +47 -0
  67. package/src/app-gocardless/banks/tests/abanca_caglesmm.spec.js +21 -0
  68. package/src/app-gocardless/banks/tests/abnamro_abnanl2a.spec.js +61 -0
  69. package/src/app-gocardless/banks/tests/bancsabadell_bsabesbbb.spec.js +53 -0
  70. package/src/app-gocardless/banks/tests/belfius_gkccbebb.spec.js +22 -0
  71. package/src/app-gocardless/banks/tests/cbc_cregbebb.spec.js +34 -0
  72. package/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js +110 -0
  73. package/src/app-gocardless/banks/tests/easybank_bawaatww.spec.js +54 -0
  74. package/src/app-gocardless/banks/tests/fortuneo_ftnofrp1xxx.spec.js +206 -0
  75. package/src/app-gocardless/banks/tests/ing_ingddeff.spec.js +302 -0
  76. package/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js +202 -0
  77. package/src/app-gocardless/banks/tests/integration_bank.spec.js +158 -0
  78. package/src/app-gocardless/banks/tests/kbc_kredbebb.spec.js +38 -0
  79. package/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js +68 -0
  80. package/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js +171 -0
  81. package/src/app-gocardless/banks/tests/nationwide_naiagb21.spec.js +105 -0
  82. package/src/app-gocardless/banks/tests/nbg_ethngraaxxx.spec.js +48 -0
  83. package/src/app-gocardless/banks/tests/revolut_revolt21.spec.js +42 -0
  84. package/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js +133 -0
  85. package/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js +256 -0
  86. package/src/app-gocardless/banks/tests/ssk_dusseldorf_dussdeddxxx.spec.js +102 -0
  87. package/src/app-gocardless/banks/tests/swedbank_habalv22.spec.js +57 -0
  88. package/src/app-gocardless/banks/tests/virgin_nrnbgb22.spec.js +54 -0
  89. package/src/app-gocardless/banks/util/extract-payeeName-from-remittanceInfo.js +36 -0
  90. package/src/app-gocardless/banks/virgin_nrnbgb22.js +39 -0
  91. package/src/app-gocardless/errors.js +84 -0
  92. package/src/app-gocardless/gocardless-node.types.ts +497 -0
  93. package/src/app-gocardless/gocardless.types.ts +93 -0
  94. package/src/app-gocardless/link.html +18 -0
  95. package/src/app-gocardless/services/gocardless-service.js +620 -0
  96. package/src/app-gocardless/services/tests/fixtures.js +181 -0
  97. package/src/app-gocardless/services/tests/gocardless-service.spec.js +537 -0
  98. package/src/app-gocardless/tests/bank-factory.spec.js +20 -0
  99. package/src/app-gocardless/tests/utils.spec.js +162 -0
  100. package/src/app-gocardless/util/handle-error.js +16 -0
  101. package/src/app-gocardless/utils.js +45 -0
  102. package/src/app-openid.js +108 -0
  103. package/src/app-pluggyai/app-pluggyai.js +215 -0
  104. package/src/app-pluggyai/pluggyai-service.js +120 -0
  105. package/src/app-secrets.js +61 -0
  106. package/src/app-simplefin/app-simplefin.js +418 -0
  107. package/src/app-sync/errors.js +13 -0
  108. package/src/app-sync/services/files-service.js +243 -0
  109. package/src/app-sync/tests/services/files-service.test.js +250 -0
  110. package/src/app-sync/validation.js +77 -0
  111. package/src/app-sync.js +391 -0
  112. package/src/app-sync.test.js +877 -0
  113. package/src/app.js +145 -0
  114. package/src/config-types.ts +44 -0
  115. package/src/db.js +58 -0
  116. package/src/load-config.js +307 -0
  117. package/src/migrations.js +36 -0
  118. package/src/run-migrations.js +8 -0
  119. package/src/scripts/disable-openid.js +44 -0
  120. package/src/scripts/enable-openid.js +53 -0
  121. package/src/scripts/health-check.js +23 -0
  122. package/src/scripts/reset-password.js +51 -0
  123. package/src/secrets.test.js +83 -0
  124. package/src/services/secrets-service.js +94 -0
  125. package/src/services/user-service.js +272 -0
  126. package/src/sql/messages.sql +9 -0
  127. package/src/sync-simple.js +95 -0
  128. package/src/util/hash.js +5 -0
  129. package/src/util/middlewares.js +62 -0
  130. package/src/util/paths.js +13 -0
  131. package/src/util/payee-name.js +45 -0
  132. package/src/util/prompt.js +88 -0
  133. package/src/util/title/index.js +59 -0
  134. package/src/util/title/lower-case.js +93 -0
  135. package/src/util/title/specials.js +21 -0
  136. package/src/util/validate-user.js +68 -0
  137. package/tsconfig.json +21 -0
package/.dockerignore ADDED
@@ -0,0 +1,12 @@
1
+ node_modules
2
+ user-files
3
+ server-files
4
+
5
+ # Yarn
6
+ .pnp.*
7
+ .yarn/*
8
+ !.yarn/patches
9
+ !.yarn/plugins
10
+ !.yarn/releases
11
+ !.yarn/sdks
12
+ !.yarn/versions
package/README.md ADDED
@@ -0,0 +1,19 @@
1
+ This is the main project to run [Actual](https://github.com/actualbudget/actual), a local-first personal finance tool. It comes with the latest version of Actual, and a server to persist changes and make data available across all devices.
2
+
3
+ ### Getting Started
4
+
5
+ Actual is a local-first personal finance tool. It is 100% free and open-source, written in NodeJS, it has a synchronization element so that all your changes can move between devices without any heavy lifting.
6
+
7
+ If you are interested in contributing, or want to know how development works, see our [contributing](https://actualbudget.org/docs/contributing/) document we would love to have you.
8
+
9
+ Want to say thanks? Click the ⭐ at the top of the page.
10
+
11
+ ### Documentation
12
+
13
+ We have a wide range of documentation on how to use Actual. This is all available in our [Community Documentation](https://actualbudget.org/docs/), including topics on [installing](https://actualbudget.org/docs/install/), [Budgeting](https://actualbudget.org/docs/budgeting/), [Account Management](https://actualbudget.org/docs/accounts/), [Tips & Tricks](https://actualbudget.org/docs/getting-started/tips-tricks) and some documentation for developers.
14
+
15
+ ### Feature Requests
16
+
17
+ Current feature requests can be seen [here](https://github.com/actualbudget/actual/issues?q=is%3Aissue+label%3A%22needs+votes%22+sort%3Areactions-%2B1-desc). Vote for your favorite requests by reacting 👍 to the top comment of the request.
18
+
19
+ To add new feature requests, open a new Issue of the "Feature Request" type.
package/app.js ADDED
@@ -0,0 +1,11 @@
1
+ import { run as runMigrations } from './src/migrations.js';
2
+
3
+ runMigrations()
4
+ .then(() => {
5
+ //import the app here becasue initial migrations need to be run first - they are dependencies of the app.js
6
+ import('./src/app.js').then(app => app.run()); // run the app
7
+ })
8
+ .catch(err => {
9
+ console.log('Error starting app:', err);
10
+ process.exit(1);
11
+ });
@@ -0,0 +1,3 @@
1
+ {
2
+ "presets": ["@babel/preset-typescript"]
3
+ }
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+ import packageJson from '../../package.json' with { type: 'json' };
5
+
6
+ const getArgs = () =>
7
+ process.argv.reduce((args, arg) => {
8
+ // long arg
9
+ if (arg.slice(0, 2) === '--') {
10
+ const longArg = arg.split('=');
11
+ const longArgFlag = longArg[0].slice(2);
12
+ const longArgValue = longArg.length > 1 ? longArg[1] : true;
13
+ args[longArgFlag] = longArgValue;
14
+ }
15
+ // flags
16
+ else if (arg[0] === '-') {
17
+ const flags = arg.slice(1).split('');
18
+ flags.forEach(flag => {
19
+ args[flag] = true;
20
+ });
21
+ }
22
+ return args;
23
+ }, {});
24
+
25
+ const args = getArgs();
26
+
27
+ if (args.h || args.help) {
28
+ console.log(
29
+ [
30
+ 'usage: @actual-app/sync-server [options]',
31
+ '',
32
+ 'options:',
33
+ ' --config Path to config file',
34
+ '',
35
+ ' -h --help Print this list and exit.',
36
+ ' -v --version Print the version and exit.',
37
+ ].join('\n'),
38
+ );
39
+
40
+ process.exit();
41
+ }
42
+
43
+ if (args.v || args.version) {
44
+ console.log('v' + packageJson.version);
45
+
46
+ process.exit();
47
+ }
48
+
49
+ // start the sync server
50
+ if (args.config) {
51
+ console.log(`Loading config from ${args.config}`);
52
+ process.env.ACTUAL_CONFIG_PATH = args.config;
53
+ }
54
+
55
+ import('../../app.js');
@@ -0,0 +1,62 @@
1
+ FROM alpine:3.18 AS deps
2
+
3
+ # Install required packages
4
+ RUN apk add --no-cache nodejs yarn python3 openssl build-base
5
+
6
+ WORKDIR /app
7
+
8
+ # Copy only the files needed for installing dependencies
9
+ COPY .yarn ./.yarn
10
+ COPY yarn.lock package.json .yarnrc.yml ./
11
+ COPY packages/api/package.json packages/api/package.json
12
+ COPY packages/component-library/package.json packages/component-library/package.json
13
+ COPY packages/crdt/package.json packages/crdt/package.json
14
+ COPY packages/desktop-client/package.json packages/desktop-client/package.json
15
+ COPY packages/desktop-electron/package.json packages/desktop-electron/package.json
16
+ COPY packages/eslint-plugin-actual/package.json packages/eslint-plugin-actual/package.json
17
+ COPY packages/loot-core/package.json packages/loot-core/package.json
18
+ COPY packages/sync-server/package.json packages/sync-server/package.json
19
+
20
+ # Avoiding memory issues with ARMv7
21
+ RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi
22
+
23
+ # Focus the workspaces in production mode
24
+ RUN if [ "$(uname -m)" = "armv7l" ]; then npm_config_build_from_source=true yarn workspaces focus @actual-app/sync-server --production; else yarn workspaces focus @actual-app/sync-server --production; fi
25
+
26
+ FROM deps AS builder
27
+
28
+ WORKDIR /app
29
+
30
+ COPY packages/sync-server ./packages/sync-server
31
+
32
+ # Remove symbolic links for @actual-app/web and @actual-app/sync-server
33
+ RUN rm -rf ./node_modules/@actual-app/web ./node_modules/@actual-app/sync-server
34
+
35
+ # Copy in the @actual-app/web artifacts manually, so we don't need the entire packages folder
36
+ COPY packages/desktop-client/package.json ./node_modules/@actual-app/web/package.json
37
+ COPY packages/desktop-client/build ./node_modules/@actual-app/web/build
38
+
39
+ FROM alpine:3.18 AS prod
40
+
41
+ # Minimal runtime dependencies
42
+ RUN apk add --no-cache nodejs tini
43
+
44
+ # Create a non-root user
45
+ ARG USERNAME=actual
46
+ ARG USER_UID=1001
47
+ ARG USER_GID=$USER_UID
48
+ RUN addgroup -S ${USERNAME} -g ${USER_GID} && adduser -S ${USERNAME} -G ${USERNAME} -u ${USER_UID}
49
+ RUN mkdir /data && chown -R ${USERNAME}:${USERNAME} /data
50
+
51
+ WORKDIR /app
52
+ ENV NODE_ENV=production
53
+
54
+ # Pull in only the necessary artifacts (built node_modules, server files, etc.)
55
+ COPY --from=builder /app/node_modules /app/node_modules
56
+ COPY --from=builder /app/packages/sync-server/package.json /app/packages/sync-server/app.js ./
57
+ COPY --from=builder /app/packages/sync-server/src ./src
58
+ COPY --from=builder /app/packages/sync-server/migrations ./migrations
59
+
60
+ ENTRYPOINT ["/sbin/tini","-g", "--"]
61
+ EXPOSE 5006
62
+ CMD ["node", "app.js"]
@@ -0,0 +1,63 @@
1
+ FROM node:18-bookworm AS deps
2
+
3
+ # Install required packages
4
+ RUN apt-get update && apt-get install -y openssl
5
+
6
+ WORKDIR /app
7
+
8
+ # Copy only the files needed for installing dependencies
9
+ COPY .yarn ./.yarn
10
+ COPY yarn.lock package.json .yarnrc.yml ./
11
+ COPY packages/api/package.json packages/api/package.json
12
+ COPY packages/component-library/package.json packages/component-library/package.json
13
+ COPY packages/crdt/package.json packages/crdt/package.json
14
+ COPY packages/desktop-client/package.json packages/desktop-client/package.json
15
+ COPY packages/desktop-electron/package.json packages/desktop-electron/package.json
16
+ COPY packages/eslint-plugin-actual/package.json packages/eslint-plugin-actual/package.json
17
+ COPY packages/loot-core/package.json packages/loot-core/package.json
18
+ COPY packages/sync-server/package.json packages/sync-server/package.json
19
+
20
+ # Avoiding memory issues with ARMv7
21
+ RUN if [ "$(uname -m)" = "armv7l" ]; then yarn config set taskPoolConcurrency 2; yarn config set networkConcurrency 5; fi
22
+
23
+ # Focus the workspaces in production mode
24
+ RUN yarn workspaces focus @actual-app/sync-server --production
25
+
26
+ FROM deps AS builder
27
+
28
+ WORKDIR /app
29
+
30
+ COPY packages/sync-server ./packages/sync-server
31
+
32
+ # Remove symbolic links for @actual-app/web and @actual-app/sync-server
33
+ RUN rm -rf ./node_modules/@actual-app/web ./node_modules/@actual-app/sync-server
34
+
35
+ # Copy in the @actual-app/web artifacts manually, so we don't need the entire packages folder
36
+ COPY packages/desktop-client/package.json ./node_modules/@actual-app/web/package.json
37
+ COPY packages/desktop-client/build ./node_modules/@actual-app/web/build
38
+
39
+ FROM node:18-bookworm-slim AS prod
40
+
41
+ # Minimal runtime dependencies
42
+ RUN apt-get update && apt-get install tini && apt-get clean -y && rm -rf /var/lib/apt/lists/*
43
+
44
+ # Create a non-root user
45
+ ARG USERNAME=actual
46
+ ARG USER_UID=1001
47
+ ARG USER_GID=$USER_UID
48
+ RUN groupadd --gid $USER_GID $USERNAME \
49
+ && useradd --uid $USER_UID --gid $USER_GID -m $USERNAME \
50
+ && mkdir /data && chown -R ${USERNAME}:${USERNAME} /data
51
+
52
+ WORKDIR /app
53
+ ENV NODE_ENV=production
54
+
55
+ # Pull in only the necessary artifacts (built node_modules, server files, etc.)
56
+ COPY --from=builder /app/node_modules /app/node_modules
57
+ COPY --from=builder /app/packages/sync-server/package.json /app/packages/sync-server/app.js ./
58
+ COPY --from=builder /app/packages/sync-server/src ./src
59
+ COPY --from=builder /app/packages/sync-server/migrations ./migrations
60
+
61
+ ENTRYPOINT ["/usr/bin/tini","-g", "--"]
62
+ EXPOSE 5006
63
+ CMD ["node", "app.js"]
@@ -0,0 +1,29 @@
1
+ services:
2
+ actual_server:
3
+ image: docker.io/actualbudget/actual-server:latest
4
+ ports:
5
+ # This line makes Actual available at port 5006 of the device you run the server on,
6
+ # i.e. http://localhost:5006. You can change the first number to change the port, if you want.
7
+ - '5006:5006'
8
+ environment:
9
+ # Uncomment any of the lines below to set configuration options.
10
+ # - ACTUAL_HTTPS_KEY=/data/selfhost.key
11
+ # - ACTUAL_HTTPS_CERT=/data/selfhost.crt
12
+ # - ACTUAL_PORT=5006
13
+ # - ACTUAL_UPLOAD_FILE_SYNC_SIZE_LIMIT_MB=20
14
+ # - ACTUAL_UPLOAD_SYNC_ENCRYPTED_FILE_SYNC_SIZE_LIMIT_MB=50
15
+ # - ACTUAL_UPLOAD_FILE_SIZE_LIMIT_MB=20
16
+ # See all options and more details at https://actualbudget.github.io/docs/Installing/Configuration
17
+ # !! If you are not using any of these options, remove the 'environment:' tag entirely.
18
+ volumes:
19
+ # Change './actual-data' below to the path to the folder you want Actual to store its data in on your server.
20
+ # '/data' is the path Actual will look for its files in by default, so leave that as-is.
21
+ - ./actual-data:/data
22
+ healthcheck:
23
+ # Enable health check for the instance
24
+ test: ['CMD-SHELL', 'node src/scripts/health-check.js']
25
+ interval: 60s
26
+ timeout: 10s
27
+ retries: 3
28
+ start_period: 20s
29
+ restart: unless-stopped
@@ -0,0 +1,19 @@
1
+ {
2
+ "globalSetup": "./jest.global-setup.js",
3
+ "globalTeardown": "./jest.global-teardown.js",
4
+ "testPathIgnorePatterns": ["dist", "/node_modules/", "/build/"],
5
+ "roots": ["<rootDir>"],
6
+ "moduleFileExtensions": ["ts", "js", "json"],
7
+ "testEnvironment": "node",
8
+ "collectCoverage": true,
9
+ "collectCoverageFrom": ["**/*.{js,ts,tsx}"],
10
+ "coveragePathIgnorePatterns": [
11
+ "dist",
12
+ "/node_modules/",
13
+ "/build/",
14
+ "/coverage/"
15
+ ],
16
+ "coverageReporters": ["html", "lcov", "text", "text-summary"],
17
+ "resetMocks": true,
18
+ "restoreMocks": true
19
+ }
@@ -0,0 +1,101 @@
1
+ import { getAccountDb } from './src/account-db.js';
2
+ import { run as runMigrations } from './src/migrations.js';
3
+
4
+ const GENERIC_ADMIN_ID = 'genericAdmin';
5
+ const GENERIC_USER_ID = 'genericUser';
6
+ const ADMIN_ROLE_ID = 'ADMIN';
7
+ const BASIC_ROLE_ID = 'BASIC';
8
+
9
+ const createUser = (userId, userName, role, owner = 0, enabled = 1) => {
10
+ const missingParams = [];
11
+ if (!userId) missingParams.push('userId');
12
+ if (!userName) missingParams.push('userName');
13
+ if (!role) missingParams.push('role');
14
+ if (missingParams.length > 0) {
15
+ throw new Error(`Missing required parameters: ${missingParams.join(', ')}`);
16
+ }
17
+
18
+ if (
19
+ typeof userId !== 'string' ||
20
+ typeof userName !== 'string' ||
21
+ typeof role !== 'string'
22
+ ) {
23
+ throw new Error(
24
+ 'Invalid parameter types. userId, userName, and role must be strings',
25
+ );
26
+ }
27
+
28
+ try {
29
+ getAccountDb().mutate(
30
+ 'INSERT INTO users (id, user_name, display_name, enabled, owner, role) VALUES (?, ?, ?, ?, ?, ?)',
31
+ [userId, userName, userName, enabled, owner, role],
32
+ );
33
+ } catch (error) {
34
+ console.error(`Error creating user ${userName}:`, error);
35
+ throw error;
36
+ }
37
+ };
38
+
39
+ const setSessionUser = (userId, token = 'valid-token') => {
40
+ if (!userId) {
41
+ throw new Error('userId is required');
42
+ }
43
+
44
+ try {
45
+ const db = getAccountDb();
46
+ const session = db.first('SELECT token FROM sessions WHERE token = ?', [
47
+ token,
48
+ ]);
49
+ if (!session) {
50
+ throw new Error(`Session not found for token: ${token}`);
51
+ }
52
+
53
+ db.mutate('UPDATE sessions SET user_id = ? WHERE token = ?', [
54
+ userId,
55
+ token,
56
+ ]);
57
+ } catch (error) {
58
+ console.error(`Error updating session for user ${userId}:`, error);
59
+ throw error;
60
+ }
61
+ };
62
+
63
+ // eslint-disable-next-line import/no-default-export
64
+ export default async function setup() {
65
+ const NEVER_EXPIRES = -1; // or consider using a far future timestamp
66
+
67
+ await runMigrations();
68
+
69
+ createUser(GENERIC_ADMIN_ID, 'admin', ADMIN_ROLE_ID, 1);
70
+
71
+ // Insert a fake "valid-token" fixture that can be reused
72
+ const db = getAccountDb();
73
+ try {
74
+ await db.mutate('BEGIN TRANSACTION');
75
+
76
+ await db.mutate('DELETE FROM sessions');
77
+ await db.mutate(
78
+ 'INSERT INTO sessions (token, expires_at, user_id) VALUES (?, ?, ?)',
79
+ ['valid-token', NEVER_EXPIRES, 'genericAdmin'],
80
+ );
81
+ await db.mutate(
82
+ 'INSERT INTO sessions (token, expires_at, user_id) VALUES (?, ?, ?)',
83
+ ['valid-token-admin', NEVER_EXPIRES, 'genericAdmin'],
84
+ );
85
+
86
+ await db.mutate(
87
+ 'INSERT INTO sessions (token, expires_at, user_id) VALUES (?, ?, ?)',
88
+ ['valid-token-user', NEVER_EXPIRES, 'genericUser'],
89
+ );
90
+
91
+ await db.mutate('COMMIT');
92
+ } catch (error) {
93
+ await db.mutate('ROLLBACK');
94
+ throw new Error(`Failed to setup test sessions: ${error.message}`);
95
+ }
96
+
97
+ setSessionUser('genericAdmin');
98
+ setSessionUser('genericAdmin', 'valid-token-admin');
99
+
100
+ createUser(GENERIC_USER_ID, 'user', BASIC_ROLE_ID, 1);
101
+ }
@@ -0,0 +1,6 @@
1
+ import { run as runMigrations } from './src/migrations.js';
2
+
3
+ // eslint-disable-next-line import/no-default-export
4
+ export default async function teardown() {
5
+ await runMigrations('down');
6
+ }
@@ -0,0 +1,25 @@
1
+ import fs from 'node:fs/promises';
2
+
3
+ import { config } from '../src/load-config.js';
4
+
5
+ async function ensureExists(path) {
6
+ try {
7
+ await fs.mkdir(path);
8
+ } catch (err) {
9
+ if (err.code === 'EEXIST') {
10
+ return null;
11
+ }
12
+
13
+ throw err;
14
+ }
15
+ }
16
+
17
+ export const up = async function () {
18
+ await ensureExists(config.get('serverFiles'));
19
+ await ensureExists(config.get('userFiles'));
20
+ };
21
+
22
+ export const down = async function () {
23
+ await fs.rm(config.get('serverFiles'), { recursive: true, force: true });
24
+ await fs.rm(config.get('userFiles'), { recursive: true, force: true });
25
+ };
@@ -0,0 +1,30 @@
1
+ import { getAccountDb } from '../src/account-db.js';
2
+
3
+ export const up = async function () {
4
+ await getAccountDb().exec(`
5
+ CREATE TABLE IF NOT EXISTS auth
6
+ (password TEXT PRIMARY KEY);
7
+
8
+ CREATE TABLE IF NOT EXISTS sessions
9
+ (token TEXT PRIMARY KEY);
10
+
11
+ CREATE TABLE IF NOT EXISTS files
12
+ (id TEXT PRIMARY KEY,
13
+ group_id TEXT,
14
+ sync_version SMALLINT,
15
+ encrypt_meta TEXT,
16
+ encrypt_keyid TEXT,
17
+ encrypt_salt TEXT,
18
+ encrypt_test TEXT,
19
+ deleted BOOLEAN DEFAULT FALSE,
20
+ name TEXT);
21
+ `);
22
+ };
23
+
24
+ export const down = async function () {
25
+ await getAccountDb().exec(`
26
+ DROP TABLE auth;
27
+ DROP TABLE sessions;
28
+ DROP TABLE files;
29
+ `);
30
+ };
@@ -0,0 +1,16 @@
1
+ import { getAccountDb } from '../src/account-db.js';
2
+
3
+ export const up = async function () {
4
+ await getAccountDb().exec(`
5
+ CREATE TABLE IF NOT EXISTS secrets (
6
+ name TEXT PRIMARY KEY,
7
+ value BLOB
8
+ );
9
+ `);
10
+ };
11
+
12
+ export const down = async function () {
13
+ await getAccountDb().exec(`
14
+ DROP TABLE secrets;
15
+ `);
16
+ };
@@ -0,0 +1,19 @@
1
+ import { getAccountDb } from '../src/account-db.js';
2
+
3
+ export const up = async function () {
4
+ await getAccountDb().exec(
5
+ `UPDATE secrets SET name = 'gocardless_secretId' WHERE name = 'nordigen_secretId'`,
6
+ );
7
+ await getAccountDb().exec(
8
+ `UPDATE secrets SET name = 'gocardless_secretKey' WHERE name = 'nordigen_secretKey'`,
9
+ );
10
+ };
11
+
12
+ export const down = async function () {
13
+ await getAccountDb().exec(
14
+ `UPDATE secrets SET name = 'nordigen_secretId' WHERE name = 'gocardless_secretId'`,
15
+ );
16
+ await getAccountDb().exec(
17
+ `UPDATE secrets SET name = 'nordigen_secretKey' WHERE name = 'gocardless_secretKey'`,
18
+ );
19
+ };
@@ -0,0 +1,41 @@
1
+ import { getAccountDb } from '../src/account-db.js';
2
+
3
+ export const up = async function () {
4
+ await getAccountDb().exec(
5
+ `
6
+ BEGIN TRANSACTION;
7
+ CREATE TABLE auth_new
8
+ (method TEXT PRIMARY KEY,
9
+ display_name TEXT,
10
+ extra_data TEXT, active INTEGER);
11
+
12
+ INSERT INTO auth_new (method, display_name, extra_data, active)
13
+ SELECT 'password', 'Password', password, 1 FROM auth;
14
+ DROP TABLE auth;
15
+ ALTER TABLE auth_new RENAME TO auth;
16
+
17
+ CREATE TABLE pending_openid_requests
18
+ (state TEXT PRIMARY KEY,
19
+ code_verifier TEXT,
20
+ return_url TEXT,
21
+ expiry_time INTEGER);
22
+ COMMIT;`,
23
+ );
24
+ };
25
+
26
+ export const down = async function () {
27
+ await getAccountDb().exec(
28
+ `
29
+ BEGIN TRANSACTION;
30
+ ALTER TABLE auth RENAME TO auth_temp;
31
+ CREATE TABLE auth
32
+ (password TEXT);
33
+ INSERT INTO auth (password)
34
+ SELECT extra_data FROM auth_temp WHERE method = 'password';
35
+ DROP TABLE auth_temp;
36
+
37
+ DROP TABLE pending_openid_requests;
38
+ COMMIT;
39
+ `,
40
+ );
41
+ };
@@ -0,0 +1,116 @@
1
+ import { v4 as uuidv4 } from 'uuid';
2
+
3
+ import { getAccountDb } from '../src/account-db.js';
4
+
5
+ export const up = async function () {
6
+ const accountDb = getAccountDb();
7
+
8
+ accountDb.transaction(() => {
9
+ accountDb.exec(
10
+ `
11
+ CREATE TABLE users
12
+ (id TEXT PRIMARY KEY,
13
+ user_name TEXT,
14
+ display_name TEXT,
15
+ role TEXT,
16
+ enabled INTEGER NOT NULL DEFAULT 1,
17
+ owner INTEGER NOT NULL DEFAULT 0);
18
+
19
+ CREATE TABLE user_access
20
+ (user_id TEXT,
21
+ file_id TEXT,
22
+ PRIMARY KEY (user_id, file_id),
23
+ FOREIGN KEY (user_id) REFERENCES users(id),
24
+ FOREIGN KEY (file_id) REFERENCES files(id)
25
+ );
26
+
27
+ ALTER TABLE files
28
+ ADD COLUMN owner TEXT;
29
+
30
+ ALTER TABLE sessions
31
+ ADD COLUMN expires_at INTEGER;
32
+
33
+ ALTER TABLE sessions
34
+ ADD COLUMN user_id TEXT;
35
+
36
+ ALTER TABLE sessions
37
+ ADD COLUMN auth_method TEXT;
38
+ `,
39
+ );
40
+
41
+ const userId = uuidv4();
42
+ accountDb.mutate(
43
+ 'INSERT INTO users (id, user_name, display_name, enabled, owner, role) VALUES (?, ?, ?, 1, 1, ?)',
44
+ [userId, '', '', 'ADMIN'],
45
+ );
46
+
47
+ accountDb.mutate(
48
+ 'UPDATE sessions SET user_id = ?, expires_at = ?, auth_method = ? WHERE auth_method IS NULL',
49
+ [userId, -1, 'password'],
50
+ );
51
+ });
52
+ };
53
+
54
+ export const down = async function () {
55
+ await getAccountDb().exec(
56
+ `
57
+ BEGIN TRANSACTION;
58
+
59
+ DROP TABLE IF EXISTS user_access;
60
+
61
+ CREATE TABLE sessions_backup (
62
+ token TEXT PRIMARY KEY
63
+ );
64
+
65
+ INSERT INTO sessions_backup (token)
66
+ SELECT token FROM sessions;
67
+
68
+ DROP TABLE sessions;
69
+
70
+ ALTER TABLE sessions_backup RENAME TO sessions;
71
+
72
+ CREATE TABLE files_backup (
73
+ id TEXT PRIMARY KEY,
74
+ group_id TEXT,
75
+ sync_version SMALLINT,
76
+ encrypt_meta TEXT,
77
+ encrypt_keyid TEXT,
78
+ encrypt_salt TEXT,
79
+ encrypt_test TEXT,
80
+ deleted BOOLEAN DEFAULT FALSE,
81
+ name TEXT
82
+ );
83
+
84
+ INSERT INTO files_backup (
85
+ id,
86
+ group_id,
87
+ sync_version,
88
+ encrypt_meta,
89
+ encrypt_keyid,
90
+ encrypt_salt,
91
+ encrypt_test,
92
+ deleted,
93
+ name
94
+ )
95
+ SELECT
96
+ id,
97
+ group_id,
98
+ sync_version,
99
+ encrypt_meta,
100
+ encrypt_keyid,
101
+ encrypt_salt,
102
+ encrypt_test,
103
+ deleted,
104
+ name
105
+ FROM files;
106
+
107
+ DROP TABLE files;
108
+
109
+ ALTER TABLE files_backup RENAME TO files;
110
+
111
+ DROP TABLE IF EXISTS users;
112
+
113
+ COMMIT;
114
+ `,
115
+ );
116
+ };