@actual-app/sync-server 25.4.0-alpha.0 → 25.4.0-alpha.1

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/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ Copyright James Long
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -8,6 +8,47 @@ If you are interested in contributing, or want to know how development works, se
8
8
 
9
9
  Want to say thanks? Click the ⭐ at the top of the page.
10
10
 
11
+ ### Using the CLI tool
12
+
13
+ Node.js v18 or higher is required for the @actual-app/sync-server npm package
14
+
15
+ **Install globally with npm:**
16
+
17
+ ```bash
18
+ npm install --location=global @actual-app/sync-server
19
+ ```
20
+
21
+ After installing, you can execute actual-server commands directly in your terminal.
22
+
23
+ **Usage**
24
+
25
+ ```bash
26
+ actual-server [options]
27
+ ```
28
+
29
+ **Available options**
30
+
31
+ | Command | Description |
32
+ | ------------- |-------------|
33
+ |`-h` or `--help` |Print this list and exit. |
34
+ |`-v` or `--version` |Print this version and exit. |
35
+ |`--config` |Path to the config file. |
36
+
37
+
38
+ **Examples**
39
+
40
+ Run with default configuration
41
+
42
+ ```bash
43
+ actual-server
44
+ ```
45
+
46
+ Runs with custom configuration
47
+
48
+ ```bash
49
+ actual-server --config ./config.json
50
+ ```
51
+
11
52
  ### Documentation
12
53
 
13
54
  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.
@@ -0,0 +1,85 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync } from 'node:fs';
3
+ import { parseArgs } from 'node:util';
4
+
5
+ import packageJson from '../package.json' with { type: 'json' };
6
+
7
+ const args = process.argv;
8
+
9
+ const options = {
10
+ help: {
11
+ type: 'boolean',
12
+ short: 'h',
13
+ },
14
+ version: {
15
+ type: 'boolean',
16
+ short: 'v',
17
+ },
18
+ config: {
19
+ type: 'string',
20
+ },
21
+ };
22
+
23
+ const { values } = parseArgs({
24
+ args,
25
+ options,
26
+ allowPositionals: true,
27
+ });
28
+
29
+ if (values.help) {
30
+ console.log(
31
+ [
32
+ 'usage: actual-server [options]',
33
+ '',
34
+ 'options:',
35
+ ' --config Path to config file',
36
+ '',
37
+ ' -h --help Print this list and exit.',
38
+ ' -v --version Print the version and exit.',
39
+ '',
40
+ 'Examples:',
41
+ '',
42
+ 'Runs actual-server with default configuration',
43
+ ' actual-server',
44
+ '',
45
+ 'Runs actual-server with custom configuration',
46
+ ' actual-server --config ./config.json',
47
+ ].join('\n'),
48
+ );
49
+
50
+ process.exit();
51
+ }
52
+
53
+ if (values.version) {
54
+ console.log('v' + packageJson.version);
55
+
56
+ process.exit();
57
+ }
58
+
59
+ // Read the config argument if specified
60
+ if (values.config) {
61
+ const configExists = existsSync(values.config);
62
+
63
+ if (!configExists) {
64
+ console.log(
65
+ `Please specify a valid config path. The path ${values.config} does not exist.`,
66
+ );
67
+
68
+ process.exit();
69
+ } else if (values.config) {
70
+ console.log(`Loading config from ${values.config}`);
71
+ process.env.ACTUAL_CONFIG_PATH = values.config;
72
+ }
73
+ } else {
74
+ // No config specified, use reasonable defaults
75
+ console.info(
76
+ 'Using default config. You can specify a custom config with --config',
77
+ );
78
+ process.env.ACTUAL_DATA_DIR = './';
79
+ console.info(
80
+ 'user-files and server-files will be created in the current directory',
81
+ );
82
+ }
83
+
84
+ // start the sync server
85
+ import('../app.js');
package/package.json CHANGED
@@ -1,10 +1,21 @@
1
1
  {
2
2
  "name": "@actual-app/sync-server",
3
- "version": "25.4.0-alpha.0",
3
+ "version": "25.4.0-alpha.1",
4
4
  "license": "MIT",
5
5
  "description": "actual syncing server",
6
- "bin": "bin/@actual-app/sync-server",
6
+ "bin": {
7
+ "actual-server": "./bin/actual-server.js"
8
+ },
7
9
  "type": "module",
10
+ "files": [
11
+ "bin",
12
+ "src",
13
+ "app.js",
14
+ "migrations",
15
+ "default-db.sqlite",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
8
19
  "scripts": {
9
20
  "start": "node app",
10
21
  "start-monitor": "nodemon app",
@@ -20,7 +31,7 @@
20
31
  },
21
32
  "dependencies": {
22
33
  "@actual-app/crdt": "2.1.0",
23
- "@actual-app/web": "25.4.0-alpha.0",
34
+ "@actual-app/web": "25.4.0",
24
35
  "bcrypt": "^5.1.1",
25
36
  "better-sqlite3": "^11.9.1",
26
37
  "body-parser": "^1.20.3",
@@ -1,198 +1,198 @@
1
- # Integration new bank
2
-
3
- If the default bank integration does not work for you, you can integrate a new bank by following these steps.
4
-
5
- 1. Find in [this google doc](https://docs.google.com/spreadsheets/d/1ogpzydzotOltbssrc3IQ8rhBLlIZbQgm5QCiiNJrkyA/edit#gid=489769432) what is the identifier of the bank which you want to integrate.
6
-
7
- 2. Launch frontend and backend server.
8
-
9
- 3. In the frontend, create a new linked account selecting the institution which you are interested in.
10
-
11
- This will trigger the process of fetching the data from the bank and will log the data in the backend. Use this data to fill the logic of the bank class.
12
-
13
- 4. Create new a bank class based on an existing example in `app-gocardless/banks`.
14
-
15
- Name of the file and class should follow the existing patterns and be created based on the ID of the integrated institution, found in step 1.
16
-
17
- 5. Fill the logic of `normalizeAccount`, `normalizeTransaction`, `sortTransactions`, and `calculateStartingBalance` functions.
18
- You do not need to fill every function, only those which are necessary for the integration to work.
19
-
20
- You should do it based on the data which you found in the logs.
21
-
22
- Example logs which help you to fill:
23
-
24
- - `normalizeAccount` function:
25
-
26
- ```log
27
- Available account properties for new institution integration {
28
- account: '{
29
- "iban": "PL00000000000000000987654321",
30
- "currency": "PLN",
31
- "ownerName": "John Example",
32
- "displayName": "Product name",
33
- "product": "Daily account",
34
- "usage": "PRIV",
35
- "ownerAddressUnstructured": [
36
- "POL",
37
- "UL. Example 1",
38
- "00-000 Warsaw"
39
- ],
40
- "id": "XXXXXXXX-XXXX-XXXXX-XXXXXX-XXXXXXXXX",
41
- "created": "2023-01-18T12:15:16.502446Z",
42
- "last_accessed": null,
43
- "institution_id": "MBANK_RETAIL_BREXPLPW",
44
- "status": "READY",
45
- "owner_name": "",
46
- "institution": {
47
- "id": "MBANK_RETAIL_BREXPLPW",
48
- "name": "mBank Retail",
49
- "bic": "BREXPLPW",
50
- "transaction_total_days": "90",
51
- "countries": [
52
- "PL"
53
- ],
54
- "logo": "https://cdn.nordigen.com/ais/MBANK_RETAIL_BREXCZPP.png",
55
- "supported_payments": {},
56
- "supported_features": [
57
- "access_scopes",
58
- "business_accounts",
59
- "card_accounts",
60
- "corporate_accounts",
61
- "pending_transactions",
62
- "private_accounts"
63
- ]
64
- }
65
- }'
66
- }
67
- ```
68
-
69
- - `sortTransactions` function:
70
-
71
- ```log
72
- Available (first 10) transactions properties for new integration of institution in sortTransactions function
73
- {
74
- top10SortedTransactions: '[
75
- {
76
- "transactionId": "20220101001",
77
- "bookingDate": "2022-01-01",
78
- "valueDate": "2022-01-01",
79
- "transactionAmount": {
80
- "amount": "5.01",
81
- "currency": "EUR"
82
- },
83
- "creditorName": "JOHN EXAMPLE",
84
- "creditorAccount": {
85
- "iban": "PL00000000000000000987654321"
86
- },
87
- "debtorName": "CHRIS EXAMPLE",
88
- "debtorAccount": {
89
- "iban": "PL12345000000000000987654321"
90
- },
91
- "remittanceInformationUnstructured": "TEST BANK TRANSFER",
92
- "remittanceInformationUnstructuredArray": [
93
- "TEST BANK TRANSFER"
94
- ],
95
- "balanceAfterTransaction": {
96
- "balanceAmount": {
97
- "amount": "448.52",
98
- "currency": "EUR"
99
- },
100
- "balanceType": "interimBooked"
101
- },
102
- "internalTransactionId": "casfib7720c2a02c0331cw2"
103
- }
104
- ]'
105
- }
106
- ```
107
-
108
- - `calculateStartingBalance` function:
109
-
110
- ```log
111
- Available (first 10) transactions properties for new integration of institution in calculateStartingBalance function {
112
- balances: '[
113
- {
114
- "balanceAmount": {
115
- "amount": "448.52",
116
- "currency": "EUR"
117
- },
118
- "balanceType": "forwardAvailable"
119
- },
120
- {
121
- "balanceAmount": {
122
- "amount": "448.52",
123
- "currency": "EUR"
124
- },
125
- "balanceType": "interimBooked"
126
- }
127
- ]',
128
- top10SortedTransactions: '[
129
- {
130
- "transactionId": "20220101001",
131
- "bookingDate": "2022-01-01",
132
- "valueDate": "2022-01-01",
133
- "transactionAmount": {
134
- "amount": "5.01",
135
- "currency": "EUR"
136
- },
137
- "creditorName": "JOHN EXAMPLE",
138
- "creditorAccount": {
139
- "iban": "PL00000000000000000987654321"
140
- },
141
- "debtorName": "CHRIS EXAMPLE",
142
- "debtorAccount": {
143
- "iban": "PL12345000000000000987654321"
144
- },
145
- "remittanceInformationUnstructured": "TEST BANK TRANSFER",
146
- "remittanceInformationUnstructuredArray": [
147
- "TEST BANK TRANSFER"
148
- ],
149
- "balanceAfterTransaction": {
150
- "balanceAmount": {
151
- "amount": "448.52",
152
- "currency": "EUR"
153
- },
154
- "balanceType": "interimBooked"
155
- },
156
- "internalTransactionId": "casfib7720c2a02c0331cw2"
157
- }
158
- ]'
159
- }
160
- ```
161
-
162
- 6. Add new bank integration to `BankFactory` class in file `actual-server/app-gocardless/bank-factory.js`
163
-
164
- 7. Remember to add tests for new bank integration in
165
-
166
- ## normalizeTransaction
167
-
168
- This is the most commonly used override as it allows you to change the data that is returned to the client.
169
-
170
- Please follow the following patterns when implementing a custom normalizeTransaction method:
171
-
172
- 1. If you need to edit the values of transaction fields (excluding the transaction amount) do not mutate the original transaction object. Instead, create a shallow copy and make your changes there.
173
- 2. End the function by returning the result of calling the fallback normalizeTransaction method from integration-bank.js
174
-
175
- E.g.
176
-
177
- ```js
178
- import Fallback from './integration-bank.js';
179
-
180
- export default {
181
- ...
182
-
183
- normalizeTransaction(transaction, booked) {
184
- // create a shallow copy of the transaction object
185
- const editedTrans = { ...transaction };
186
-
187
- // make any changes required to the copy
188
- editedTrans.remittanceInformationUnstructured = transaction.remittanceInformationStructured;
189
-
190
- // call the fallback method, passing in your edited transaction as the 3rd parameter
191
- // this will calculate the date, payee name and notes fields based on your changes
192
- // but leave the original fields available for mapping in the UI
193
- return Fallback.normalizeTransaction(transaction, booked, editedTrans);
194
- }
195
-
196
- ...
197
- }
198
- ```
1
+ # Integration new bank
2
+
3
+ If the default bank integration does not work for you, you can integrate a new bank by following these steps.
4
+
5
+ 1. Find in [this google doc](https://docs.google.com/spreadsheets/d/1ogpzydzotOltbssrc3IQ8rhBLlIZbQgm5QCiiNJrkyA/edit#gid=489769432) what is the identifier of the bank which you want to integrate.
6
+
7
+ 2. Launch frontend and backend server.
8
+
9
+ 3. In the frontend, create a new linked account selecting the institution which you are interested in.
10
+
11
+ This will trigger the process of fetching the data from the bank and will log the data in the backend. Use this data to fill the logic of the bank class.
12
+
13
+ 4. Create new a bank class based on an existing example in `app-gocardless/banks`.
14
+
15
+ Name of the file and class should follow the existing patterns and be created based on the ID of the integrated institution, found in step 1.
16
+
17
+ 5. Fill the logic of `normalizeAccount`, `normalizeTransaction`, `sortTransactions`, and `calculateStartingBalance` functions.
18
+ You do not need to fill every function, only those which are necessary for the integration to work.
19
+
20
+ You should do it based on the data which you found in the logs.
21
+
22
+ Example logs which help you to fill:
23
+
24
+ - `normalizeAccount` function:
25
+
26
+ ```log
27
+ Available account properties for new institution integration {
28
+ account: '{
29
+ "iban": "PL00000000000000000987654321",
30
+ "currency": "PLN",
31
+ "ownerName": "John Example",
32
+ "displayName": "Product name",
33
+ "product": "Daily account",
34
+ "usage": "PRIV",
35
+ "ownerAddressUnstructured": [
36
+ "POL",
37
+ "UL. Example 1",
38
+ "00-000 Warsaw"
39
+ ],
40
+ "id": "XXXXXXXX-XXXX-XXXXX-XXXXXX-XXXXXXXXX",
41
+ "created": "2023-01-18T12:15:16.502446Z",
42
+ "last_accessed": null,
43
+ "institution_id": "MBANK_RETAIL_BREXPLPW",
44
+ "status": "READY",
45
+ "owner_name": "",
46
+ "institution": {
47
+ "id": "MBANK_RETAIL_BREXPLPW",
48
+ "name": "mBank Retail",
49
+ "bic": "BREXPLPW",
50
+ "transaction_total_days": "90",
51
+ "countries": [
52
+ "PL"
53
+ ],
54
+ "logo": "https://cdn.nordigen.com/ais/MBANK_RETAIL_BREXCZPP.png",
55
+ "supported_payments": {},
56
+ "supported_features": [
57
+ "access_scopes",
58
+ "business_accounts",
59
+ "card_accounts",
60
+ "corporate_accounts",
61
+ "pending_transactions",
62
+ "private_accounts"
63
+ ]
64
+ }
65
+ }'
66
+ }
67
+ ```
68
+
69
+ - `sortTransactions` function:
70
+
71
+ ```log
72
+ Available (first 10) transactions properties for new integration of institution in sortTransactions function
73
+ {
74
+ top10SortedTransactions: '[
75
+ {
76
+ "transactionId": "20220101001",
77
+ "bookingDate": "2022-01-01",
78
+ "valueDate": "2022-01-01",
79
+ "transactionAmount": {
80
+ "amount": "5.01",
81
+ "currency": "EUR"
82
+ },
83
+ "creditorName": "JOHN EXAMPLE",
84
+ "creditorAccount": {
85
+ "iban": "PL00000000000000000987654321"
86
+ },
87
+ "debtorName": "CHRIS EXAMPLE",
88
+ "debtorAccount": {
89
+ "iban": "PL12345000000000000987654321"
90
+ },
91
+ "remittanceInformationUnstructured": "TEST BANK TRANSFER",
92
+ "remittanceInformationUnstructuredArray": [
93
+ "TEST BANK TRANSFER"
94
+ ],
95
+ "balanceAfterTransaction": {
96
+ "balanceAmount": {
97
+ "amount": "448.52",
98
+ "currency": "EUR"
99
+ },
100
+ "balanceType": "interimBooked"
101
+ },
102
+ "internalTransactionId": "casfib7720c2a02c0331cw2"
103
+ }
104
+ ]'
105
+ }
106
+ ```
107
+
108
+ - `calculateStartingBalance` function:
109
+
110
+ ```log
111
+ Available (first 10) transactions properties for new integration of institution in calculateStartingBalance function {
112
+ balances: '[
113
+ {
114
+ "balanceAmount": {
115
+ "amount": "448.52",
116
+ "currency": "EUR"
117
+ },
118
+ "balanceType": "forwardAvailable"
119
+ },
120
+ {
121
+ "balanceAmount": {
122
+ "amount": "448.52",
123
+ "currency": "EUR"
124
+ },
125
+ "balanceType": "interimBooked"
126
+ }
127
+ ]',
128
+ top10SortedTransactions: '[
129
+ {
130
+ "transactionId": "20220101001",
131
+ "bookingDate": "2022-01-01",
132
+ "valueDate": "2022-01-01",
133
+ "transactionAmount": {
134
+ "amount": "5.01",
135
+ "currency": "EUR"
136
+ },
137
+ "creditorName": "JOHN EXAMPLE",
138
+ "creditorAccount": {
139
+ "iban": "PL00000000000000000987654321"
140
+ },
141
+ "debtorName": "CHRIS EXAMPLE",
142
+ "debtorAccount": {
143
+ "iban": "PL12345000000000000987654321"
144
+ },
145
+ "remittanceInformationUnstructured": "TEST BANK TRANSFER",
146
+ "remittanceInformationUnstructuredArray": [
147
+ "TEST BANK TRANSFER"
148
+ ],
149
+ "balanceAfterTransaction": {
150
+ "balanceAmount": {
151
+ "amount": "448.52",
152
+ "currency": "EUR"
153
+ },
154
+ "balanceType": "interimBooked"
155
+ },
156
+ "internalTransactionId": "casfib7720c2a02c0331cw2"
157
+ }
158
+ ]'
159
+ }
160
+ ```
161
+
162
+ 6. Add new bank integration to `BankFactory` class in file `actual-server/app-gocardless/bank-factory.js`
163
+
164
+ 7. Remember to add tests for new bank integration in
165
+
166
+ ## normalizeTransaction
167
+
168
+ This is the most commonly used override as it allows you to change the data that is returned to the client.
169
+
170
+ Please follow the following patterns when implementing a custom normalizeTransaction method:
171
+
172
+ 1. If you need to edit the values of transaction fields (excluding the transaction amount) do not mutate the original transaction object. Instead, create a shallow copy and make your changes there.
173
+ 2. End the function by returning the result of calling the fallback normalizeTransaction method from integration-bank.js
174
+
175
+ E.g.
176
+
177
+ ```js
178
+ import Fallback from './integration-bank.js';
179
+
180
+ export default {
181
+ ...
182
+
183
+ normalizeTransaction(transaction, booked) {
184
+ // create a shallow copy of the transaction object
185
+ const editedTrans = { ...transaction };
186
+
187
+ // make any changes required to the copy
188
+ editedTrans.remittanceInformationUnstructured = transaction.remittanceInformationStructured;
189
+
190
+ // call the fallback method, passing in your edited transaction as the 3rd parameter
191
+ // this will calculate the date, payee name and notes fields based on your changes
192
+ // but leave the original fields available for mapping in the UI
193
+ return Fallback.normalizeTransaction(transaction, booked, editedTrans);
194
+ }
195
+
196
+ ...
197
+ }
198
+ ```
@@ -1,18 +1,18 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <title>Actual</title>
6
- </head>
7
- <body>
8
- <script>
9
- window.close();
10
- </script>
11
-
12
- <p>Please wait...</p>
13
- <p>
14
- The window should close automatically. If nothing happened you can close
15
- this window or tab.
16
- </p>
17
- </body>
18
- </html>
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>Actual</title>
6
+ </head>
7
+ <body>
8
+ <script>
9
+ window.close();
10
+ </script>
11
+
12
+ <p>Please wait...</p>
13
+ <p>
14
+ The window should close automatically. If nothing happened you can close
15
+ this window or tab.
16
+ </p>
17
+ </body>
18
+ </html>
@@ -1,9 +1,9 @@
1
-
2
- CREATE TABLE messages_binary
3
- (timestamp TEXT PRIMARY KEY,
4
- is_encrypted BOOLEAN,
5
- content bytea);
6
-
7
- CREATE TABLE messages_merkles
8
- (id INTEGER PRIMARY KEY,
9
- merkle TEXT);
1
+
2
+ CREATE TABLE messages_binary
3
+ (timestamp TEXT PRIMARY KEY,
4
+ is_encrypted BOOLEAN,
5
+ content bytea);
6
+
7
+ CREATE TABLE messages_merkles
8
+ (id INTEGER PRIMARY KEY,
9
+ merkle TEXT);
package/.dockerignore DELETED
@@ -1,12 +0,0 @@
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/babel.config.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "presets": ["@babel/preset-typescript"]
3
- }
@@ -1,55 +0,0 @@
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');
@@ -1,62 +0,0 @@
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"]
@@ -1,63 +0,0 @@
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"]
@@ -1,29 +0,0 @@
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
package/jest.config.json DELETED
@@ -1,19 +0,0 @@
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
- }
@@ -1,101 +0,0 @@
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
- }
@@ -1,6 +0,0 @@
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
- }
package/tsconfig.json DELETED
@@ -1,21 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2022",
4
- // DOM for URL global in Node 16+
5
- "lib": ["ES2021"],
6
- "allowSyntheticDefaultImports": true,
7
- "esModuleInterop": true,
8
- "experimentalDecorators": true,
9
- "resolveJsonModule": true,
10
- "downlevelIteration": true,
11
- "skipLibCheck": true,
12
- "jsx": "preserve",
13
- // Check JS files too
14
- "allowJs": true,
15
- "moduleResolution": "node16",
16
- "module": "node16",
17
- "outDir": "build"
18
- },
19
- "include": ["src/**/*.js", "types/global.d.ts"],
20
- "exclude": ["node_modules", "build", "./app-plaid.js", "coverage"]
21
- }