@@ -88,17 +88,17 @@ export class DB {
8888
8989 async createValidationToken (
9090 email : string ,
91- accessTokenId : number ,
91+ grantId : number ,
9292 validationToken : string ,
9393 ) {
9494 const insertResult = await this . db
9595 . prepare (
9696 sql `
97- INSERT INTO validation_tokens (email, access_token_id , token_value)
97+ INSERT INTO validation_tokens (email, grant_id , token_value)
9898 VALUES (?1, ?2, ?3)
9999 ` ,
100100 )
101- . bind ( email , accessTokenId , validationToken )
101+ . bind ( email , grantId , validationToken )
102102 . run ( )
103103
104104 if ( ! insertResult . success || ! insertResult . meta . last_row_id ) {
@@ -116,6 +116,7 @@ export class DB {
116116 sql `
117117 SELECT id, email, grant_id FROM validation_tokens
118118 WHERE grant_id = ?1 AND token_value = ?2
119+ LIMIT 1
119120 ` ,
120121 )
121122 . bind ( grantId , validationToken )
@@ -129,6 +130,7 @@ export class DB {
129130 sql `
130131 SELECT id FROM users
131132 WHERE email = ?1
133+ LIMIT 1
132134 ` ,
133135 )
134136 . bind ( validationResult . email )
@@ -142,12 +144,12 @@ export class DB {
142144 userId = createdUser . id
143145 }
144146
145- // set access token to user id
147+ // set grant to user id
146148 const claimGrantResult = await this . db
147149 . prepare (
148150 sql `
149151 UPDATE grants
150- SET user_id = ?1, updated_at = CURRENT_TIMESTAMP
152+ SET user_id = ?1, updated_at = CURRENT_TIMESTAMP
151153 WHERE id = ?2
152154 ` ,
153155 )
@@ -264,27 +266,85 @@ export class DB {
264266 }
265267
266268 async getEntry ( userId : number , id : number ) {
267- const result = await this . db
269+ const entryResult = await this . db
268270 . prepare ( sql `SELECT * FROM entries WHERE id = ?1 AND user_id = ?2` )
269271 . bind ( id , userId )
270272 . first ( )
271273
272- if ( ! result ) return null
274+ if ( ! entryResult ) return null
273275
274- return entrySchema . parse ( snakeToCamel ( result ) )
275- }
276+ const entry = entrySchema . parse ( snakeToCamel ( entryResult ) )
276277
277- async listEntries ( userId : number ) {
278- const results = await this . db
278+ // Get tags for this entry
279+ const tagsResult = await this . db
279280 . prepare (
280- sql `SELECT * FROM entries WHERE user_id = ?1 ORDER BY created_at DESC` ,
281+ sql `
282+ SELECT t.id, t.name
283+ FROM tags t
284+ JOIN entry_tags et ON et.tag_id = t.id
285+ WHERE et.entry_id = ?1
286+ ORDER BY t.name
287+ ` ,
281288 )
282- . bind ( userId )
289+ . bind ( id )
290+ . all ( )
291+
292+ const tags = z
293+ . array (
294+ z . object ( {
295+ id : z . number ( ) ,
296+ name : z . string ( ) ,
297+ } ) ,
298+ )
299+ . parse ( tagsResult . results . map ( ( result ) => snakeToCamel ( result ) ) )
300+
301+ return {
302+ ...entry ,
303+ tags,
304+ }
305+ }
306+
307+ async listEntries ( userId : number , tagIds ?: number [ ] ) {
308+ const queryParts = [
309+ sql `SELECT DISTINCT e.*, COUNT(et.id) as tag_count` ,
310+ sql `FROM entries e` ,
311+ sql `LEFT JOIN entry_tags et ON e.id = et.entry_id` ,
312+ sql `WHERE e.user_id = ?1` ,
313+ ]
314+ const params : number [ ] = [ userId ]
315+
316+ if ( tagIds && tagIds . length > 0 ) {
317+ queryParts . push ( sql `AND EXISTS (
318+ SELECT 1 FROM entry_tags et2
319+ WHERE et2.entry_id = e.id
320+ AND et2.tag_id IN (${ tagIds . map ( ( _ , i ) => `?${ i + 2 } ` ) . join ( ',' ) } )
321+ )` )
322+ params . push ( ...tagIds )
323+ }
324+
325+ queryParts . push ( sql `GROUP BY e.id` , sql `ORDER BY e.created_at DESC` )
326+
327+ const query = queryParts . join ( ' ' )
328+ const results = await this . db
329+ . prepare ( query )
330+ . bind ( ...params )
283331 . all ( )
284332
285333 return z
286- . array ( entrySchema )
287- . parse ( results . results . map ( ( result ) => snakeToCamel ( result ) ) )
334+ . array (
335+ z . object ( {
336+ id : z . number ( ) ,
337+ title : z . string ( ) ,
338+ tagCount : z . number ( ) ,
339+ } ) ,
340+ )
341+ . parse (
342+ results . results . map ( ( result ) => ( {
343+ id : result . id ,
344+ title : result . title ,
345+ tagCount : result . tag_count ,
346+ } ) ) ,
347+ )
288348 }
289349
290350 async updateEntry (
@@ -297,8 +357,9 @@ export class DB {
297357 throw new Error ( `Entry with ID ${ id } not found` )
298358 }
299359
360+ // Only include fields that are explicitly provided (even if null) but not undefined
300361 const updates = Object . entries ( entry )
301- . filter ( ( [ key , value ] ) => value !== undefined && key !== 'userId' )
362+ . filter ( ( [ key , value ] ) => key !== 'userId' && value !== undefined )
302363 . map (
303364 ( [ key ] , index ) =>
304365 `${ key === 'isPrivate' ? 'is_private' : key === 'isFavorite' ? 'is_favorite' : key } = ?${ index + 3 } ` ,
@@ -319,7 +380,7 @@ export class DB {
319380 id ,
320381 userId ,
321382 ...Object . entries ( entry )
322- . filter ( ( [ key , value ] ) => value !== undefined && key !== 'userId' )
383+ . filter ( ( [ key , value ] ) => key !== 'userId' && value !== undefined )
323384 . map ( ( [ , value ] ) => value ) ,
324385 ]
325386
@@ -343,13 +404,6 @@ export class DB {
343404 throw new Error ( `Entry with ID ${ id } not found` )
344405 }
345406
346- // First delete all entry tags
347- await this . db
348- . prepare ( sql `DELETE FROM entry_tags WHERE entry_id = ?1` )
349- . bind ( id )
350- . run ( )
351-
352- // Then delete the entry
353407 const deleteResult = await this . db
354408 . prepare ( sql `DELETE FROM entries WHERE id = ?1 AND user_id = ?2` )
355409 . bind ( id , userId )
@@ -400,12 +454,24 @@ export class DB {
400454
401455 async listTags ( userId : number ) {
402456 const results = await this . db
403- . prepare ( sql `SELECT * FROM tags WHERE user_id = ?1 ORDER BY name` )
457+ . prepare (
458+ sql `
459+ SELECT id, name
460+ FROM tags
461+ WHERE user_id = ?1
462+ ORDER BY name
463+ ` ,
464+ )
404465 . bind ( userId )
405466 . all ( )
406467
407468 return z
408- . array ( tagSchema )
469+ . array (
470+ z . object ( {
471+ id : z . number ( ) ,
472+ name : z . string ( ) ,
473+ } ) ,
474+ )
409475 . parse ( results . results . map ( ( result ) => snakeToCamel ( result ) ) )
410476 }
411477
@@ -429,13 +495,14 @@ export class DB {
429495 }
430496
431497 const ps = this . db . prepare ( sql `
432- UPDATE tags
433- SET ${ updates } , updated_at = CURRENT_TIMESTAMP
498+ UPDATE tags
499+ SET ${ updates } , updated_at = CURRENT_TIMESTAMP
434500 WHERE id = ?1 AND user_id = ?2
435501 ` )
436502
437503 const updateValues = [
438504 id ,
505+ userId ,
439506 ...Object . entries ( tag )
440507 . filter ( ( [ , value ] ) => value !== undefined )
441508 . map ( ( [ , value ] ) => value ) ,
@@ -461,13 +528,6 @@ export class DB {
461528 throw new Error ( `Tag with ID ${ id } not found` )
462529 }
463530
464- // First delete all entry tags
465- await this . db
466- . prepare ( sql `DELETE FROM entry_tags WHERE tag_id = ?1 AND user_id = ?2` )
467- . bind ( id , userId )
468- . run ( )
469-
470- // Then delete the tag
471531 const deleteResult = await this . db
472532 . prepare ( sql `DELETE FROM tags WHERE id = ?1 AND user_id = ?2` )
473533 . bind ( id , userId )
0 commit comments