11export const dynamic = "force-dynamic" ;
22
33import { NextRequest , NextResponse } from "next/server" ;
4- import { z } from "zod" ;
5- import { withAuth } from "@/lib/auth/middleware" ;
6- import { enforceRateLimit , buildRateLimitKey } from "@/lib/security/rateLimit" ;
7- import { getUserIdByWallet } from "@/lib/reputation" ;
4+ import { withAuth , AuthContext , resolveUserIdByWallet } from "@/lib/auth/middleware" ;
85import {
9- listNotifications as listNotificationsDb ,
10- countNotifications as countNotificationsDb ,
11- markAllAsRead as markAllAsReadDb ,
6+ parseNotificationQuery ,
7+ listNotificationsForUser ,
8+ markAllNotificationsRead ,
9+ getUnreadCount ,
10+ NotificationError ,
1211} from "@/lib/notifications" ;
1312
14- // ─── Validation schemas ────────────────────────────────────────────────────
15-
16- const ListNotificationsSchema = z . object ( {
17- isRead : z . coerce . boolean ( ) . optional ( ) ,
18- limit : z . coerce . number ( ) . int ( ) . min ( 1 ) . max ( 100 ) . optional ( ) ,
19- offset : z . coerce . number ( ) . int ( ) . min ( 0 ) . optional ( ) ,
20- } ) ;
21-
2213// ─── GET /api/notifications ────────────────────────────────────────────────
2314
2415/**
25- * GET /api/notifications?limit=20&offset=0&isRead=false
16+ * GET /api/notifications?page=1& limit=20&type=milestone_approved&unreadOnly=true
2617 *
2718 * Returns a paginated list of notifications for the authenticated user.
2819 * Query parameters:
29- * - limit: max 100, default 20
30- * - offset: pagination offset, default 0
31- * - isRead: filter by read status (optional)
20+ * - page: ≥1, default 1
21+ * - limit: 1..100, default 20
22+ * - type: one of NOTIFICATION_EVENT_TYPES, optional
23+ * - unreadOnly: true/1, optional
24+ *
25+ * Response:
26+ * { data: Notification[], meta: { totalCount, page, limit, totalPages, unreadCount } }
3227 */
33- export const GET = withAuth ( async ( request : NextRequest , auth ) => {
34- const limited = await enforceRateLimit ( request , {
35- key : buildRateLimitKey ( request , "notifications:list" , auth . walletAddress ) ,
36- limit : 60 ,
37- windowMs : 60_000 ,
38- } ) ;
39- if ( limited ) return limited ;
40-
41- const userId = await getUserIdByWallet ( auth . walletAddress ) ;
42- if ( userId === null ) {
43- return NextResponse . json (
44- {
45- error : "Platform user not found for this wallet" ,
46- code : "USER_NOT_FOUND" ,
47- } ,
48- { status : 404 } ,
49- ) ;
50- }
51-
52- const { searchParams } = request . nextUrl ;
53- const parsed = ListNotificationsSchema . safeParse ( {
54- isRead : searchParams . get ( "isRead" ) ?? undefined ,
55- limit : searchParams . get ( "limit" ) ?? undefined ,
56- offset : searchParams . get ( "offset" ) ?? undefined ,
57- } ) ;
58-
59- if ( ! parsed . success ) {
60- return NextResponse . json (
61- {
62- error : "Validation failed" ,
63- details : parsed . error . flatten ( ) . fieldErrors ,
64- } ,
65- { status : 422 } ,
66- ) ;
67- }
68-
28+ export const GET = withAuth ( async ( request : NextRequest , auth : AuthContext ) => {
29+ void request ;
6930 try {
70- const notifications = await listNotificationsDb ( {
71- userId,
72- isRead : parsed . data . isRead ,
73- limit : parsed . data . limit ,
74- offset : parsed . data . offset ,
75- } ) ;
31+ const userId = await resolveUserIdByWallet ( auth . walletAddress ) ;
32+ if ( userId === null ) {
33+ return NextResponse . json (
34+ { error : "Platform user not found for this wallet" , code : "USER_NOT_FOUND" } ,
35+ { status : 404 } ,
36+ ) ;
37+ }
38+
39+ const query = parseNotificationQuery ( request . nextUrl . searchParams ) ;
7640
77- const total = await countNotificationsDb ( userId , parsed . data . isRead ) ;
41+ const [ result , unreadCount ] = await Promise . all ( [
42+ listNotificationsForUser ( userId , query ) ,
43+ getUnreadCount ( userId ) ,
44+ ] ) ;
7845
7946 return NextResponse . json (
8047 {
81- notifications,
82- pagination : {
83- total,
84- limit : parsed . data . limit ?? 20 ,
85- offset : parsed . data . offset ?? 0 ,
86- hasMore : ( parsed . data . offset ?? 0 ) + notifications . length < total ,
48+ data : result . notifications ,
49+ meta : {
50+ totalCount : result . totalItems ,
51+ page : query . page ,
52+ limit : query . limit ,
53+ totalPages : Math . max ( 1 , Math . ceil ( result . totalItems / query . limit ) ) ,
54+ unreadCount,
8755 } ,
8856 } ,
8957 {
@@ -92,10 +60,16 @@ export const GET = withAuth(async (request: NextRequest, auth) => {
9260 } ,
9361 ) ;
9462 } catch ( err ) {
63+ if ( err instanceof NotificationError ) {
64+ return NextResponse . json (
65+ { error : err . message , code : err . code } ,
66+ { status : 400 } ,
67+ ) ;
68+ }
9569 console . error ( "[GET /api/notifications]" , err ) ;
9670 return NextResponse . json (
97- { error : "Failed to fetch notifications" } ,
98- { status : 500 } ,
71+ { error : "Failed to fetch notifications" , code : "NOTIFICATIONS_LIST_FAILED" } ,
72+ { status : 503 } ,
9973 ) ;
10074 }
10175} ) ;
@@ -106,40 +80,35 @@ export const GET = withAuth(async (request: NextRequest, auth) => {
10680 * PATCH /api/notifications
10781 *
10882 * Marks all unread notifications for the authenticated user as read.
83+ * Returns { updatedCount }.
10984 */
110- export const PATCH = withAuth ( async ( request : NextRequest , auth ) => {
111- const limited = await enforceRateLimit ( request , {
112- key : buildRateLimitKey ( request , "notifications:update" , auth . walletAddress ) ,
113- limit : 30 ,
114- windowMs : 60_000 ,
115- } ) ;
116- if ( limited ) return limited ;
117-
118- const userId = await getUserIdByWallet ( auth . walletAddress ) ;
119- if ( userId === null ) {
120- return NextResponse . json (
121- {
122- error : "Platform user not found for this wallet" ,
123- code : "USER_NOT_FOUND" ,
124- } ,
125- { status : 404 } ,
126- ) ;
127- }
128-
85+ export const PATCH = withAuth ( async ( request : NextRequest , auth : AuthContext ) => {
86+ void request ;
12987 try {
130- const updatedCount = await markAllAsReadDb ( userId ) ;
88+ const userId = await resolveUserIdByWallet ( auth . walletAddress ) ;
89+ if ( userId === null ) {
90+ return NextResponse . json (
91+ { error : "Platform user not found for this wallet" , code : "USER_NOT_FOUND" } ,
92+ { status : 404 } ,
93+ ) ;
94+ }
95+
96+ const result = await markAllNotificationsRead ( userId ) ;
13197 return NextResponse . json (
132- {
133- message : "All notifications marked as read" ,
134- updatedCount,
135- } ,
98+ { message : "All notifications marked as read" , updatedCount : result . updatedCount } ,
13699 { status : 200 } ,
137100 ) ;
138101 } catch ( err ) {
102+ if ( err instanceof NotificationError ) {
103+ return NextResponse . json (
104+ { error : err . message , code : err . code } ,
105+ { status : 400 } ,
106+ ) ;
107+ }
139108 console . error ( "[PATCH /api/notifications]" , err ) ;
140109 return NextResponse . json (
141- { error : "Failed to update notifications" } ,
142- { status : 500 } ,
110+ { error : "Failed to update notifications" , code : "NOTIFICATION_UPDATE_FAILED" } ,
111+ { status : 503 } ,
143112 ) ;
144113 }
145114} ) ;
0 commit comments