-
Notifications
You must be signed in to change notification settings - Fork 68
Expand file tree
/
Copy pathroute.ts
More file actions
137 lines (115 loc) · 4.32 KB
/
route.ts
File metadata and controls
137 lines (115 loc) · 4.32 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
import { getAllGoals } from "@/lib/contracts/savings-goal";
import { NextRequest, NextResponse } from 'next/server';
import { withAuth } from '@/lib/auth';
import { validatePaginationParams, paginateData, PaginatedResult } from '../../../lib/utils/pagination';
import { buildCreateGoalTx } from '@/lib/contracts/savings-goals';
import {
createValidationError,
handleUnexpectedError
} from '@/lib/errors/api-errors';
import {
validateAmount,
validateFutureDate,
validateGoalName
} from '@/lib/validation/savings-goals';
import { ApiSuccessResponse } from '@/lib/types/savings-goals';
import { auditLog, createAuditEvent, extractIp, AuditAction } from '@/lib/audit';
// ===== Goal Interface & Mock Data =====
interface Goal {
id: string;
title: string;
targetAmount: number;
currentAmount: number;
deadline: string;
createdAt: string;
updatedAt: string;
}
const mockGoals: Goal[] = [
{ id: '1', title: 'Emergency Fund', targetAmount: 10000, currentAmount: 4500, deadline: '2024-12-31', createdAt: '2023-01-15', updatedAt: '2023-06-20' },
{ id: '2', title: 'Vacation Trip', targetAmount: 5000, currentAmount: 1200, deadline: '2024-08-15', createdAt: '2023-03-10', updatedAt: '2023-07-01' },
{ id: '3', title: 'New Car', targetAmount: 25000, currentAmount: 8000, deadline: '2025-06-30', createdAt: '2023-02-20', updatedAt: '2023-08-15' },
];
// ===== Auth-wrapped GET =====
async function getHandler(request: NextRequest, session: string) {
try {
const url = new URL(request.url);
const limitParam = url.searchParams.get('limit');
const cursorParam = url.searchParams.get('cursor');
const paginationParams = {
limit: limitParam ? parseInt(limitParam, 10) : undefined,
cursor: cursorParam || undefined,
};
const { limit, cursor } = validatePaginationParams(paginationParams);
const paginatedResult: PaginatedResult<Goal> = paginateData(
mockGoals,
limit,
(item) => item.id,
cursor
);
// Log goals list retrieval
await auditLog(
createAuditEvent(AuditAction.GOAL_LIST, 'success', {
address: session,
ip: extractIp(request),
})
);
return NextResponse.json(paginatedResult);
} catch (error) {
console.error('Error fetching goals:', error);
return NextResponse.json({ error: 'Failed to fetch goals' }, { status: 500 });
}
}
// ===== Auth-wrapped POST =====
async function postHandler(request: NextRequest, session: string) {
try {
let body;
try {
body = await request.json();
} catch {
return createValidationError('Invalid request body', 'Request body must be valid JSON');
}
const { name, targetAmount, targetDate } = body;
if (!name) {
return createValidationError('Missing required field', 'Goal name is required');
}
const nameValidation = validateGoalName(name);
if (!nameValidation.isValid) {
return createValidationError('Invalid goal name', nameValidation.error);
}
if (targetAmount === undefined || targetAmount === null) {
return createValidationError('Missing required field', 'Target amount is required');
}
const amountValidation = validateAmount(targetAmount);
if (!amountValidation.isValid) {
return createValidationError('Invalid target amount', amountValidation.error);
}
if (!targetDate) {
return createValidationError('Missing required field', 'Target date is required');
}
const dateValidation = validateFutureDate(targetDate);
if (!dateValidation.isValid) {
return createValidationError('Invalid target date', dateValidation.error);
}
// session already validated by withAuth → use as publicKey
const result = await buildCreateGoalTx(session, name, targetAmount, targetDate);
const response: ApiSuccessResponse = { xdr: result.xdr };
// Log goal creation
await auditLog(
createAuditEvent(AuditAction.GOAL_CREATE, 'success', {
address: session,
ip: extractIp(request),
metadata: {
name,
targetAmount,
targetDate,
},
})
);
return NextResponse.json(response, { status: 200 });
} catch (error) {
return handleUnexpectedError(error);
}
}
// Remove the old unprotected GET export and use only auth-wrapped versions
export const GET = withAuth(getHandler);
export const POST = withAuth(postHandler);