Skip to content

Commit a49849e

Browse files
authored
Merge pull request #24 from Brayden/bwilmoth/import-json
Import JSON from body or file
2 parents 4ac918f + ca807d1 commit a49849e

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed

src/import/json.ts

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

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.sql, this.operationQueue, this.ctx, this.processingOperation, tableName, request);
147154
} else {
148155
return createResponse(undefined, 'Unknown operation', 400);
149156
}

0 commit comments

Comments
 (0)