11import { NextRequest , NextResponse } from 'next/server' ;
22import { getDb } from '@/lib/db' ;
33import { getSessionDid } from '@/lib/auth' ;
4+ import type { DiscountType } from '@/lib/types' ;
45
56export async function GET ( req : NextRequest ) {
67 const { searchParams } = new URL ( req . url ) ;
@@ -23,22 +24,109 @@ export async function GET(req: NextRequest) {
2324 }
2425}
2526
27+ function slugify ( value : string ) : string {
28+ return value
29+ . toLowerCase ( )
30+ . replace ( / [ ^ a - z 0 - 9 ] + / g, '-' )
31+ . replace ( / ( ^ - | - $ ) / g, '' ) ;
32+ }
33+
34+ /** Human-readable badge text from structured discount fields. */
35+ function formatDiscount ( type : DiscountType | null , value : number | null ) : string | null {
36+ if ( value == null || Number . isNaN ( value ) ) return null ;
37+ if ( type === 'percent' ) return `${ value } %` ;
38+ if ( type === 'fixed' ) return `$${ value } off` ;
39+ return null ;
40+ }
41+
42+ /**
43+ * Resolve a store by website domain (preferred) or name, creating it if needed.
44+ * Returns the store id.
45+ */
46+ async function resolveStoreId (
47+ db : ReturnType < typeof getDb > ,
48+ opts : { name : string | null ; website : string | null ; logoCandidate : string | null }
49+ ) : Promise < number > {
50+ const name = opts . name ?. trim ( ) || null ;
51+
52+ // Derive a stable slug: prefer the website host, fall back to the name.
53+ let slugBase = name ;
54+ let website = opts . website ?. trim ( ) || null ;
55+ if ( website ) {
56+ try {
57+ const host = new URL ( website ) . hostname . replace ( / ^ w w w \. / , '' ) ;
58+ slugBase = slugBase ?? host . split ( '.' ) [ 0 ] ;
59+ website = `${ new URL ( website ) . protocol } //${ new URL ( website ) . hostname } ` ;
60+ } catch {
61+ /* keep website as-is */
62+ }
63+ }
64+ if ( ! slugBase ) slugBase = 'unknown-store' ;
65+ const slug = slugify ( slugBase ) || 'unknown-store' ;
66+
67+ const existing = await db . sql `SELECT id FROM stores WHERE slug = ${ slug } LIMIT 1` ;
68+ if ( existing . length > 0 ) return existing [ 0 ] . id ;
69+
70+ await db . sql `
71+ INSERT INTO stores (name, slug, website)
72+ VALUES (${ name ?? slugBase } , ${ slug } , ${ website } )
73+ ` ;
74+ const created = await db . sql `SELECT id FROM stores WHERE slug = ${ slug } LIMIT 1` ;
75+ return created [ 0 ] . id ;
76+ }
77+
2678export async function POST ( req : NextRequest ) {
2779 const did = await getSessionDid ( ) ;
2880 if ( ! did ) return NextResponse . json ( { error : 'Authentication required' } , { status : 401 } ) ;
2981
3082 try {
3183 const body = await req . json ( ) ;
32- const { store_id, code, title, description, discount, expiry_date, url } = body ;
84+ const {
85+ url,
86+ code,
87+ discount_type,
88+ discount_value,
89+ expiry_date,
90+ // AI-scraped listing metadata
91+ title,
92+ description,
93+ store_name,
94+ store_website,
95+ image_url,
96+ } = body ;
3397
34- if ( ! store_id || ! title ) {
35- return NextResponse . json ( { error : 'store_id and title are required' } , { status : 400 } ) ;
98+ if ( ! url ) {
99+ return NextResponse . json ( { error : 'url is required' } , { status : 400 } ) ;
36100 }
101+ if ( ! title ) {
102+ return NextResponse . json (
103+ { error : 'title is required (scrape the URL first)' } ,
104+ { status : 400 }
105+ ) ;
106+ }
107+
108+ const type : DiscountType | null =
109+ discount_type === 'percent' || discount_type === 'fixed' ? discount_type : null ;
110+ const value =
111+ discount_value === '' || discount_value == null ? null : Number ( discount_value ) ;
112+ const discount = formatDiscount ( type , value ) ;
37113
38114 const db = getDb ( ) ;
115+ const storeId = await resolveStoreId ( db , {
116+ name : store_name ?? null ,
117+ website : store_website ?? null ,
118+ logoCandidate : image_url ?? null ,
119+ } ) ;
120+
39121 await db . sql `
40- INSERT INTO coupons (store_id, code, title, description, discount, expiry_date, url)
41- VALUES (${ store_id } , ${ code } , ${ title } , ${ description } , ${ discount } , ${ expiry_date } , ${ url } )
122+ INSERT INTO coupons (
123+ store_id, code, title, description, discount, discount_type, discount_value,
124+ expiry_date, url, image_url
125+ )
126+ VALUES (
127+ ${ storeId } , ${ code ?. trim ( ) || null } , ${ title } , ${ description ?? null } , ${ discount } ,
128+ ${ type } , ${ value } , ${ expiry_date || null } , ${ url } , ${ image_url ?? null }
129+ )
42130 ` ;
43131 return NextResponse . json ( { success : true } , { status : 201 } ) ;
44132 } catch ( err ) {
0 commit comments