Skip to content

Commit 7544b46

Browse files
committed
checkpoint
1 parent df4d8f9 commit 7544b46

File tree

6 files changed

+160
-34
lines changed

6 files changed

+160
-34
lines changed

src/data-import/csv-utils.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,13 @@ function parseCSV(data: string) {
3737
currentLine += char;
3838
i++;
3939
}
40+
let unnamed = 1;
4041
const headers: Array<string> = (lines[0] || []).map((header) => {
41-
const snake = convertString(header.trim(), "snake").replace(/_+/g, "_");
42+
let snake = convertString(header.trim(), "snake").replace(/_+/g, "_");
43+
if (!snake) {
44+
snake = `unnamed_${unnamed}`;
45+
unnamed++;
46+
}
4247
return convertString(snake, "camel");
4348
});
4449
const records: Array<Record<string, unknown>> = [];

src/data-import/data-import.ts

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,29 @@ import { csvUtils } from "./csv-utils.ts";
44
import type { GenericEntry } from "../orm/entry/entry-base.ts";
55
import type { Entry } from "../orm/entry/entry.ts";
66
import { handlePgError, isPgError } from "../orm/db/postgres/pgError.ts";
7+
import { dateUtils } from "@inspatial/cloud/utils";
78

89
export const dataImport = defineEntry("dataImport", {
910
label: "",
1011
description: "",
12+
titleField: "title",
13+
defaultListFields: [
14+
"title",
15+
"entryType",
16+
"status",
17+
"importType",
18+
"totalRecords",
19+
"successfulRecords",
20+
"failedRecords",
21+
],
1122
fields: [{
23+
key: "title",
24+
label: "Title",
25+
type: "DataField",
26+
required: true,
27+
readOnly: true,
28+
defaultValue: "",
29+
}, {
1230
key: "csv",
1331
label: "",
1432
type: "FileField",
@@ -26,10 +44,12 @@ export const dataImport = defineEntry("dataImport", {
2644
label: "Status",
2745
type: "ChoicesField",
2846
choices: [
29-
{ key: "pending", label: "Pending" },
30-
{ key: "processing", label: "Processing" },
31-
{ key: "completed", label: "Completed" },
32-
{ key: "failed", label: "Failed" },
47+
{ key: "pending", label: "Pending", color: "primary" },
48+
{ key: "processing", label: "Processing", color: "warning" },
49+
{ key: "readyForImport", label: "Ready to Import", color: "info" },
50+
{ key: "importing", label: "Importing", color: "warning" },
51+
{ key: "completed", label: "Completed", color: "success" },
52+
{ key: "failed", label: "Failed", color: "error" },
3353
],
3454
defaultValue: "pending",
3555
readOnly: true,
@@ -77,11 +97,20 @@ export const dataImport = defineEntry("dataImport", {
7797
type: "IntField",
7898
defaultValue: 0,
7999
readOnly: true,
100+
}, {
101+
key: "sampleData",
102+
label: "Sample Data",
103+
type: "JSONField",
104+
readOnly: true,
80105
}, {
81106
key: "errorMessage",
82107
label: "Error Message",
83108
type: "TextField",
84109
readOnly: true,
110+
}, {
111+
key: "importColumns",
112+
type: "ListField",
113+
readOnly: true,
85114
}],
86115
});
87116

@@ -92,6 +121,17 @@ dataImport.addHook("validate", {
92121
const result = orm.getEntryType(entryType as EntryName);
93122
},
94123
});
124+
dataImport.addHook("beforeUpdate", {
125+
name: "generateTitle",
126+
description: "Generates the title from the create date and entry type",
127+
handler({ dataImport }) {
128+
const date = dateUtils.getPrettyDate(dataImport.$createdAt, {
129+
format: "yyyy-mm-dd",
130+
showTime: true,
131+
});
132+
dataImport.$title = `Import ${dataImport.$entryType__title} - ${date}`;
133+
},
134+
});
95135
dataImport.addChild("columnMap", {
96136
fields: [{
97137
key: "columnName",
@@ -115,6 +155,7 @@ dataImport.addChild("columnMap", {
115155
}],
116156
});
117157
dataImport.addAction("getContent", {
158+
private: true,
118159
async action({ dataImport, orm }) {
119160
if (!dataImport.$csv) {
120161
return { success: false, message: "No CSV file provided" };
@@ -128,13 +169,29 @@ dataImport.addAction("getContent", {
128169
},
129170
});
130171
dataImport.addAction("processInfo", {
172+
label: "Process CSV",
173+
description:
174+
"Reads the contents of the uploaded CSV and creates the column map and sets the total row count available for import.",
131175
async action({ dataImport, orm }) {
132-
const result = await dataImport.runAction("getContent") as {
176+
dataImport.$errorMessage = "";
177+
dataImport.$totalRecords = null;
178+
dataImport.$successfulRecords = null;
179+
dataImport.$failedRecords = null;
180+
dataImport.$status = "processing";
181+
await dataImport.save();
182+
183+
const result = await dataImport.runAction("getContent").catch((e) => {
184+
return {
185+
success: false,
186+
message: e instanceof Error ? e.message : "Unknown error",
187+
};
188+
}) as {
133189
success: boolean;
134190
message?: string;
135191
headers?: Array<string>;
136192
records?: Array<Record<string, unknown>>;
137193
};
194+
138195
if (!result.success) {
139196
dataImport.$status = "failed";
140197
await dataImport.save();
@@ -153,6 +210,10 @@ dataImport.addAction("processInfo", {
153210
) => [key, `${dataImport.$entryType}:${key}`]),
154211
);
155212
const firstRecord = new Map<string, any>(Object.entries(records[0] || {}));
213+
214+
const sampleData = records.slice(0, 10);
215+
dataImport.$sampleData = { data: sampleData };
216+
156217
const columnMapData = headers.map((header) => ({
157218
columnName: header,
158219
exampleData: (firstRecord.get(header) || "").toString().slice(0, 255),
@@ -161,10 +222,14 @@ dataImport.addAction("processInfo", {
161222

162223
dataImport.$columnMap.update(columnMapData);
163224
dataImport.$totalRecords = records.length;
225+
dataImport.$status = "readyForImport";
226+
dataImport.$importColumns = headers;
164227
await dataImport.save();
165228
},
166229
});
167230
dataImport.addAction("import", {
231+
label: "Run Import",
232+
description: "Imports the data from the CSV based on the current settings.",
168233
async action({ dataImport, orm }) {
169234
const dataMap = dataImport.$columnMap.data.filter((col) => col.mapTo).map(
170235
(col) => ({

src/files/actions/upload-file.ts

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,19 @@
1-
import { CloudAPIAction } from "~/api/cloud-action.ts";
1+
import { defineAPIAction } from "~/api/cloud-action.ts";
22
import MimeTypes from "../mime-types/mime-types.ts";
33
import { joinPath } from "~/utils/path-utils.ts";
44
import type { CloudFile, GlobalCloudFile } from "#types/models.ts";
5-
export const uploadFile = new CloudAPIAction("upload", {
5+
6+
export const uploadFile = defineAPIAction("upload", {
67
label: "Upload File",
78
raw: true,
8-
params: [{
9-
key: "global",
10-
type: "BooleanField",
11-
}, {
12-
key: "publicFile",
13-
type: "BooleanField",
14-
}, {
15-
key: "optimizeImage",
16-
type: "BooleanField",
17-
}, {
18-
key: "optimizeWidth",
19-
type: "IntField",
20-
}, {
21-
key: "optimizeHeight",
22-
type: "IntField",
23-
}, {
24-
key: "optimizeFormat",
25-
type: "DataField",
26-
}],
9+
params: [
10+
{ key: "global", type: "BooleanField" },
11+
{ key: "publicFile", type: "BooleanField" },
12+
{ key: "optimizeImage", type: "BooleanField" },
13+
{ key: "optimizeWidth", type: "IntField" },
14+
{ key: "optimizeHeight", type: "IntField" },
15+
{ key: "optimizeFormat", type: "DataField" },
16+
],
2717
async action(
2818
{
2919
inCloud,

src/orm/field/fields/list-field.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ export default new ORMFieldConfig("ListField", {
1414
return true;
1515
},
1616
dbSave(value, _fieldDef) {
17+
if (Array.isArray(value)) {
18+
value = JSON.stringify(value);
19+
} else {
20+
value = JSON.stringify([]);
21+
}
1722
return value;
1823
},
1924
});

src/types/models.ts

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4952,6 +4952,12 @@ export interface ApiGroupPermission
49524952
}
49534953

49544954
type DataImportFields = {
4955+
/**
4956+
* **Title** (DataField)
4957+
* @type {string}
4958+
* @required true
4959+
*/
4960+
title: string;
49554961
/**
49564962
* **Csv** (FileField)
49574963
* @type {string}
@@ -4967,9 +4973,16 @@ type DataImportFields = {
49674973
entryType: string;
49684974
/**
49694975
* **Status** (ChoicesField)
4970-
* @type {'pending' | 'processing' | 'completed' | 'failed'}
4971-
*/
4972-
status?: "pending" | "processing" | "completed" | "failed" | null;
4976+
* @type {'pending' | 'processing' | 'readyForImport' | 'importing' | 'completed' | 'failed'}
4977+
*/
4978+
status?:
4979+
| "pending"
4980+
| "processing"
4981+
| "readyForImport"
4982+
| "importing"
4983+
| "completed"
4984+
| "failed"
4985+
| null;
49734986
/**
49744987
* **Import Type** (ChoicesField)
49754988
* @type {'create' | 'update' | 'upsert'}
@@ -4998,11 +5011,21 @@ type DataImportFields = {
49985011
* @type {number}
49995012
*/
50005013
failedRecords?: number | null;
5014+
/**
5015+
* **Sample Data** (JSONField)
5016+
* @type {Record<string, unknown>}
5017+
*/
5018+
sampleData?: Record<string, unknown> | null;
50015019
/**
50025020
* **Error Message** (TextField)
50035021
* @type {string}
50045022
*/
50055023
errorMessage?: string | null;
5024+
/**
5025+
* **Import Columns** (ListField)
5026+
* @type {Array<string>}
5027+
*/
5028+
importColumns?: Array<string> | null;
50065029
/**
50075030
* **ID** (IDField)
50085031
* @type {string}
@@ -5067,6 +5090,12 @@ type DataImportFields = {
50675090
export interface DataImport extends EntryBase<"dataImport", DataImportFields> {
50685091
_name: "dataImport";
50695092
__fields__: DataImportFields;
5093+
/**
5094+
* **Title** (DataField)
5095+
* @type {string}
5096+
* @required true
5097+
*/
5098+
$title: string;
50705099
/**
50715100
* **Csv** (FileField)
50725101
* @type {string}
@@ -5082,9 +5111,16 @@ export interface DataImport extends EntryBase<"dataImport", DataImportFields> {
50825111
$entryType: string;
50835112
/**
50845113
* **Status** (ChoicesField)
5085-
* @type {'pending' | 'processing' | 'completed' | 'failed'}
5086-
*/
5087-
$status?: "pending" | "processing" | "completed" | "failed" | null;
5114+
* @type {'pending' | 'processing' | 'readyForImport' | 'importing' | 'completed' | 'failed'}
5115+
*/
5116+
$status?:
5117+
| "pending"
5118+
| "processing"
5119+
| "readyForImport"
5120+
| "importing"
5121+
| "completed"
5122+
| "failed"
5123+
| null;
50885124
/**
50895125
* **Import Type** (ChoicesField)
50905126
* @type {'create' | 'update' | 'upsert'}
@@ -5113,11 +5149,21 @@ export interface DataImport extends EntryBase<"dataImport", DataImportFields> {
51135149
* @type {number}
51145150
*/
51155151
$failedRecords?: number | null;
5152+
/**
5153+
* **Sample Data** (JSONField)
5154+
* @type {Record<string, unknown>}
5155+
*/
5156+
$sampleData?: Record<string, unknown> | null;
51165157
/**
51175158
* **Error Message** (TextField)
51185159
* @type {string}
51195160
*/
51205161
$errorMessage?: string | null;
5162+
/**
5163+
* **Import Columns** (ListField)
5164+
* @type {Array<string>}
5165+
*/
5166+
$importColumns?: Array<string> | null;
51215167
/**
51225168
* **ID** (IDField)
51235169
* @type {string}

src/utils/date-utils.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ export type DateFormat =
3535
| "compact"
3636
| "y/m/d"
3737
| "full"
38-
| "dd.mm.yy";
38+
| "dd.mm.yy"
39+
| "yyyy-mm-dd";
3940

4041
function formatTime(value: string | undefined, options: {
4142
showSeconds?: boolean;
@@ -123,6 +124,18 @@ function getPrettyDate(value: string | number | undefined, options?: {
123124
const year = date.getFullYear();
124125
return `${day}/${month}/${year}`;
125126
};
127+
const formatymd = (date: Date, withTime?: boolean) => {
128+
const day = padZeros(date.getDate());
129+
const month = padZeros(date.getMonth() + 1);
130+
const year = date.getFullYear();
131+
return `${year}-${month}-${day} ${
132+
withTime
133+
? ` at ${padZeros(date.getHours())}:${padZeros(date.getMinutes())}${
134+
options?.showSeconds ? `:${padZeros(date.getSeconds())}` : ""
135+
}`
136+
: ""
137+
}`;
138+
};
126139
const formatCompact = (date: Date) => {
127140
const now = new Date();
128141
const currentYear = now.getFullYear();
@@ -173,6 +186,8 @@ function getPrettyDate(value: string | number | undefined, options?: {
173186
month: "2-digit",
174187
year: "2-digit",
175188
}).replace(/\//g, ".");
189+
case "yyyy-mm-dd":
190+
return formatymd(date, options?.showTime);
176191
default: {
177192
const day = padZeros(date.getDate());
178193
const month = padZeros(date.getMonth() + 1);

0 commit comments

Comments
 (0)