Skip to content

Commit 975fd0d

Browse files
committed
Import JSON from body or file
1 parent 4ac918f commit 975fd0d

File tree

2 files changed

+110
-0
lines changed

2 files changed

+110
-0
lines changed

src/import/json.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { createResponse } from '../utils';
2+
import { enqueueOperation, processNextOperation } from '../operation';
3+
4+
interface ColumnMapping {
5+
[key: string]: string;
6+
}
7+
8+
interface JsonData {
9+
data: any[];
10+
columnMapping?: Record<string, string>;
11+
}
12+
13+
export async function importTableFromJsonRoute(
14+
operationQueue: any,
15+
ctx: any,
16+
processingOperation: { value: boolean },
17+
tableName: string,
18+
request: Request
19+
): Promise<Response> {
20+
try {
21+
if (!request.body) {
22+
return createResponse(undefined, 'Request body is empty', 400);
23+
}
24+
25+
let jsonData: JsonData;
26+
const contentType = request.headers.get('Content-Type') || '';
27+
28+
if (contentType.includes('application/json')) {
29+
// Handle JSON data in POST body
30+
jsonData = await request.json() as JsonData;
31+
} else if (contentType.includes('multipart/form-data')) {
32+
// Handle file upload
33+
const formData = await request.formData();
34+
const file = formData.get('file') as File | null;
35+
36+
if (!file) {
37+
return createResponse(undefined, 'No file uploaded', 400);
38+
}
39+
40+
const fileContent = await file.text();
41+
jsonData = JSON.parse(fileContent) as JsonData;
42+
} else {
43+
return createResponse(undefined, 'Unsupported Content-Type', 400);
44+
}
45+
46+
if (!Array.isArray(jsonData.data)) {
47+
return createResponse(undefined, 'Invalid JSON format. Expected an object with "data" array and optional "columnMapping".', 400);
48+
}
49+
50+
const { data, columnMapping = {} } = jsonData;
51+
52+
const failedStatements: { statement: string; error: string }[] = [];
53+
let successCount = 0;
54+
55+
for (const record of data) {
56+
const mappedRecord = mapRecord(record, columnMapping);
57+
const columns = Object.keys(mappedRecord);
58+
const values = Object.values(mappedRecord);
59+
const placeholders = values.map(() => '?').join(', ');
60+
61+
const sql = `INSERT INTO ${tableName} (${columns.join(', ')}) VALUES (${placeholders})`;
62+
63+
try {
64+
await enqueueOperation(
65+
[{ sql, params: values }],
66+
false,
67+
false,
68+
operationQueue,
69+
() => processNextOperation(sql, operationQueue, ctx, processingOperation)
70+
);
71+
successCount++;
72+
} catch (error: any) {
73+
failedStatements.push({
74+
statement: sql,
75+
error: error.message || 'Unknown error'
76+
});
77+
}
78+
}
79+
80+
const totalRecords = data.length;
81+
const failedCount = failedStatements.length;
82+
83+
const resultMessage = `Imported ${successCount} out of ${totalRecords} records successfully. ${failedCount} records failed.`;
84+
85+
return createResponse({
86+
message: resultMessage,
87+
failedStatements: failedStatements
88+
}, undefined, 200);
89+
90+
} catch (error: any) {
91+
console.error('JSON Import Error:', error);
92+
return createResponse(undefined, 'Failed to import JSON data', 500);
93+
}
94+
}
95+
96+
function mapRecord(record: any, columnMapping: ColumnMapping): any {
97+
const mappedRecord: any = {};
98+
for (const [key, value] of Object.entries(record)) {
99+
const mappedKey = columnMapping[key] || key;
100+
mappedRecord[mappedKey] = value;
101+
}
102+
return mappedRecord;
103+
}

src/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { dumpDatabaseRoute } from './export/dump';
77
import { exportTableToJsonRoute } from './export/json';
88
import { exportTableToCsvRoute } from './export/csv';
99
import { importDumpRoute } from './import/dump';
10+
import { importTableFromJsonRoute } from './import/json';
1011

1112
const DURABLE_OBJECT_ID = 'sql-durable-object';
1213

@@ -144,6 +145,12 @@ export class DatabaseDurableObject extends DurableObject {
144145
return exportTableToCsvRoute(this.sql, this.operationQueue, this.ctx, this.processingOperation, tableName);
145146
} else if (request.method === 'POST' && url.pathname === '/import/dump') {
146147
return importDumpRoute(request, this.sql, this.operationQueue, this.ctx, this.processingOperation);
148+
} else if (request.method === 'POST' && url.pathname.startsWith('/import/json/')) {
149+
const tableName = url.pathname.split('/').pop();
150+
if (!tableName) {
151+
return createResponse(undefined, 'Table name is required', 400);
152+
}
153+
return importTableFromJsonRoute(this.operationQueue, this.ctx, this.processingOperation, tableName, request);
147154
} else {
148155
return createResponse(undefined, 'Unknown operation', 400);
149156
}

0 commit comments

Comments
 (0)