1+ import { zValidator } from '@hono/zod-validator' ;
12import { subscriptions as subscriptionsTable } from '@sublistme/db/schema' ;
2- import type { SubscriptionInput } from '@sublistme/db/types' ;
33import { and , eq } from 'drizzle-orm' ;
4- import { drizzle } from 'drizzle-orm/d1' ;
54import { Hono } from 'hono' ;
65import type { Session , User } from 'lucia' ;
6+ import { z } from 'zod' ;
77import type { Env } from '../index' ;
88import { requireAuth } from '../middleware/auth' ;
9+ import type { DbVariables } from '../middleware/db' ;
910
1011type Variables = {
1112 user : User | null ;
1213 session : Session | null ;
13- } ;
14+ } & DbVariables ;
15+
16+ const subscriptionSchema = z . object ( {
17+ name : z . string ( ) . min ( 1 ) ,
18+ description : z . string ( ) . optional ( ) ,
19+ price : z . number ( ) . nonnegative ( ) ,
20+ originalPrice : z . number ( ) . nonnegative ( ) . optional ( ) ,
21+ currency : z . enum ( [ 'KRW' , 'USD' , 'JPY' , 'EUR' ] ) . default ( 'KRW' ) ,
22+ billingCycle : z . enum ( [ 'monthly' , 'yearly' , 'weekly' , 'quarterly' ] ) . default ( 'monthly' ) ,
23+ nextBillingDate : z . string ( ) . optional ( ) ,
24+ startDate : z . string ( ) . optional ( ) ,
25+ country : z . string ( ) . default ( 'KR' ) ,
26+ category : z . enum ( [ 'ott' , 'music' , 'gaming' , 'shopping' , 'productivity' , 'cloud' , 'news' , 'fitness' , 'education' , 'finance' , 'food' , 'security' , 'other' ] ) . optional ( ) ,
27+ url : z . string ( ) . optional ( ) ,
28+ logoUrl : z . string ( ) . optional ( ) ,
29+ memo : z . string ( ) . optional ( ) ,
30+ isActive : z . boolean ( ) . default ( true ) ,
31+ } ) ;
32+
33+ const updateSubscriptionSchema = subscriptionSchema . partial ( ) ;
34+
35+ const bulkSubscriptionSchema = z . object ( {
36+ subscriptions : z . array ( subscriptionSchema ) ,
37+ } ) ;
1438
1539export const subscriptions = new Hono < { Bindings : Env ; Variables : Variables } > ( )
1640 // 모든 구독 라우트에 인증 필수
@@ -19,7 +43,7 @@ export const subscriptions = new Hono<{ Bindings: Env; Variables: Variables }>()
1943 // 구독 목록 조회 (사용자별)
2044 . get ( '/' , async ( c ) => {
2145 const user = c . get ( 'user' ) ! ;
22- const db = drizzle ( c . env . DB ) ;
46+ const db = c . get ( 'db' ) ;
2347 const result = await db
2448 . select ( )
2549 . from ( subscriptionsTable )
@@ -30,7 +54,7 @@ export const subscriptions = new Hono<{ Bindings: Env; Variables: Variables }>()
3054 // 구독 상세 조회 (사용자별)
3155 . get ( '/:id' , async ( c ) => {
3256 const user = c . get ( 'user' ) ! ;
33- const db = drizzle ( c . env . DB ) ;
57+ const db = c . get ( 'db' ) ;
3458 const id = c . req . param ( 'id' ) ;
3559 const result = await db
3660 . select ( )
@@ -49,10 +73,10 @@ export const subscriptions = new Hono<{ Bindings: Env; Variables: Variables }>()
4973 } )
5074
5175 // 구독 생성 (사용자 ID 자동 추가)
52- . post ( '/' , async ( c ) => {
76+ . post ( '/' , zValidator ( 'json' , subscriptionSchema ) , async ( c ) => {
5377 const user = c . get ( 'user' ) ! ;
54- const db = drizzle ( c . env . DB ) ;
55- const body = await c . req . json ( ) ;
78+ const db = c . get ( 'db' ) ;
79+ const body = c . req . valid ( 'json' ) ;
5680 const result = await db
5781 . insert ( subscriptionsTable )
5882 . values ( { ...body , userId : user . id } )
@@ -61,20 +85,16 @@ export const subscriptions = new Hono<{ Bindings: Env; Variables: Variables }>()
6185 } )
6286
6387 // 구독 일괄 생성 (다건)
64- . post ( '/bulk' , async ( c ) => {
88+ . post ( '/bulk' , zValidator ( 'json' , bulkSubscriptionSchema ) , async ( c ) => {
6589 const user = c . get ( 'user' ) ! ;
66- const db = drizzle ( c . env . DB ) ;
67- const body = await c . req . json < { subscriptions : SubscriptionInput [ ] } > ( ) ;
90+ const db = c . get ( 'db' ) ;
91+ const { subscriptions } = c . req . valid ( 'json' ) ;
6892
69- if ( ! body . subscriptions || ! Array . isArray ( body . subscriptions ) ) {
70- return c . json ( { error : 'Invalid request body' } , 400 ) ;
71- }
72-
73- if ( body . subscriptions . length === 0 ) {
93+ if ( subscriptions . length === 0 ) {
7494 return c . json ( { error : 'No subscriptions provided' } , 400 ) ;
7595 }
7696
77- const subscriptionsToInsert = body . subscriptions . map ( ( s ) => ( {
97+ const subscriptionsToInsert = subscriptions . map ( ( s ) => ( {
7898 ...s ,
7999 userId : user . id ,
80100 } ) ) ;
@@ -88,11 +108,11 @@ export const subscriptions = new Hono<{ Bindings: Env; Variables: Variables }>()
88108 } )
89109
90110 // 구독 수정 (사용자별)
91- . put ( '/:id' , async ( c ) => {
111+ . put ( '/:id' , zValidator ( 'json' , updateSubscriptionSchema ) , async ( c ) => {
92112 const user = c . get ( 'user' ) ! ;
93- const db = drizzle ( c . env . DB ) ;
113+ const db = c . get ( 'db' ) ;
94114 const id = c . req . param ( 'id' ) ;
95- const body = await c . req . json ( ) ;
115+ const body = c . req . valid ( 'json' ) ;
96116 const result = await db
97117 . update ( subscriptionsTable )
98118 . set ( body )
@@ -113,7 +133,7 @@ export const subscriptions = new Hono<{ Bindings: Env; Variables: Variables }>()
113133 // 구독 삭제 (사용자별)
114134 . delete ( '/:id' , async ( c ) => {
115135 const user = c . get ( 'user' ) ! ;
116- const db = drizzle ( c . env . DB ) ;
136+ const db = c . get ( 'db' ) ;
117137 const id = c . req . param ( 'id' ) ;
118138 const result = await db
119139 . delete ( subscriptionsTable )
0 commit comments