@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.
- package/.dockerignore +12 -0
- package/README.md +19 -0
- package/app.js +11 -0
- package/babel.config.json +3 -0
- package/bin/@actual-app/sync-server +55 -0
- package/docker/alpine.Dockerfile +62 -0
- package/docker/ubuntu.Dockerfile +63 -0
- package/docker-compose.yml +29 -0
- package/jest.config.json +19 -0
- package/jest.global-setup.js +101 -0
- package/jest.global-teardown.js +6 -0
- package/migrations/1694360000000-create-folders.js +25 -0
- package/migrations/1694360479680-create-account-db.js +30 -0
- package/migrations/1694362247011-create-secret-table.js +16 -0
- package/migrations/1702667624000-rename-nordigen-secrets.js +19 -0
- package/migrations/1718889148000-openid.js +41 -0
- package/migrations/1719409568000-multiuser.js +116 -0
- package/package.json +64 -0
- package/src/account-db.js +239 -0
- package/src/accounts/openid.js +361 -0
- package/src/accounts/password.js +149 -0
- package/src/app-account.js +155 -0
- package/src/app-admin.js +410 -0
- package/src/app-admin.test.js +381 -0
- package/src/app-gocardless/README.md +198 -0
- package/src/app-gocardless/app-gocardless.js +274 -0
- package/src/app-gocardless/bank-factory.js +91 -0
- package/src/app-gocardless/banks/abanca_caglesmm.js +22 -0
- package/src/app-gocardless/banks/abnamro_abnanl2a.js +57 -0
- package/src/app-gocardless/banks/american_express_aesudef1.js +40 -0
- package/src/app-gocardless/banks/bancsabadell_bsabesbbb.js +31 -0
- package/src/app-gocardless/banks/bank.interface.ts +51 -0
- package/src/app-gocardless/banks/bank_of_ireland_b365_bofiie2d.js +39 -0
- package/src/app-gocardless/banks/bankinter_bkbkesmm.js +24 -0
- package/src/app-gocardless/banks/belfius_gkccbebb.js +17 -0
- package/src/app-gocardless/banks/berliner_sparkasse_beladebexxx.js +61 -0
- package/src/app-gocardless/banks/bnp_be_gebabebb.js +73 -0
- package/src/app-gocardless/banks/cbc_cregbebb.js +34 -0
- package/src/app-gocardless/banks/commerzbank_cobadeff.js +51 -0
- package/src/app-gocardless/banks/danskebank_dabno22.js +39 -0
- package/src/app-gocardless/banks/direkt_heladef1822.js +18 -0
- package/src/app-gocardless/banks/easybank_bawaatww.js +50 -0
- package/src/app-gocardless/banks/entercard_swednokk.js +40 -0
- package/src/app-gocardless/banks/fortuneo_ftnofrp1xxx.js +46 -0
- package/src/app-gocardless/banks/hype_hyeeit22.js +74 -0
- package/src/app-gocardless/banks/ing_ingbrobu.js +70 -0
- package/src/app-gocardless/banks/ing_ingddeff.js +47 -0
- package/src/app-gocardless/banks/ing_pl_ingbplpw.js +46 -0
- package/src/app-gocardless/banks/integration-bank.js +115 -0
- package/src/app-gocardless/banks/isybank_itbbitmm.js +18 -0
- package/src/app-gocardless/banks/kbc_kredbebb.js +33 -0
- package/src/app-gocardless/banks/lhv-lhvbee22.js +36 -0
- package/src/app-gocardless/banks/mbank_retail_brexplpw.js +56 -0
- package/src/app-gocardless/banks/nationwide_naiagb21.js +46 -0
- package/src/app-gocardless/banks/nbg_ethngraaxxx.js +51 -0
- package/src/app-gocardless/banks/norwegian_xx_norwnok1.js +74 -0
- package/src/app-gocardless/banks/revolut_revolt21.js +37 -0
- package/src/app-gocardless/banks/sandboxfinance_sfin0000.js +28 -0
- package/src/app-gocardless/banks/seb_kort_bank_ab.js +58 -0
- package/src/app-gocardless/banks/seb_privat.js +29 -0
- package/src/app-gocardless/banks/sparnord_spnodk22.js +24 -0
- package/src/app-gocardless/banks/spk_karlsruhe_karsde66.js +61 -0
- package/src/app-gocardless/banks/spk_marburg_biedenkopf_heladef1mar.js +30 -0
- package/src/app-gocardless/banks/spk_worms_alzey_ried_malade51wor.js +19 -0
- package/src/app-gocardless/banks/ssk_dusseldorf_dussdeddxxx.js +50 -0
- package/src/app-gocardless/banks/swedbank_habalv22.js +47 -0
- package/src/app-gocardless/banks/tests/abanca_caglesmm.spec.js +21 -0
- package/src/app-gocardless/banks/tests/abnamro_abnanl2a.spec.js +61 -0
- package/src/app-gocardless/banks/tests/bancsabadell_bsabesbbb.spec.js +53 -0
- package/src/app-gocardless/banks/tests/belfius_gkccbebb.spec.js +22 -0
- package/src/app-gocardless/banks/tests/cbc_cregbebb.spec.js +34 -0
- package/src/app-gocardless/banks/tests/commerzbank_cobadeff.spec.js +110 -0
- package/src/app-gocardless/banks/tests/easybank_bawaatww.spec.js +54 -0
- package/src/app-gocardless/banks/tests/fortuneo_ftnofrp1xxx.spec.js +206 -0
- package/src/app-gocardless/banks/tests/ing_ingddeff.spec.js +302 -0
- package/src/app-gocardless/banks/tests/ing_pl_ingbplpw.spec.js +202 -0
- package/src/app-gocardless/banks/tests/integration_bank.spec.js +158 -0
- package/src/app-gocardless/banks/tests/kbc_kredbebb.spec.js +38 -0
- package/src/app-gocardless/banks/tests/lhv-lhvbee22.spec.js +68 -0
- package/src/app-gocardless/banks/tests/mbank_retail_brexplpw.spec.js +171 -0
- package/src/app-gocardless/banks/tests/nationwide_naiagb21.spec.js +105 -0
- package/src/app-gocardless/banks/tests/nbg_ethngraaxxx.spec.js +48 -0
- package/src/app-gocardless/banks/tests/revolut_revolt21.spec.js +42 -0
- package/src/app-gocardless/banks/tests/sandboxfinance_sfin0000.spec.js +133 -0
- package/src/app-gocardless/banks/tests/spk_marburg_biedenkopf_heladef1mar.spec.js +256 -0
- package/src/app-gocardless/banks/tests/ssk_dusseldorf_dussdeddxxx.spec.js +102 -0
- package/src/app-gocardless/banks/tests/swedbank_habalv22.spec.js +57 -0
- package/src/app-gocardless/banks/tests/virgin_nrnbgb22.spec.js +54 -0
- package/src/app-gocardless/banks/util/extract-payeeName-from-remittanceInfo.js +36 -0
- package/src/app-gocardless/banks/virgin_nrnbgb22.js +39 -0
- package/src/app-gocardless/errors.js +84 -0
- package/src/app-gocardless/gocardless-node.types.ts +497 -0
- package/src/app-gocardless/gocardless.types.ts +93 -0
- package/src/app-gocardless/link.html +18 -0
- package/src/app-gocardless/services/gocardless-service.js +620 -0
- package/src/app-gocardless/services/tests/fixtures.js +181 -0
- package/src/app-gocardless/services/tests/gocardless-service.spec.js +537 -0
- package/src/app-gocardless/tests/bank-factory.spec.js +20 -0
- package/src/app-gocardless/tests/utils.spec.js +162 -0
- package/src/app-gocardless/util/handle-error.js +16 -0
- package/src/app-gocardless/utils.js +45 -0
- package/src/app-openid.js +108 -0
- package/src/app-pluggyai/app-pluggyai.js +215 -0
- package/src/app-pluggyai/pluggyai-service.js +120 -0
- package/src/app-secrets.js +61 -0
- package/src/app-simplefin/app-simplefin.js +418 -0
- package/src/app-sync/errors.js +13 -0
- package/src/app-sync/services/files-service.js +243 -0
- package/src/app-sync/tests/services/files-service.test.js +250 -0
- package/src/app-sync/validation.js +77 -0
- package/src/app-sync.js +391 -0
- package/src/app-sync.test.js +877 -0
- package/src/app.js +145 -0
- package/src/config-types.ts +44 -0
- package/src/db.js +58 -0
- package/src/load-config.js +307 -0
- package/src/migrations.js +36 -0
- package/src/run-migrations.js +8 -0
- package/src/scripts/disable-openid.js +44 -0
- package/src/scripts/enable-openid.js +53 -0
- package/src/scripts/health-check.js +23 -0
- package/src/scripts/reset-password.js +51 -0
- package/src/secrets.test.js +83 -0
- package/src/services/secrets-service.js +94 -0
- package/src/services/user-service.js +272 -0
- package/src/sql/messages.sql +9 -0
- package/src/sync-simple.js +95 -0
- package/src/util/hash.js +5 -0
- package/src/util/middlewares.js +62 -0
- package/src/util/paths.js +13 -0
- package/src/util/payee-name.js +45 -0
- package/src/util/prompt.js +88 -0
- package/src/util/title/index.js +59 -0
- package/src/util/title/lower-case.js +93 -0
- package/src/util/title/specials.js +21 -0
- package/src/util/validate-user.js +68 -0
- package/tsconfig.json +21 -0
package/src/app-sync.js
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
|
|
4
|
+
import { SyncProtoBuf } from '@actual-app/crdt';
|
|
5
|
+
import express from 'express';
|
|
6
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
7
|
+
|
|
8
|
+
import { getAccountDb } from './account-db.js';
|
|
9
|
+
import { FileNotFound } from './app-sync/errors.js';
|
|
10
|
+
import {
|
|
11
|
+
File,
|
|
12
|
+
FilesService,
|
|
13
|
+
FileUpdate,
|
|
14
|
+
} from './app-sync/services/files-service.js';
|
|
15
|
+
import {
|
|
16
|
+
validateSyncedFile,
|
|
17
|
+
validateUploadedFile,
|
|
18
|
+
} from './app-sync/validation.js';
|
|
19
|
+
import * as simpleSync from './sync-simple.js';
|
|
20
|
+
import {
|
|
21
|
+
errorMiddleware,
|
|
22
|
+
requestLoggerMiddleware,
|
|
23
|
+
validateSessionMiddleware,
|
|
24
|
+
} from './util/middlewares.js';
|
|
25
|
+
import { getPathForUserFile, getPathForGroupFile } from './util/paths.js';
|
|
26
|
+
|
|
27
|
+
const app = express();
|
|
28
|
+
app.use(validateSessionMiddleware);
|
|
29
|
+
app.use(errorMiddleware);
|
|
30
|
+
app.use(requestLoggerMiddleware);
|
|
31
|
+
app.use(express.raw({ type: 'application/actual-sync' }));
|
|
32
|
+
app.use(express.raw({ type: 'application/encrypted-file' }));
|
|
33
|
+
app.use(express.json());
|
|
34
|
+
|
|
35
|
+
export { app as handlers };
|
|
36
|
+
|
|
37
|
+
const OK_RESPONSE = { status: 'ok' };
|
|
38
|
+
|
|
39
|
+
function boolToInt(deleted) {
|
|
40
|
+
return deleted ? 1 : 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const verifyFileExists = (fileId, filesService, res, errorObject) => {
|
|
44
|
+
try {
|
|
45
|
+
return filesService.get(fileId);
|
|
46
|
+
} catch (e) {
|
|
47
|
+
if (e instanceof FileNotFound) {
|
|
48
|
+
//FIXME: error code should be 404. Need to make sure frontend is ok with it.
|
|
49
|
+
//TODO: put this into a middleware that checks if FileNotFound is thrown and returns 404 and same error message
|
|
50
|
+
// for every FileNotFound error
|
|
51
|
+
res.status(400).send(errorObject);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
throw e;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
app.post('/sync', async (req, res) => {
|
|
59
|
+
let requestPb;
|
|
60
|
+
try {
|
|
61
|
+
requestPb = SyncProtoBuf.SyncRequest.deserializeBinary(req.body);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
console.log('Error parsing sync request', e);
|
|
64
|
+
res.status(500);
|
|
65
|
+
res.send({ status: 'error', reason: 'internal-error' });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const fileId = requestPb.getFileid() || null;
|
|
70
|
+
const groupId = requestPb.getGroupid() || null;
|
|
71
|
+
const keyId = requestPb.getKeyid() || null;
|
|
72
|
+
const since = requestPb.getSince() || null;
|
|
73
|
+
const messages = requestPb.getMessagesList();
|
|
74
|
+
|
|
75
|
+
if (!since) {
|
|
76
|
+
return res.status(422).send({
|
|
77
|
+
details: 'since-required',
|
|
78
|
+
reason: 'unprocessable-entity',
|
|
79
|
+
status: 'error',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const filesService = new FilesService(getAccountDb());
|
|
84
|
+
|
|
85
|
+
const currentFile = verifyFileExists(
|
|
86
|
+
fileId,
|
|
87
|
+
filesService,
|
|
88
|
+
res,
|
|
89
|
+
'file-not-found',
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (!currentFile) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const errorMessage = validateSyncedFile(groupId, keyId, currentFile);
|
|
97
|
+
if (errorMessage) {
|
|
98
|
+
res.status(400);
|
|
99
|
+
res.send(errorMessage);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const { trie, newMessages } = simpleSync.sync(messages, since, groupId);
|
|
104
|
+
|
|
105
|
+
// encode it back...
|
|
106
|
+
const responsePb = new SyncProtoBuf.SyncResponse();
|
|
107
|
+
responsePb.setMerkle(JSON.stringify(trie));
|
|
108
|
+
newMessages.forEach(msg => responsePb.addMessages(msg));
|
|
109
|
+
|
|
110
|
+
res.set('Content-Type', 'application/actual-sync');
|
|
111
|
+
res.set('X-ACTUAL-SYNC-METHOD', 'simple');
|
|
112
|
+
res.send(Buffer.from(responsePb.serializeBinary()));
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
app.post('/user-get-key', (req, res) => {
|
|
116
|
+
if (!res.locals) return;
|
|
117
|
+
|
|
118
|
+
const { fileId } = req.body;
|
|
119
|
+
|
|
120
|
+
const filesService = new FilesService(getAccountDb());
|
|
121
|
+
const file = verifyFileExists(fileId, filesService, res, 'file-not-found');
|
|
122
|
+
|
|
123
|
+
if (!file) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
res.send({
|
|
128
|
+
status: 'ok',
|
|
129
|
+
data: {
|
|
130
|
+
id: file.encryptKeyId,
|
|
131
|
+
salt: file.encryptSalt,
|
|
132
|
+
test: file.encryptTest,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
app.post('/user-create-key', (req, res) => {
|
|
138
|
+
const { fileId, keyId, keySalt, testContent } = req.body;
|
|
139
|
+
|
|
140
|
+
const filesService = new FilesService(getAccountDb());
|
|
141
|
+
|
|
142
|
+
if (!verifyFileExists(fileId, filesService, res, 'file not found')) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
filesService.update(
|
|
147
|
+
fileId,
|
|
148
|
+
new FileUpdate({
|
|
149
|
+
encryptSalt: keySalt,
|
|
150
|
+
encryptKeyId: keyId,
|
|
151
|
+
encryptTest: testContent,
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
res.send(OK_RESPONSE);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
app.post('/reset-user-file', async (req, res) => {
|
|
159
|
+
const { fileId } = req.body;
|
|
160
|
+
|
|
161
|
+
const filesService = new FilesService(getAccountDb());
|
|
162
|
+
const file = verifyFileExists(
|
|
163
|
+
fileId,
|
|
164
|
+
filesService,
|
|
165
|
+
res,
|
|
166
|
+
'User or file not found',
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
if (!file) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const groupId = file.groupId;
|
|
174
|
+
|
|
175
|
+
filesService.update(fileId, new FileUpdate({ groupId: null }));
|
|
176
|
+
|
|
177
|
+
if (groupId) {
|
|
178
|
+
try {
|
|
179
|
+
await fs.unlink(getPathForGroupFile(groupId));
|
|
180
|
+
} catch {
|
|
181
|
+
console.log(`Unable to delete sync data for group "${groupId}"`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
res.send(OK_RESPONSE);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
app.post('/upload-user-file', async (req, res) => {
|
|
189
|
+
if (typeof req.headers['x-actual-name'] !== 'string') {
|
|
190
|
+
// FIXME: Not sure how this cannot be a string when the header is
|
|
191
|
+
// set.
|
|
192
|
+
res.status(400).send('single x-actual-name is required');
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const name = decodeURIComponent(req.headers['x-actual-name']);
|
|
197
|
+
const fileId = req.headers['x-actual-file-id'];
|
|
198
|
+
|
|
199
|
+
if (!fileId || typeof fileId !== 'string') {
|
|
200
|
+
res.status(400).send('fileId is required');
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
let groupId = req.headers['x-actual-group-id'] || null;
|
|
205
|
+
const encryptMeta = req.headers['x-actual-encrypt-meta'] || null;
|
|
206
|
+
const syncFormatVersion = req.headers['x-actual-format'] || null;
|
|
207
|
+
|
|
208
|
+
const keyId =
|
|
209
|
+
encryptMeta && typeof encryptMeta === 'string'
|
|
210
|
+
? JSON.parse(encryptMeta).keyId
|
|
211
|
+
: null;
|
|
212
|
+
|
|
213
|
+
const filesService = new FilesService(getAccountDb());
|
|
214
|
+
let currentFile;
|
|
215
|
+
|
|
216
|
+
try {
|
|
217
|
+
currentFile = filesService.get(fileId);
|
|
218
|
+
} catch (e) {
|
|
219
|
+
if (e instanceof FileNotFound) {
|
|
220
|
+
currentFile = null;
|
|
221
|
+
} else {
|
|
222
|
+
throw e;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const errorMessage = validateUploadedFile(groupId, keyId, currentFile);
|
|
227
|
+
if (errorMessage) {
|
|
228
|
+
res.status(400).send(errorMessage);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
await fs.writeFile(getPathForUserFile(fileId), req.body);
|
|
234
|
+
} catch (err) {
|
|
235
|
+
console.log('Error writing file', err);
|
|
236
|
+
res.status(500).send({ status: 'error' });
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!currentFile) {
|
|
241
|
+
// it's new
|
|
242
|
+
groupId = uuidv4();
|
|
243
|
+
|
|
244
|
+
filesService.set(
|
|
245
|
+
new File({
|
|
246
|
+
id: fileId,
|
|
247
|
+
groupId,
|
|
248
|
+
syncVersion: syncFormatVersion,
|
|
249
|
+
name,
|
|
250
|
+
encryptMeta,
|
|
251
|
+
owner:
|
|
252
|
+
res.locals.user_id ||
|
|
253
|
+
(() => {
|
|
254
|
+
throw new Error('User ID is required for file creation');
|
|
255
|
+
})(),
|
|
256
|
+
}),
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
res.send({ status: 'ok', groupId });
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (!groupId) {
|
|
264
|
+
// sync state was reset, create new group
|
|
265
|
+
groupId = uuidv4();
|
|
266
|
+
filesService.update(fileId, new FileUpdate({ groupId }));
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Regardless, update some properties
|
|
270
|
+
filesService.update(
|
|
271
|
+
fileId,
|
|
272
|
+
new FileUpdate({
|
|
273
|
+
syncVersion: syncFormatVersion,
|
|
274
|
+
encryptMeta,
|
|
275
|
+
name,
|
|
276
|
+
}),
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
res.send({ status: 'ok', groupId });
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
app.get('/download-user-file', async (req, res) => {
|
|
283
|
+
const fileId = req.headers['x-actual-file-id'];
|
|
284
|
+
if (typeof fileId !== 'string') {
|
|
285
|
+
// FIXME: Not sure how this cannot be a string when the header is
|
|
286
|
+
// set.
|
|
287
|
+
res.status(400).send('Single file ID is required');
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const filesService = new FilesService(getAccountDb());
|
|
292
|
+
if (!verifyFileExists(fileId, filesService, res, 'User or file not found')) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
res.setHeader('Content-Disposition', `attachment;filename=${fileId}`);
|
|
297
|
+
res.sendFile(getPathForUserFile(fileId));
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
app.post('/update-user-filename', (req, res) => {
|
|
301
|
+
const { fileId, name } = req.body;
|
|
302
|
+
|
|
303
|
+
const filesService = new FilesService(getAccountDb());
|
|
304
|
+
|
|
305
|
+
if (!verifyFileExists(fileId, filesService, res, 'file not found')) {
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
filesService.update(fileId, new FileUpdate({ name }));
|
|
310
|
+
res.send(OK_RESPONSE);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
app.get('/list-user-files', (req, res) => {
|
|
314
|
+
const fileService = new FilesService(getAccountDb());
|
|
315
|
+
const rows = fileService.find({ userId: res.locals.user_id });
|
|
316
|
+
res.send({
|
|
317
|
+
status: 'ok',
|
|
318
|
+
data: rows.map(row => ({
|
|
319
|
+
deleted: boolToInt(row.deleted),
|
|
320
|
+
fileId: row.id,
|
|
321
|
+
groupId: row.groupId,
|
|
322
|
+
name: row.name,
|
|
323
|
+
encryptKeyId: row.encryptKeyId,
|
|
324
|
+
owner: row.owner,
|
|
325
|
+
usersWithAccess: fileService.findUsersWithAccess(row.id).map(access => ({
|
|
326
|
+
...access,
|
|
327
|
+
owner: access.userId === row.owner,
|
|
328
|
+
})),
|
|
329
|
+
})),
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
app.get('/get-user-file-info', (req, res) => {
|
|
334
|
+
const fileId = req.headers['x-actual-file-id'];
|
|
335
|
+
|
|
336
|
+
// TODO: Return 422 if fileId is not provided. Need to make sure frontend can handle it
|
|
337
|
+
// if (!fileId) {
|
|
338
|
+
// return res.status(422).send({
|
|
339
|
+
// details: 'fileId-required',
|
|
340
|
+
// reason: 'unprocessable-entity',
|
|
341
|
+
// status: 'error',
|
|
342
|
+
// });
|
|
343
|
+
// }
|
|
344
|
+
|
|
345
|
+
const fileService = new FilesService(getAccountDb());
|
|
346
|
+
|
|
347
|
+
const file = verifyFileExists(fileId, fileService, res, {
|
|
348
|
+
status: 'error',
|
|
349
|
+
reason: 'file-not-found',
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
if (!file) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
res.send({
|
|
357
|
+
status: 'ok',
|
|
358
|
+
data: {
|
|
359
|
+
deleted: boolToInt(file.deleted), // FIXME: convert to boolean, make sure it works in the frontend
|
|
360
|
+
fileId: file.id,
|
|
361
|
+
groupId: file.groupId,
|
|
362
|
+
name: file.name,
|
|
363
|
+
encryptMeta: file.encryptMeta ? JSON.parse(file.encryptMeta) : null,
|
|
364
|
+
usersWithAccess: fileService.findUsersWithAccess(file.id).map(access => ({
|
|
365
|
+
...access,
|
|
366
|
+
owner: access.userId === file.owner,
|
|
367
|
+
})),
|
|
368
|
+
},
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
app.post('/delete-user-file', (req, res) => {
|
|
373
|
+
const { fileId } = req.body;
|
|
374
|
+
|
|
375
|
+
if (!fileId) {
|
|
376
|
+
return res.status(422).send({
|
|
377
|
+
details: 'fileId-required',
|
|
378
|
+
reason: 'unprocessable-entity',
|
|
379
|
+
status: 'error',
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const filesService = new FilesService(getAccountDb());
|
|
384
|
+
if (!verifyFileExists(fileId, filesService, res, 'file-not-found')) {
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
filesService.update(fileId, new FileUpdate({ deleted: true }));
|
|
389
|
+
|
|
390
|
+
res.send(OK_RESPONSE);
|
|
391
|
+
});
|