sitepaige-mcp-server 1.2.3 → 1.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/form.tsx +57 -30
- package/components/menu.tsx +0 -1
- package/defaultapp/api/db-forms.ts +203 -0
- package/defaultapp/api/form/route.ts +244 -0
- package/defaultapp/api/profile/route.ts +3 -3
- package/dist/components/form.tsx +57 -30
- package/dist/components/menu.tsx +0 -1
- package/dist/defaultapp/api/db-forms.ts +203 -0
- package/dist/defaultapp/api/form/route.ts +244 -0
- package/dist/defaultapp/api/profile/route.ts +3 -3
- package/dist/generators/apis.js +2 -2
- package/dist/generators/apis.js.map +1 -1
- package/dist/generators/design.js +0 -1
- package/dist/generators/design.js.map +1 -1
- package/dist/generators/html-to-jsx.js +57 -0
- package/dist/generators/html-to-jsx.js.map +1 -0
- package/dist/generators/sql.js +15 -0
- package/dist/generators/sql.js.map +1 -1
- package/dist/generators/views.js +36 -4
- package/dist/generators/views.js.map +1 -1
- package/package.json +1 -1
- package/transpiler/transpiler.cjs +3 -0
- package/dist/defaultapp/storage/email.ts +0 -162
- package/dist/defaultapp/storage/files.ts +0 -160
package/components/form.tsx
CHANGED
|
@@ -155,6 +155,7 @@ interface FormData {
|
|
|
155
155
|
export default function RForm({ name, custom_view_description, design }: RFormProps) {
|
|
156
156
|
const [submitted, setSubmitted] = useState(false);
|
|
157
157
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
158
|
+
const [error, setError] = useState<string | null>(null);
|
|
158
159
|
|
|
159
160
|
// Parse form configuration
|
|
160
161
|
let formData: FormData = {
|
|
@@ -171,26 +172,60 @@ export default function RForm({ name, custom_view_description, design }: RFormPr
|
|
|
171
172
|
console.error('Error parsing form data:', e);
|
|
172
173
|
}
|
|
173
174
|
|
|
174
|
-
// Don't render if no submission email
|
|
175
|
-
if (!formData.submissionEmail) {
|
|
176
|
-
return (
|
|
177
|
-
<div className="p-8 text-center text-gray-500">
|
|
178
|
-
<h1>{name}</h1>
|
|
179
|
-
<p>Form configuration error: No submission email specified.</p>
|
|
180
|
-
</div>
|
|
181
|
-
);
|
|
182
|
-
}
|
|
183
|
-
|
|
184
175
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
|
185
176
|
e.preventDefault();
|
|
186
177
|
setIsSubmitting(true);
|
|
178
|
+
setError(null);
|
|
179
|
+
|
|
180
|
+
// Get form data
|
|
181
|
+
const form = e.currentTarget;
|
|
182
|
+
const formDataObj: Record<string, any> = {};
|
|
183
|
+
|
|
184
|
+
// Collect all form fields
|
|
185
|
+
formData.fields.forEach((field) => {
|
|
186
|
+
const element = form.elements.namedItem(field.name) as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
|
|
187
|
+
if (element) {
|
|
188
|
+
formDataObj[field.name] = element.value;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Add metadata
|
|
193
|
+
formDataObj._formName = name;
|
|
194
|
+
if (formData.submissionEmail) {
|
|
195
|
+
formDataObj._notificationEmail = formData.submissionEmail;
|
|
196
|
+
}
|
|
187
197
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
198
|
+
try {
|
|
199
|
+
// Get CSRF token if available
|
|
200
|
+
const csrfMeta = document.querySelector('meta[name="csrf-token"]');
|
|
201
|
+
const csrfToken = csrfMeta?.getAttribute('content');
|
|
202
|
+
|
|
203
|
+
const response = await fetch('/api/form', {
|
|
204
|
+
method: 'POST',
|
|
205
|
+
headers: {
|
|
206
|
+
'Content-Type': 'application/json',
|
|
207
|
+
...(csrfToken && { 'X-CSRF-Token': csrfToken })
|
|
208
|
+
},
|
|
209
|
+
body: JSON.stringify({
|
|
210
|
+
formName: name,
|
|
211
|
+
data: formDataObj
|
|
212
|
+
})
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const result = await response.json();
|
|
216
|
+
|
|
217
|
+
if (response.ok) {
|
|
218
|
+
setSubmitted(true);
|
|
219
|
+
setIsSubmitting(false);
|
|
220
|
+
} else {
|
|
221
|
+
setError(result.error || 'Failed to submit form. Please try again.');
|
|
222
|
+
setIsSubmitting(false);
|
|
223
|
+
}
|
|
224
|
+
} catch (err) {
|
|
225
|
+
console.error('Form submission error:', err);
|
|
226
|
+
setError('Network error. Please check your connection and try again.');
|
|
192
227
|
setIsSubmitting(false);
|
|
193
|
-
}
|
|
228
|
+
}
|
|
194
229
|
};
|
|
195
230
|
|
|
196
231
|
if (submitted) {
|
|
@@ -219,8 +254,7 @@ export default function RForm({ name, custom_view_description, design }: RFormPr
|
|
|
219
254
|
<button
|
|
220
255
|
onClick={() => {
|
|
221
256
|
setSubmitted(false);
|
|
222
|
-
|
|
223
|
-
window.location.reload();
|
|
257
|
+
setError(null);
|
|
224
258
|
}}
|
|
225
259
|
className="mt-6 px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors"
|
|
226
260
|
>
|
|
@@ -252,19 +286,17 @@ export default function RForm({ name, custom_view_description, design }: RFormPr
|
|
|
252
286
|
<div className="w-full max-w-2xl mx-auto p-6" style={formStyles}>
|
|
253
287
|
<h1>{name}</h1>
|
|
254
288
|
<form
|
|
255
|
-
action={`https://formsubmit.co/${formData.submissionEmail}`}
|
|
256
|
-
method="POST"
|
|
257
289
|
onSubmit={handleSubmit}
|
|
258
290
|
className="space-y-6"
|
|
259
291
|
>
|
|
260
|
-
{/*
|
|
261
|
-
{
|
|
262
|
-
<
|
|
292
|
+
{/* Display error message if any */}
|
|
293
|
+
{error && (
|
|
294
|
+
<div className="bg-red-50 border border-red-200 rounded-lg p-4 text-red-700">
|
|
295
|
+
<p className="font-medium">Error</p>
|
|
296
|
+
<p className="text-sm mt-1">{error}</p>
|
|
297
|
+
</div>
|
|
263
298
|
)}
|
|
264
299
|
|
|
265
|
-
{/* Thank you page - redirect back to same page with submitted state */}
|
|
266
|
-
<input type="hidden" name="_next" value={typeof window !== 'undefined' ? window.location.href : ''} />
|
|
267
|
-
|
|
268
300
|
{/* Render form fields */}
|
|
269
301
|
{formData.fields.map((field, index) => (
|
|
270
302
|
<div key={field.id || index} className="form-field">
|
|
@@ -384,11 +416,6 @@ export default function RForm({ name, custom_view_description, design }: RFormPr
|
|
|
384
416
|
</button>
|
|
385
417
|
</div>
|
|
386
418
|
</form>
|
|
387
|
-
|
|
388
|
-
{/* FormSubmit.co attribution (optional but nice to have) */}
|
|
389
|
-
<div className="mt-6 text-center text-xs text-gray-400">
|
|
390
|
-
<p>{getFormTranslation('Form secured by FormSubmit', design.websiteLanguage)}</p>
|
|
391
|
-
</div>
|
|
392
419
|
</div>
|
|
393
420
|
);
|
|
394
421
|
}
|
package/components/menu.tsx
CHANGED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Form submission database functions
|
|
3
|
+
Handles storing form data in the database
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { db_init, db_query, DatabaseClient } from './db';
|
|
7
|
+
import * as crypto from 'crypto';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Insert a form submission into the database
|
|
11
|
+
* @param formName - Name/identifier of the form
|
|
12
|
+
* @param formData - JSON data from the form
|
|
13
|
+
* @returns The ID of the inserted submission
|
|
14
|
+
*/
|
|
15
|
+
export async function insertFormSubmission(
|
|
16
|
+
formName: string,
|
|
17
|
+
formData: Record<string, any>
|
|
18
|
+
): Promise<number> {
|
|
19
|
+
const client = await db_init();
|
|
20
|
+
|
|
21
|
+
const dbType = (process.env.DATABASE_TYPE || process.env.DB_TYPE || 'postgres').toLowerCase();
|
|
22
|
+
|
|
23
|
+
let query: string;
|
|
24
|
+
let params: any[];
|
|
25
|
+
|
|
26
|
+
switch (dbType) {
|
|
27
|
+
case 'postgres':
|
|
28
|
+
query = `
|
|
29
|
+
INSERT INTO form_submissions (form_name, form_data)
|
|
30
|
+
VALUES ($1, $2)
|
|
31
|
+
RETURNING id
|
|
32
|
+
`;
|
|
33
|
+
params = [formName, JSON.stringify(formData)];
|
|
34
|
+
break;
|
|
35
|
+
|
|
36
|
+
case 'mysql':
|
|
37
|
+
query = `
|
|
38
|
+
INSERT INTO form_submissions (form_name, form_data)
|
|
39
|
+
VALUES (?, ?)
|
|
40
|
+
`;
|
|
41
|
+
params = [formName, JSON.stringify(formData)];
|
|
42
|
+
break;
|
|
43
|
+
|
|
44
|
+
default: // sqlite
|
|
45
|
+
query = `
|
|
46
|
+
INSERT INTO form_submissions (form_name, form_data)
|
|
47
|
+
VALUES (?, ?)
|
|
48
|
+
`;
|
|
49
|
+
params = [formName, JSON.stringify(formData)];
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const result = await db_query(client, query, params);
|
|
54
|
+
|
|
55
|
+
if (dbType === 'postgres') {
|
|
56
|
+
return result[0].id;
|
|
57
|
+
} else if (dbType === 'mysql') {
|
|
58
|
+
return (result as any).insertId;
|
|
59
|
+
} else {
|
|
60
|
+
// SQLite - get the last inserted row id
|
|
61
|
+
const lastIdResult = await db_query(client, 'SELECT last_insert_rowid() as id', []);
|
|
62
|
+
return lastIdResult[0].id;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Get form submissions by form name with pagination
|
|
68
|
+
* @param formName - Name of the form (optional, returns all if not provided)
|
|
69
|
+
* @param limit - Number of records to return
|
|
70
|
+
* @param offset - Number of records to skip
|
|
71
|
+
* @returns Array of form submissions
|
|
72
|
+
*/
|
|
73
|
+
export async function getFormSubmissions(
|
|
74
|
+
formName?: string,
|
|
75
|
+
limit: number = 50,
|
|
76
|
+
offset: number = 0
|
|
77
|
+
): Promise<Array<{
|
|
78
|
+
id: number;
|
|
79
|
+
timestamp: string;
|
|
80
|
+
form_name: string;
|
|
81
|
+
form_data: any;
|
|
82
|
+
}>> {
|
|
83
|
+
const client = await db_init();
|
|
84
|
+
|
|
85
|
+
let query: string;
|
|
86
|
+
let params: any[];
|
|
87
|
+
|
|
88
|
+
if (formName) {
|
|
89
|
+
query = `
|
|
90
|
+
SELECT id, timestamp, form_name, form_data
|
|
91
|
+
FROM form_submissions
|
|
92
|
+
WHERE form_name = ?
|
|
93
|
+
ORDER BY timestamp DESC
|
|
94
|
+
LIMIT ? OFFSET ?
|
|
95
|
+
`;
|
|
96
|
+
params = [formName, limit, offset];
|
|
97
|
+
} else {
|
|
98
|
+
query = `
|
|
99
|
+
SELECT id, timestamp, form_name, form_data
|
|
100
|
+
FROM form_submissions
|
|
101
|
+
ORDER BY timestamp DESC
|
|
102
|
+
LIMIT ? OFFSET ?
|
|
103
|
+
`;
|
|
104
|
+
params = [limit, offset];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const result = await db_query(client, query, params);
|
|
108
|
+
|
|
109
|
+
// Parse JSON data for each submission
|
|
110
|
+
return result.map(row => ({
|
|
111
|
+
...row,
|
|
112
|
+
form_data: typeof row.form_data === 'string' ? JSON.parse(row.form_data) : row.form_data
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get a single form submission by ID
|
|
118
|
+
* @param id - ID of the submission
|
|
119
|
+
* @returns Form submission or null if not found
|
|
120
|
+
*/
|
|
121
|
+
export async function getFormSubmissionById(
|
|
122
|
+
id: number
|
|
123
|
+
): Promise<{
|
|
124
|
+
id: number;
|
|
125
|
+
timestamp: string;
|
|
126
|
+
form_name: string;
|
|
127
|
+
form_data: any;
|
|
128
|
+
} | null> {
|
|
129
|
+
const client = await db_init();
|
|
130
|
+
|
|
131
|
+
const query = `
|
|
132
|
+
SELECT id, timestamp, form_name, form_data
|
|
133
|
+
FROM form_submissions
|
|
134
|
+
WHERE id = ?
|
|
135
|
+
`;
|
|
136
|
+
|
|
137
|
+
const result = await db_query(client, query, [id]);
|
|
138
|
+
|
|
139
|
+
if (result.length === 0) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const row = result[0];
|
|
144
|
+
return {
|
|
145
|
+
...row,
|
|
146
|
+
form_data: typeof row.form_data === 'string' ? JSON.parse(row.form_data) : row.form_data
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Delete a form submission by ID
|
|
152
|
+
* @param id - ID of the submission to delete
|
|
153
|
+
* @returns true if deleted, false if not found
|
|
154
|
+
*/
|
|
155
|
+
export async function deleteFormSubmission(id: number): Promise<boolean> {
|
|
156
|
+
const client = await db_init();
|
|
157
|
+
|
|
158
|
+
const query = `
|
|
159
|
+
DELETE FROM form_submissions
|
|
160
|
+
WHERE id = ?
|
|
161
|
+
`;
|
|
162
|
+
|
|
163
|
+
const result = await db_query(client, query, [id]);
|
|
164
|
+
|
|
165
|
+
// Check if a row was deleted
|
|
166
|
+
if ((result as any).affectedRows !== undefined) {
|
|
167
|
+
return (result as any).affectedRows > 0;
|
|
168
|
+
} else if ((result as any).changes !== undefined) {
|
|
169
|
+
return (result as any).changes > 0;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get count of form submissions by form name
|
|
177
|
+
* @param formName - Name of the form (optional, returns total if not provided)
|
|
178
|
+
* @returns Count of submissions
|
|
179
|
+
*/
|
|
180
|
+
export async function getFormSubmissionCount(formName?: string): Promise<number> {
|
|
181
|
+
const client = await db_init();
|
|
182
|
+
|
|
183
|
+
let query: string;
|
|
184
|
+
let params: any[];
|
|
185
|
+
|
|
186
|
+
if (formName) {
|
|
187
|
+
query = `
|
|
188
|
+
SELECT COUNT(*) as count
|
|
189
|
+
FROM form_submissions
|
|
190
|
+
WHERE form_name = ?
|
|
191
|
+
`;
|
|
192
|
+
params = [formName];
|
|
193
|
+
} else {
|
|
194
|
+
query = `
|
|
195
|
+
SELECT COUNT(*) as count
|
|
196
|
+
FROM form_submissions
|
|
197
|
+
`;
|
|
198
|
+
params = [];
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const result = await db_query(client, query, params);
|
|
202
|
+
return parseInt(result[0].count, 10);
|
|
203
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { getFormSubmissions, getFormSubmissionById } from '../db-forms';
|
|
3
|
+
import { validateSession } from '../db-users';
|
|
4
|
+
import { cookies } from 'next/headers';
|
|
5
|
+
import { send_email } from '../storage/email';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* POST /api/form
|
|
9
|
+
* Submit a form with data
|
|
10
|
+
*
|
|
11
|
+
* Request body:
|
|
12
|
+
* {
|
|
13
|
+
* formName: string, // Required: Name/identifier of the form
|
|
14
|
+
* data: object // Required: Form data as key-value pairs
|
|
15
|
+
* }
|
|
16
|
+
*
|
|
17
|
+
* Response:
|
|
18
|
+
* {
|
|
19
|
+
* success: boolean,
|
|
20
|
+
* message: string
|
|
21
|
+
* }
|
|
22
|
+
*/
|
|
23
|
+
export async function POST(request: NextRequest) {
|
|
24
|
+
try {
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
const body = await request.json();
|
|
28
|
+
const { formName, data } = body;
|
|
29
|
+
|
|
30
|
+
// Validate required fields
|
|
31
|
+
if (!formName || typeof formName !== 'string') {
|
|
32
|
+
return NextResponse.json(
|
|
33
|
+
{ error: 'Form name is required' },
|
|
34
|
+
{ status: 400 }
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!data || typeof data !== 'object') {
|
|
39
|
+
return NextResponse.json(
|
|
40
|
+
{ error: 'Form data is required and must be an object' },
|
|
41
|
+
{ status: 400 }
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Add metadata to the form data
|
|
46
|
+
const enrichedData = {
|
|
47
|
+
...data,
|
|
48
|
+
_metadata: {
|
|
49
|
+
submittedAt: new Date().toISOString(),
|
|
50
|
+
userAgent: request.headers.get('user-agent') || 'Unknown',
|
|
51
|
+
ip: request.headers.get('x-forwarded-for') ||
|
|
52
|
+
request.headers.get('x-real-ip') ||
|
|
53
|
+
'Unknown',
|
|
54
|
+
referer: request.headers.get('referer') || 'Direct'
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Check if user is authenticated (optional - forms can work for anonymous users too)
|
|
59
|
+
let userId: string | null = null;
|
|
60
|
+
try {
|
|
61
|
+
const sessionCookie = await cookies();
|
|
62
|
+
const sessionToken = sessionCookie.get('session_id')?.value;
|
|
63
|
+
if (sessionToken) {
|
|
64
|
+
const sessionData = await validateSession(sessionToken);
|
|
65
|
+
if (sessionData.valid && sessionData.user) {
|
|
66
|
+
userId = sessionData.user.userid;
|
|
67
|
+
enrichedData._metadata.userId = userId;
|
|
68
|
+
enrichedData._metadata.userEmail = sessionData.user.email;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} catch (err) {
|
|
72
|
+
// Authentication is optional, continue without user data
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Send email using Resend
|
|
76
|
+
const siteDomain = process.env.NODE_ENV === 'production' ? process.env.DOMAIN : process.env.LOCAL_DOMAIN;
|
|
77
|
+
|
|
78
|
+
// Determine hostname for email addresses
|
|
79
|
+
let hostname = 'sitepaige.com'; // Fallback
|
|
80
|
+
try {
|
|
81
|
+
if (siteDomain) {
|
|
82
|
+
// Handle potential missing protocol
|
|
83
|
+
const urlStr = siteDomain.startsWith('http') ? siteDomain : `https://${siteDomain}`;
|
|
84
|
+
hostname = new URL(urlStr).hostname;
|
|
85
|
+
}
|
|
86
|
+
} catch (e) {
|
|
87
|
+
console.warn('Could not parse siteDomain for hostname', siteDomain);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const toAddress = `admin@${hostname}`;
|
|
91
|
+
const fromAddress = process.env.EMAIL_FROM || `noreply@sitepaige.com`;
|
|
92
|
+
|
|
93
|
+
// Construct email content
|
|
94
|
+
const dataRows = Object.entries(enrichedData)
|
|
95
|
+
.filter(([key]) => key !== '_metadata')
|
|
96
|
+
.map(([key, value]) => `
|
|
97
|
+
<tr>
|
|
98
|
+
<td style="padding: 8px; border-bottom: 1px solid #ddd;"><strong>${key}</strong></td>
|
|
99
|
+
<td style="padding: 8px; border-bottom: 1px solid #ddd;">${typeof value === 'object' ? JSON.stringify(value) : String(value)}</td>
|
|
100
|
+
</tr>
|
|
101
|
+
`).join('');
|
|
102
|
+
|
|
103
|
+
const emailHtml = `
|
|
104
|
+
<h2>New Form Submission: ${formName}</h2>
|
|
105
|
+
<table style="width: 100%; border-collapse: collapse;">
|
|
106
|
+
<tbody>
|
|
107
|
+
${dataRows}
|
|
108
|
+
</tbody>
|
|
109
|
+
</table>
|
|
110
|
+
<br>
|
|
111
|
+
<h3>Metadata</h3>
|
|
112
|
+
<pre>${JSON.stringify(enrichedData._metadata, null, 2)}</pre>
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
const emailText = `
|
|
116
|
+
New Form Submission: ${formName}
|
|
117
|
+
--------------------------------
|
|
118
|
+
${Object.entries(enrichedData)
|
|
119
|
+
.filter(([key]) => key !== '_metadata')
|
|
120
|
+
.map(([key, value]) => `${key}: ${typeof value === 'object' ? JSON.stringify(value) : String(value)}`)
|
|
121
|
+
.join('\n')}
|
|
122
|
+
|
|
123
|
+
Metadata:
|
|
124
|
+
${JSON.stringify(enrichedData._metadata, null, 2)}
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
await send_email({
|
|
128
|
+
to: toAddress,
|
|
129
|
+
from: fromAddress,
|
|
130
|
+
subject: `New Form Submission: ${formName}`,
|
|
131
|
+
html: emailHtml,
|
|
132
|
+
text: emailText
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return NextResponse.json({
|
|
136
|
+
success: true,
|
|
137
|
+
message: 'Form submitted successfully'
|
|
138
|
+
}, { status: 201 });
|
|
139
|
+
|
|
140
|
+
} catch (error) {
|
|
141
|
+
console.error('Form submission error:', error);
|
|
142
|
+
return NextResponse.json(
|
|
143
|
+
{ error: error instanceof Error ? error.message : 'Failed to submit form' },
|
|
144
|
+
{ status: 500 }
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* GET /api/form
|
|
151
|
+
* Retrieve form submissions (requires admin authentication)
|
|
152
|
+
*
|
|
153
|
+
* Query parameters:
|
|
154
|
+
* - formName: string (optional) - Filter by form name
|
|
155
|
+
* - limit: number (optional, default 50) - Number of records to return
|
|
156
|
+
* - offset: number (optional, default 0) - Number of records to skip
|
|
157
|
+
* - id: number (optional) - Get a specific submission by ID
|
|
158
|
+
*
|
|
159
|
+
* Response:
|
|
160
|
+
* {
|
|
161
|
+
* submissions: Array<{
|
|
162
|
+
* id: number,
|
|
163
|
+
* timestamp: string,
|
|
164
|
+
* form_name: string,
|
|
165
|
+
* form_data: object
|
|
166
|
+
* }>,
|
|
167
|
+
* total: number
|
|
168
|
+
* }
|
|
169
|
+
*/
|
|
170
|
+
export async function GET(request: NextRequest) {
|
|
171
|
+
try {
|
|
172
|
+
// Check authentication - only admins can view form submissions
|
|
173
|
+
const sessionCookie = await cookies();
|
|
174
|
+
const sessionToken = sessionCookie.get('session_id')?.value;
|
|
175
|
+
|
|
176
|
+
if (!sessionToken) {
|
|
177
|
+
return NextResponse.json(
|
|
178
|
+
{ error: 'Authentication required' },
|
|
179
|
+
{ status: 401 }
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const sessionData = await validateSession(sessionToken);
|
|
184
|
+
if (!sessionData.valid || !sessionData.user) {
|
|
185
|
+
return NextResponse.json(
|
|
186
|
+
{ error: 'Invalid session' },
|
|
187
|
+
{ status: 401 }
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check if user is admin (userlevel 2)
|
|
192
|
+
if (sessionData.user.userlevel !== 2) {
|
|
193
|
+
return NextResponse.json(
|
|
194
|
+
{ error: 'Admin access required' },
|
|
195
|
+
{ status: 403 }
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Parse query parameters
|
|
200
|
+
const { searchParams } = new URL(request.url);
|
|
201
|
+
const formName = searchParams.get('formName') || undefined;
|
|
202
|
+
const limit = parseInt(searchParams.get('limit') || '50', 10);
|
|
203
|
+
const offset = parseInt(searchParams.get('offset') || '0', 10);
|
|
204
|
+
const id = searchParams.get('id');
|
|
205
|
+
|
|
206
|
+
// If ID is provided, get a specific submission
|
|
207
|
+
if (id) {
|
|
208
|
+
const submission = await getFormSubmissionById(parseInt(id, 10));
|
|
209
|
+
if (!submission) {
|
|
210
|
+
return NextResponse.json(
|
|
211
|
+
{ error: 'Submission not found' },
|
|
212
|
+
{ status: 404 }
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
return NextResponse.json({ submission });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Get form submissions with pagination
|
|
219
|
+
const [submissions, total] = await Promise.all([
|
|
220
|
+
getFormSubmissions(formName, limit, offset),
|
|
221
|
+
getFormSubmissionCount(formName)
|
|
222
|
+
]);
|
|
223
|
+
|
|
224
|
+
return NextResponse.json({
|
|
225
|
+
submissions,
|
|
226
|
+
total,
|
|
227
|
+
pagination: {
|
|
228
|
+
limit,
|
|
229
|
+
offset,
|
|
230
|
+
hasMore: offset + submissions.length < total
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
} catch (error) {
|
|
235
|
+
console.error('Form retrieval error:', error);
|
|
236
|
+
return NextResponse.json(
|
|
237
|
+
{ error: error instanceof Error ? error.message : 'Failed to retrieve forms' },
|
|
238
|
+
{ status: 500 }
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Import the missing function
|
|
244
|
+
import { getFormSubmissionCount } from '../db-forms';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { cookies } from 'next/headers';
|
|
3
|
-
import { validateSession } from '
|
|
4
|
-
import { updatePassword
|
|
5
|
-
import { deleteUser } from '
|
|
3
|
+
import { validateSession } from '../db-users';
|
|
4
|
+
import { updatePassword } from '../db-password-auth';
|
|
5
|
+
import { deleteUser } from '../db-users';
|
|
6
6
|
|
|
7
7
|
// Handle password change
|
|
8
8
|
export async function PUT(request: NextRequest) {
|