@@ -2,6 +2,7 @@ import { base64url, queryString, sqlite } from './deps.ts';
2
2
import { clientConnectionListener } from './routers/api.ts' ;
3
3
import * as types from './types.ts' ;
4
4
import { randomStringWithEntropy } from './util.ts' ;
5
+ import env from './config.ts' ;
5
6
6
7
const { DB } = sqlite ;
7
8
@@ -37,20 +38,19 @@ async function updateAccessToken(endpoint: types.HealthLinkEndpoint) {
37
38
}
38
39
39
40
export const DbLinks = {
40
- handleUser ( userid : string ) {
41
- if ( this . userExists ( userid ) ) {
42
- return
43
- }
41
+ createUser ( userid : string ) {
42
+ return db . query ( `INSERT or ignore INTO user (id) values (?)` , [ userid ] ) ;
44
43
} ,
45
44
create ( config : types . HealthLinkConfig ) {
45
+ this . createUser ( config . userId ) ;
46
46
const link = {
47
47
config,
48
48
id : randomStringWithEntropy ( 32 ) ,
49
49
managementToken : randomStringWithEntropy ( 32 ) ,
50
50
active : true ,
51
51
} ;
52
52
db . query (
53
- `INSERT INTO shlink (id, management_token, active, config_exp, config_passcode)
53
+ `INSERT INTO shlink_access (id, management_token, active, config_exp, config_passcode)
54
54
values (:id, :managementToken, :active, :exp, :passcode)` ,
55
55
{
56
56
id : link . id ,
@@ -61,30 +61,65 @@ export const DbLinks = {
61
61
} ,
62
62
) ;
63
63
64
+ db . query (
65
+ `INSERT INTO user_shlink (user, shlink)
66
+ values (:user, :shlink)` ,
67
+ {
68
+ user : config . userId ,
69
+ shlink : link . id ,
70
+ } ,
71
+ ) ;
72
+
64
73
const link_public = {
65
74
id : link . id ,
66
- manifestUrl : `${ Deno . env . get ( 'PUBliC_URL' ) } /api/shl/${ link . id } ` ,
75
+ manifestUrl : `${ env . PUBLIC_URL } /api/shl/${ link . id } ` ,
67
76
key : randomStringWithEntropy ( 32 ) ,
68
77
flag : link . config . passcode ? 'P' : '' ,
69
- label : config . label
78
+ label : config . label ,
79
+ version : 1
70
80
}
71
81
72
82
db . query (
73
- `INSERT INTO shlink_public (shlink, manifest_url, encryption_key, flag, label)
74
- values (:shlink, :manifestUrl, :encryptionKey, :flag, :label)` ,
83
+ `INSERT INTO shlink_public (shlink, manifest_url, encryption_key, flag, label, version )
84
+ values (:shlink, :manifestUrl, :encryptionKey, :flag, :label, :version )` ,
75
85
{
76
86
shlink : link_public . id ,
77
87
manifestUrl : link_public . manifestUrl ,
78
88
encryptionKey : link_public . key ,
79
89
flag : link_public . flag ,
80
- label : link_public . label
90
+ label : link_public . label ,
91
+ version : link_public . version
81
92
} ,
82
93
) ;
83
94
84
- return link_public ;
95
+ return {
96
+ id : link_public . id as string ,
97
+ url : link_public . manifestUrl as string ,
98
+ key : link_public . key as string ,
99
+ flag : link_public . flag as string ,
100
+ label : link_public . label as string ,
101
+ v : link_public . version as number ,
102
+ files : [ ] ,
103
+ config : {
104
+ exp : link . config . exp as number ,
105
+ passcode : link . config . passcode as string
106
+ } ,
107
+ managementToken : link . managementToken as string
108
+ } ;
109
+ } ,
110
+ getConfig ( shlId : types . HealthLink ) {
111
+ let shl = db . prepareQuery ( `SELECT * from shlink_access where shlink=?` ) . oneEntry ( [ shlId ] ) ;
112
+ if ( ! shl ) {
113
+ return false ;
114
+ }
115
+ return {
116
+ exp : shl . config_exp ,
117
+ passcode : shl . config_passcode ,
118
+ label : shl . label
119
+ } ;
85
120
} ,
86
121
updateConfig ( shl : types . HealthLink ) {
87
- let pub = db . query ( `SELECT * from shlink_public where shlink=?` ) . oneEntry ( [ shl . id ] ) ;
122
+ let pub = db . prepareQuery ( `SELECT * from shlink_public where shlink=?` ) . oneEntry ( [ shl . id ] ) ;
88
123
if ( ! pub ) {
89
124
return false ;
90
125
}
@@ -104,7 +139,7 @@ export const DbLinks = {
104
139
}
105
140
) ;
106
141
db . query (
107
- `UPDATE shlink set config_passcode=:passcode, config_exp=:exp where id=:id` ,
142
+ `UPDATE shlink_access set config_passcode=:passcode, config_exp=:exp where id=:id` ,
108
143
{
109
144
id : shl . id ,
110
145
exp : shl . config . exp ,
@@ -115,50 +150,34 @@ export const DbLinks = {
115
150
return true ;
116
151
} ,
117
152
deactivate ( shl : types . HealthLink ) {
118
- db . query ( `UPDATE shlink set active=false where id=?` , [ shl . id ] ) ;
119
- return true ;
153
+ try {
154
+ db . query ( `UPDATE shlink_access set active=false where id=?` , [ shl . id ] ) ;
155
+ } catch ( e ) {
156
+ return false ;
157
+ }
158
+ return shl . id ;
120
159
} ,
121
- reactivate ( linkId : string , managementToken : string ) : boolean {
122
- db . query ( `UPDATE shlink set active=true, passcode_failures_remaining=5 where id=? and management_token=? ` , [ linkId , managementToken ] ) ;
160
+ reactivate ( shl : types . HealthLink ) : boolean {
161
+ db . query ( `UPDATE shlink_access set active=true, passcode_failures_remaining=5 where id=?` , [ shl . id ] ) ;
123
162
return true ;
124
163
} ,
125
164
linkExists ( linkId : string ) : boolean {
126
- return Boolean ( db . query ( `SELECT * from shlink where id=? and active=1` , [ linkId ] ) ) ;
165
+ return Boolean ( db . query ( `SELECT * from shlink_access where id=? and active=1` , [ linkId ] ) ) ;
127
166
} ,
128
167
userExists ( userId : string ) : boolean {
129
168
return Boolean ( db . query ( `SELECT * from user where id=?` , [ userId ] ) ) ;
130
169
} ,
131
- getUserShls ( userId : string ) : types . HealthLink | undefined {
132
- try {
133
- const linkRow = db
134
- . prepareQuery ( `SELECT * from shlink where user_id=? and active=1 order by created desc limit 1` )
135
- . allEntries ( [ userId ] ) ;
136
- // TODO: fix
137
- const linkPub = db
138
- . prepareQuery ( `SELECT * from shlink_public where shlink=?` )
139
- . allEntries ( [ linkRow . id ] ) ;
140
- return {
141
- id : linkRow . id as string ,
142
- passcodeFailuresRemaining : linkRow . passcode_failures_remaining as number ,
143
- active : Boolean ( linkRow . active ) as boolean ,
144
- sessionId : linkRow . session_id as string ,
145
- created : linkRow . created as string ,
146
- managementToken : linkRow . management_token as string ,
147
- config : {
148
- exp : linkRow . config_exp as number ,
149
- passcode : linkRow . config_passcode as string ,
150
- } ,
151
- } ;
152
- } catch ( e ) {
153
- console . warn ( e ) ;
154
- return undefined ;
155
- }
170
+ managementTokenExists ( managementToken : string ) : boolean {
171
+ return Boolean ( db . query ( `SELECT * from shlink_access where management_token=?` , [ managementToken ] ) ) ;
156
172
} ,
157
- getManagedShl ( linkId : string , managementToken : string ) : types . HealthLink {
158
- const linkRow = db
159
- . prepareQuery ( `SELECT * from shlink where id=? and management_token=?` )
160
- . oneEntry ( [ linkId , managementToken ] ) ;
161
-
173
+ getManagementTokenUserInternal ( managementToken : string ) : string {
174
+ const result = db . prepareQuery (
175
+ `SELECT * from shlink_access JOIN user_shlink on shlink_access.id=user_shlink.shlink where management_token=?`
176
+ ) . oneEntry ( [ managementToken ] ) ;
177
+ return result . user as string ;
178
+ } ,
179
+ getShlInternal ( linkId : string ) : types . HealthLink {
180
+ const linkRow = db . prepareQuery ( `SELECT * from shlink_access where id=?` ) . oneEntry ( [ linkId ] ) ;
162
181
return {
163
182
id : linkRow . id as string ,
164
183
passcodeFailuresRemaining : linkRow . passcode_failures_remaining as number ,
@@ -170,8 +189,11 @@ export const DbLinks = {
170
189
} ,
171
190
} ;
172
191
} ,
173
- getShlInternal ( linkId : string ) : types . HealthLink {
174
- const linkRow = db . prepareQuery ( `SELECT * from shlink where id=?` ) . oneEntry ( [ linkId ] ) ;
192
+ getManagedShl ( linkId : string , managementToken : string ) : types . HealthLink {
193
+ const linkRow = db
194
+ . prepareQuery ( `SELECT * from shlink_access where id=? and management_token=?` )
195
+ . oneEntry ( [ linkId , managementToken ] ) ;
196
+
175
197
return {
176
198
id : linkRow . id as string ,
177
199
passcodeFailuresRemaining : linkRow . passcode_failures_remaining as number ,
@@ -183,44 +205,123 @@ export const DbLinks = {
183
205
} ,
184
206
} ;
185
207
} ,
186
- async addFile ( linkId : string , file : types . HealthLinkFile ) : Promise < string > {
208
+ getUserShl ( linkId : string , userId : string ) : types . HealthLink {
209
+ try {
210
+ const userRow = db
211
+ . prepareQuery ( `SELECT * from user_shlink where shlink=? and user=?` )
212
+ . oneEntry ( [ linkId , userId ] ) ;
213
+
214
+ if ( ! userRow ) {
215
+ return ;
216
+ }
217
+
218
+ const linkRow = db
219
+ . prepareQuery ( `SELECT * from shlink_access where id=?` )
220
+ . oneEntry ( [ linkId ] ) ;
221
+
222
+ return {
223
+ id : linkRow . id as string ,
224
+ passcodeFailuresRemaining : linkRow . passcode_failures_remaining as number ,
225
+ active : Boolean ( linkRow . active ) as boolean ,
226
+ managementToken : linkRow . management_token as string ,
227
+ config : {
228
+ exp : linkRow . config_exp as number ,
229
+ passcode : linkRow . config_passcode as string ,
230
+ } ,
231
+ } ;
232
+ } catch ( e ) {
233
+ console . error ( e ) ;
234
+ return ;
235
+ }
236
+ } ,
237
+ getUserShls ( userId : string ) : Array < types . HealthLink > | undefined {
238
+ try {
239
+ const userPubShls = db
240
+ . prepareQuery ( `
241
+ SELECT
242
+ shlink_public.*,
243
+ shlink_access.config_passcode,
244
+ shlink_access.config_exp,
245
+ shlink_access.management_token
246
+ FROM user_shlink
247
+ JOIN shlink_public on shlink_public.shlink=user_shlink.shlink
248
+ JOIN shlink_access on shlink_access.id=user_shlink.shlink
249
+ WHERE
250
+ user_shlink.user=?
251
+ and shlink_access.active=1
252
+ ` )
253
+ . allEntries ( [ userId ] )
254
+ . map ( row => {
255
+ return {
256
+ id : row . shlink as string ,
257
+ url : row . manifest_url as string ,
258
+ exp : row . config_exp as number ,
259
+ key : row . encryption_key as string ,
260
+ flag : row . flag as string ,
261
+ label : row . label as string ,
262
+ v : row . version as number ,
263
+ files : this . getFiles ( row . shlink as string ) ,
264
+ config : {
265
+ exp : row . config_exp as number ,
266
+ passcode : row . config_passcode as string
267
+ } ,
268
+ managementToken : row . management_token as string
269
+ }
270
+ } ) ;
271
+ return userPubShls ;
272
+ } catch ( e ) {
273
+ console . error ( e ) ;
274
+ }
275
+ } ,
276
+ async addFile ( linkId : string , file : types . HealthLinkFile ) {
187
277
const hash = await crypto . subtle . digest ( 'SHA-256' , file . content ) ;
188
278
const hashEncoded = base64url . encode ( hash ) ;
189
- db . query ( `insert or ignore into cas_item(hash, content) values(:hashEncoded, :content)` , {
190
- hashEncoded,
191
- content : file . content ,
192
- } ) ;
193
-
194
- db . query (
195
- `insert into shlink_file(shlink, content_type, content_hash) values (:linkId, :contentType, :hashEncoded)` ,
196
- {
197
- linkId,
198
- contentType : file . contentType ,
199
- hashEncoded,
200
- } ,
201
- ) ;
279
+ try {
280
+ db . transaction ( ( ) => {
281
+ db . query ( `insert or ignore into cas_item(hash, content) values(:hashEncoded, :content)` , {
282
+ hashEncoded,
283
+ content : file . content ,
284
+ } ) ;
285
+
286
+ db . query (
287
+ `insert into shlink_file(shlink, content_type, content_hash) values (:linkId, :contentType, :hashEncoded)` ,
288
+ {
289
+ linkId,
290
+ contentType : file . contentType ,
291
+ hashEncoded,
292
+ } ,
293
+ ) ;
294
+ } ) ;
295
+ } catch ( e ) {
296
+ console . error ( e ) ;
297
+ return false ;
298
+ }
202
299
203
300
return hashEncoded ;
204
301
} ,
205
302
async deleteFile ( linkId : string , content : Uint8Array ) {
206
303
const hash = await crypto . subtle . digest ( 'SHA-256' , content ) ;
207
304
const hashEncoded = base64url . encode ( hash ) ;
208
-
209
- db . query (
210
- `delete from shlink_file where shlink = :linkId and content_hash = :hashEncoded` ,
211
- {
212
- linkId,
213
- hashEncoded,
214
- }
215
- ) ;
216
-
305
+ try {
306
+ db . query (
307
+ `delete from shlink_file where shlink = :linkId and content_hash = :hashEncoded` ,
308
+ {
309
+ linkId,
310
+ hashEncoded,
311
+ }
312
+ ) ;
313
+ } catch ( e ) {
314
+ console . error ( e ) ;
315
+ return false ;
316
+ }
317
+ // Hard delete
217
318
// db.query(`delete from cas_item where hash = :hashEncoded and content = :content`,
218
319
// {
219
320
// hashEncoded,
220
321
// content: file.content,
221
322
// });
222
323
223
- return true ;
324
+ return hashEncoded as string ;
224
325
} ,
225
326
async deleteAllFiles ( linkId : string ) {
226
327
@@ -352,8 +453,23 @@ export const DbLinks = {
352
453
contentType : fileRow [ 0 ] . content_type ,
353
454
} ;
354
455
} ,
456
+ getFiles ( shlId : string ) : types . HealthLinkFile [ ] {
457
+ const files = db . queryEntries < {
458
+ label : string ;
459
+ added_time : string ;
460
+ content_type : string ;
461
+ } > (
462
+ `select * from shlink_file where shlink=?` ,
463
+ [ shlId ] ,
464
+ ) ;
465
+ return files . map ( ( f ) => ( {
466
+ label : f . label ,
467
+ added : f . added_time ,
468
+ contentType : f . content_type ,
469
+ } ) ) ;
470
+ } ,
355
471
recordAccess ( shlId : string , recipient : string ) {
356
- const q = db . prepareQuery ( `insert into shlink_access (shlink, recipient) values (?, ?)` ) ;
472
+ const q = db . prepareQuery ( `insert into shlink_access_log (shlink, recipient) values (?, ?)` ) ;
357
473
q . execute ( [ shlId , recipient ] ) ;
358
474
359
475
clientConnectionListener ( {
@@ -363,7 +479,7 @@ export const DbLinks = {
363
479
} ,
364
480
recordPasscodeFailure ( shlId : string ) {
365
481
const q = db . prepareQuery (
366
- `update shlink set passcode_failures_remaining = passcode_failures_remaining - 1 where id=?`
482
+ `update shlink_access set passcode_failures_remaining = passcode_failures_remaining - 1 where id=?`
367
483
) ;
368
484
q . execute ( [ shlId ] ) ;
369
485
} ,
0 commit comments