nuxt-google-sheets-import 0.1.4
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/README.md +223 -0
- package/dist/module.d.mts +12 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +115 -0
- package/dist/runtime/assets/css/main.css +1 -0
- package/dist/runtime/components/GoogleSheetsImportExecute.d.vue.ts +7 -0
- package/dist/runtime/components/GoogleSheetsImportExecute.vue +282 -0
- package/dist/runtime/components/GoogleSheetsImportExecute.vue.d.ts +7 -0
- package/dist/runtime/components/GoogleSheetsImportSchemaGuide.d.vue.ts +6 -0
- package/dist/runtime/components/GoogleSheetsImportSchemaGuide.vue +213 -0
- package/dist/runtime/components/GoogleSheetsImportSchemaGuide.vue.d.ts +6 -0
- package/dist/runtime/components/GoogleSheetsImportSource.d.vue.ts +9 -0
- package/dist/runtime/components/GoogleSheetsImportSource.vue +127 -0
- package/dist/runtime/components/GoogleSheetsImportSource.vue.d.ts +9 -0
- package/dist/runtime/composables/useGoogleSheetsImport.d.ts +50 -0
- package/dist/runtime/composables/useGoogleSheetsImport.js +40 -0
- package/dist/runtime/composables/useGoogleSheetsImportWorkflow.d.ts +82 -0
- package/dist/runtime/composables/useGoogleSheetsImportWorkflow.js +256 -0
- package/dist/runtime/import/schemas.d.ts +67 -0
- package/dist/runtime/import/schemas.js +35 -0
- package/dist/runtime/pages/google-sheets-import.d.vue.ts +3 -0
- package/dist/runtime/pages/google-sheets-import.vue +14 -0
- package/dist/runtime/pages/google-sheets-import.vue.d.ts +3 -0
- package/dist/runtime/server/api/collection-type.get.d.ts +6 -0
- package/dist/runtime/server/api/collection-type.get.js +20 -0
- package/dist/runtime/server/api/schema-columns.get.d.ts +14 -0
- package/dist/runtime/server/api/schema-columns.get.js +39 -0
- package/dist/runtime/server/api/sheets.get.d.ts +8 -0
- package/dist/runtime/server/api/sheets.get.js +26 -0
- package/dist/runtime/server/api/values.post.d.ts +6 -0
- package/dist/runtime/server/api/values.post.js +33 -0
- package/dist/runtime/server/api/write.post.d.ts +11 -0
- package/dist/runtime/server/api/write.post.js +64 -0
- package/dist/runtime/server/utils/collectionType.d.ts +3 -0
- package/dist/runtime/server/utils/collectionType.js +58 -0
- package/dist/runtime/server/utils/googleSheets.d.ts +1 -0
- package/dist/runtime/server/utils/googleSheets.js +10 -0
- package/dist/runtime/server/utils/schemaColumns.d.ts +3 -0
- package/dist/runtime/server/utils/schemaColumns.js +84 -0
- package/dist/runtime/server/utils/transform.d.ts +5 -0
- package/dist/runtime/server/utils/transform.js +218 -0
- package/dist/runtime/server/utils/writeFrontmatter.d.ts +17 -0
- package/dist/runtime/server/utils/writeFrontmatter.js +92 -0
- package/dist/runtime/types/googleSheetsApi.d.ts +75 -0
- package/dist/runtime/types/googleSheetsApi.js +0 -0
- package/dist/types.d.mts +3 -0
- package/package.json +66 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
import { useRuntimeConfig } from "#imports";
|
|
2
|
+
import { computed, reactive, ref, toValue, watch } from "vue";
|
|
3
|
+
import { useGoogleSheetsImport } from "./useGoogleSheetsImport.js";
|
|
4
|
+
export function useGoogleSheetsImportWorkflow(options = {}) {
|
|
5
|
+
const { getSheets, getValues, getCollectionType, writeFiles } = useGoogleSheetsImport();
|
|
6
|
+
const config = useRuntimeConfig();
|
|
7
|
+
const defaultContentDir = computed(
|
|
8
|
+
() => config.public.googleSheetsImport?.defaultContentDir ?? "content/data"
|
|
9
|
+
);
|
|
10
|
+
const sourceStatus = ref("idle");
|
|
11
|
+
const sourceError = ref("");
|
|
12
|
+
const sheetTitles = ref([]);
|
|
13
|
+
const source = reactive({
|
|
14
|
+
spreadsheetId: void 0,
|
|
15
|
+
sheetTitle: void 0,
|
|
16
|
+
range: void 0,
|
|
17
|
+
schema: void 0
|
|
18
|
+
});
|
|
19
|
+
const upperCaseRange = computed({
|
|
20
|
+
get: () => source.range,
|
|
21
|
+
set: (value) => {
|
|
22
|
+
source.range = value?.toUpperCase() || "";
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
const selectedSheetPreview = computed(
|
|
26
|
+
() => sheetTitles.value.find((sheet) => sheet.label === source.sheetTitle)
|
|
27
|
+
);
|
|
28
|
+
const sourceQuery = computed(() => {
|
|
29
|
+
if (!source.spreadsheetId || !source.sheetTitle || !source.range || !source.schema) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
spreadsheetId: source.spreadsheetId,
|
|
34
|
+
sheetTitle: source.sheetTitle,
|
|
35
|
+
range: source.range,
|
|
36
|
+
schema: source.schema
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
const externalQuery = computed(() => {
|
|
40
|
+
if (!options.query) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const value = toValue(options.query);
|
|
44
|
+
return value ?? null;
|
|
45
|
+
});
|
|
46
|
+
const activeQuery = computed(() => externalQuery.value ?? sourceQuery.value);
|
|
47
|
+
const canLoadValues = computed(() => Boolean(activeQuery.value));
|
|
48
|
+
const selectedCollectionType = ref("unknown");
|
|
49
|
+
const selectedCollectionTypeStatus = ref("idle");
|
|
50
|
+
async function refreshCollectionType() {
|
|
51
|
+
const schema = activeQuery.value?.schema?.trim();
|
|
52
|
+
if (!schema) {
|
|
53
|
+
selectedCollectionType.value = "unknown";
|
|
54
|
+
selectedCollectionTypeStatus.value = "idle";
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
selectedCollectionTypeStatus.value = "pending";
|
|
58
|
+
try {
|
|
59
|
+
const resolved = await getCollectionType(schema);
|
|
60
|
+
selectedCollectionType.value = resolved.collectionType;
|
|
61
|
+
selectedCollectionTypeStatus.value = "success";
|
|
62
|
+
} catch {
|
|
63
|
+
selectedCollectionType.value = "unknown";
|
|
64
|
+
selectedCollectionTypeStatus.value = "error";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
watch(() => activeQuery.value?.schema, async () => {
|
|
68
|
+
await refreshCollectionType();
|
|
69
|
+
}, { immediate: true });
|
|
70
|
+
async function loadSheetTitles(spreadsheetId) {
|
|
71
|
+
if (!spreadsheetId) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
sourceStatus.value = "pending";
|
|
75
|
+
sourceError.value = "";
|
|
76
|
+
try {
|
|
77
|
+
sheetTitles.value = await getSheets(spreadsheetId);
|
|
78
|
+
sourceStatus.value = "success";
|
|
79
|
+
} catch (error) {
|
|
80
|
+
sourceStatus.value = "error";
|
|
81
|
+
sheetTitles.value = [];
|
|
82
|
+
sourceError.value = error instanceof Error ? error.message : "Failed to retrieve Google Sheet titles.";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
watch(() => source.spreadsheetId, async (spreadsheetId, previousSpreadsheetId) => {
|
|
86
|
+
if (spreadsheetId !== previousSpreadsheetId) {
|
|
87
|
+
source.sheetTitle = void 0;
|
|
88
|
+
source.range = void 0;
|
|
89
|
+
source.schema = void 0;
|
|
90
|
+
await loadSheetTitles(spreadsheetId);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
watch(() => source.sheetTitle, () => {
|
|
94
|
+
if (!source.range) {
|
|
95
|
+
source.range = sheetTitles.value.find((sheet) => sheet.label === source.sheetTitle)?.range;
|
|
96
|
+
}
|
|
97
|
+
if (source.sheetTitle && !source.schema) {
|
|
98
|
+
source.schema = source.sheetTitle.replace(/ /g, "_");
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
const status = ref("idle");
|
|
102
|
+
const writeStatus = ref("idle");
|
|
103
|
+
const importError = ref("");
|
|
104
|
+
const writeError = ref("");
|
|
105
|
+
const writeSummary = ref({
|
|
106
|
+
written: 0,
|
|
107
|
+
overwritten: 0,
|
|
108
|
+
skipped: 0
|
|
109
|
+
});
|
|
110
|
+
const values = ref([]);
|
|
111
|
+
const validationErrors = ref([]);
|
|
112
|
+
const logs = ref([]);
|
|
113
|
+
const writeFile = reactive({
|
|
114
|
+
folder: kebabize(activeQuery.value?.sheetTitle ?? source.sheetTitle ?? ""),
|
|
115
|
+
slug: void 0,
|
|
116
|
+
order: void 0,
|
|
117
|
+
outputFormat: "frontmatter",
|
|
118
|
+
overwriteMode: "overwrite"
|
|
119
|
+
});
|
|
120
|
+
const resolvedBaseContentDir = computed(() => {
|
|
121
|
+
if (selectedCollectionType.value === "page") {
|
|
122
|
+
return "content";
|
|
123
|
+
}
|
|
124
|
+
if (selectedCollectionType.value === "data") {
|
|
125
|
+
return "content/data";
|
|
126
|
+
}
|
|
127
|
+
return defaultContentDir.value;
|
|
128
|
+
});
|
|
129
|
+
const resolvedDestinationPath = computed(() => {
|
|
130
|
+
const folder = writeFile.folder?.trim();
|
|
131
|
+
return folder ? `${resolvedBaseContentDir.value}/${folder}` : resolvedBaseContentDir.value;
|
|
132
|
+
});
|
|
133
|
+
watch(activeQuery, (query) => {
|
|
134
|
+
if (query?.sheetTitle && !writeFile.folder) {
|
|
135
|
+
writeFile.folder = kebabize(query.sheetTitle);
|
|
136
|
+
}
|
|
137
|
+
}, { immediate: true });
|
|
138
|
+
const columnList = computed(() => {
|
|
139
|
+
const unique = /* @__PURE__ */ new Set();
|
|
140
|
+
for (const record of values.value) {
|
|
141
|
+
collectKeys(record).forEach((key) => unique.add(key));
|
|
142
|
+
}
|
|
143
|
+
return [...unique];
|
|
144
|
+
});
|
|
145
|
+
const productsLength = computed(() => values.value.length);
|
|
146
|
+
const logsLength = computed(() => logs.value.length);
|
|
147
|
+
const canWrite = computed(() => Boolean(values.value.length && writeFile.slug && writeFile.folder && writeStatus.value !== "pending"));
|
|
148
|
+
const previewRows = computed(() => values.value.slice(0, 5));
|
|
149
|
+
const shownValidationErrors = computed(() => validationErrors.value.slice(0, 20));
|
|
150
|
+
watch(columnList, (keys) => {
|
|
151
|
+
if (!keys.length) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
if (!writeFile.slug) {
|
|
155
|
+
const slugCandidate = keys.find((key) => /(?:^|\.)(?:slug|id)$/i.test(key));
|
|
156
|
+
writeFile.slug = slugCandidate ?? keys[0];
|
|
157
|
+
}
|
|
158
|
+
if (!writeFile.order) {
|
|
159
|
+
const orderCandidate = keys.find((key) => /(?:^|\.)(?:order|pageOrder)$/i.test(key));
|
|
160
|
+
writeFile.order = orderCandidate;
|
|
161
|
+
}
|
|
162
|
+
}, { immediate: true });
|
|
163
|
+
async function loadValues(queryArg) {
|
|
164
|
+
const query = queryArg ?? activeQuery.value;
|
|
165
|
+
if (!query) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
status.value = "pending";
|
|
169
|
+
validationErrors.value = [];
|
|
170
|
+
importError.value = "";
|
|
171
|
+
writeStatus.value = "idle";
|
|
172
|
+
writeError.value = "";
|
|
173
|
+
writeSummary.value = { written: 0, overwritten: 0, skipped: 0 };
|
|
174
|
+
logs.value = [];
|
|
175
|
+
try {
|
|
176
|
+
const response = await getValues(query);
|
|
177
|
+
values.value = response.records;
|
|
178
|
+
validationErrors.value = response.errors;
|
|
179
|
+
status.value = "success";
|
|
180
|
+
} catch (error) {
|
|
181
|
+
status.value = "error";
|
|
182
|
+
values.value = [];
|
|
183
|
+
importError.value = error instanceof Error ? error.message : "Could not load values from Google Sheets.";
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async function writeValues() {
|
|
187
|
+
if (!canWrite.value || !writeFile.slug || !writeFile.folder) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
writeStatus.value = "pending";
|
|
191
|
+
writeError.value = "";
|
|
192
|
+
try {
|
|
193
|
+
const response = await writeFiles({
|
|
194
|
+
records: values.value,
|
|
195
|
+
schema: activeQuery.value?.schema,
|
|
196
|
+
folder: writeFile.folder,
|
|
197
|
+
slugKey: writeFile.slug,
|
|
198
|
+
orderKey: writeFile.order,
|
|
199
|
+
outputFormat: writeFile.outputFormat,
|
|
200
|
+
overwriteMode: writeFile.overwriteMode
|
|
201
|
+
});
|
|
202
|
+
logs.value = response.logs;
|
|
203
|
+
writeSummary.value = response.summary;
|
|
204
|
+
writeStatus.value = "success";
|
|
205
|
+
} catch (error) {
|
|
206
|
+
writeStatus.value = "error";
|
|
207
|
+
writeError.value = error instanceof Error ? error.message : "Could not write files.";
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
source,
|
|
212
|
+
sourceStatus,
|
|
213
|
+
sourceError,
|
|
214
|
+
sheetTitles,
|
|
215
|
+
upperCaseRange,
|
|
216
|
+
selectedSheetPreview,
|
|
217
|
+
sourceQuery,
|
|
218
|
+
activeQuery,
|
|
219
|
+
canLoadValues,
|
|
220
|
+
selectedCollectionType,
|
|
221
|
+
selectedCollectionTypeStatus,
|
|
222
|
+
resolvedBaseContentDir,
|
|
223
|
+
resolvedDestinationPath,
|
|
224
|
+
loadSheetTitles,
|
|
225
|
+
status,
|
|
226
|
+
writeStatus,
|
|
227
|
+
importError,
|
|
228
|
+
writeError,
|
|
229
|
+
writeSummary,
|
|
230
|
+
values,
|
|
231
|
+
validationErrors,
|
|
232
|
+
logs,
|
|
233
|
+
writeFile,
|
|
234
|
+
columnList,
|
|
235
|
+
productsLength,
|
|
236
|
+
logsLength,
|
|
237
|
+
canWrite,
|
|
238
|
+
previewRows,
|
|
239
|
+
shownValidationErrors,
|
|
240
|
+
loadValues,
|
|
241
|
+
writeValues
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
function kebabize(str) {
|
|
245
|
+
return str.replace(/[A-Z]+(?![a-z])|[A-Z]/g, ($, ofs) => (ofs ? "-" : "") + $.toLowerCase());
|
|
246
|
+
}
|
|
247
|
+
function collectKeys(obj, prefix = "") {
|
|
248
|
+
if (!obj || typeof obj !== "object" || Array.isArray(obj)) {
|
|
249
|
+
return [];
|
|
250
|
+
}
|
|
251
|
+
return Object.entries(obj).flatMap(([key, value]) => {
|
|
252
|
+
const next = prefix ? `${prefix}.${key}` : key;
|
|
253
|
+
const nested = collectKeys(value, next);
|
|
254
|
+
return nested.length ? nested : [next];
|
|
255
|
+
});
|
|
256
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const machinesSmoke: z.ZodObject<{
|
|
3
|
+
pageOrder: z.ZodNumber;
|
|
4
|
+
modelId: z.ZodString;
|
|
5
|
+
machineName: z.ZodString;
|
|
6
|
+
cutRate: z.ZodNumber;
|
|
7
|
+
featurePrimary: z.ZodOptional<z.ZodString>;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
export declare const example: z.ZodObject<{
|
|
10
|
+
slug: z.ZodString;
|
|
11
|
+
pageOrder: z.ZodCoercedNumber<unknown>;
|
|
12
|
+
number: z.ZodCoercedNumber<unknown>;
|
|
13
|
+
string: z.ZodString;
|
|
14
|
+
enumString: z.ZodEnum<{
|
|
15
|
+
foo: "foo";
|
|
16
|
+
bar: "bar";
|
|
17
|
+
baz: "baz";
|
|
18
|
+
}>;
|
|
19
|
+
literalString: z.ZodLiteral<"foo">;
|
|
20
|
+
unionString: z.ZodUnion<readonly [z.ZodLiteral<"foo">, z.ZodLiteral<"bar">]>;
|
|
21
|
+
unionStringArray: z.ZodArray<z.ZodUnion<readonly [z.ZodLiteral<"foo">, z.ZodLiteral<"bar">]>>;
|
|
22
|
+
exclusiveUnionString: z.ZodXor<readonly [z.ZodLiteral<"foo">, z.ZodLiteral<"bar">]>;
|
|
23
|
+
stringArray: z.ZodArray<z.ZodString>;
|
|
24
|
+
boolean: z.ZodCoercedBoolean<unknown>;
|
|
25
|
+
object: z.ZodObject<{
|
|
26
|
+
key1: z.ZodString;
|
|
27
|
+
key2: z.ZodString;
|
|
28
|
+
}, z.core.$strip>;
|
|
29
|
+
objectArray: z.ZodArray<z.ZodObject<{
|
|
30
|
+
keyA: z.ZodString;
|
|
31
|
+
keyB: z.ZodString;
|
|
32
|
+
}, z.core.$strip>>;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
export declare const schemas: {
|
|
35
|
+
machinesSmoke: z.ZodObject<{
|
|
36
|
+
pageOrder: z.ZodNumber;
|
|
37
|
+
modelId: z.ZodString;
|
|
38
|
+
machineName: z.ZodString;
|
|
39
|
+
cutRate: z.ZodNumber;
|
|
40
|
+
featurePrimary: z.ZodOptional<z.ZodString>;
|
|
41
|
+
}, z.core.$strip>;
|
|
42
|
+
example: z.ZodObject<{
|
|
43
|
+
slug: z.ZodString;
|
|
44
|
+
pageOrder: z.ZodCoercedNumber<unknown>;
|
|
45
|
+
number: z.ZodCoercedNumber<unknown>;
|
|
46
|
+
string: z.ZodString;
|
|
47
|
+
enumString: z.ZodEnum<{
|
|
48
|
+
foo: "foo";
|
|
49
|
+
bar: "bar";
|
|
50
|
+
baz: "baz";
|
|
51
|
+
}>;
|
|
52
|
+
literalString: z.ZodLiteral<"foo">;
|
|
53
|
+
unionString: z.ZodUnion<readonly [z.ZodLiteral<"foo">, z.ZodLiteral<"bar">]>;
|
|
54
|
+
unionStringArray: z.ZodArray<z.ZodUnion<readonly [z.ZodLiteral<"foo">, z.ZodLiteral<"bar">]>>;
|
|
55
|
+
exclusiveUnionString: z.ZodXor<readonly [z.ZodLiteral<"foo">, z.ZodLiteral<"bar">]>;
|
|
56
|
+
stringArray: z.ZodArray<z.ZodString>;
|
|
57
|
+
boolean: z.ZodCoercedBoolean<unknown>;
|
|
58
|
+
object: z.ZodObject<{
|
|
59
|
+
key1: z.ZodString;
|
|
60
|
+
key2: z.ZodString;
|
|
61
|
+
}, z.core.$strip>;
|
|
62
|
+
objectArray: z.ZodArray<z.ZodObject<{
|
|
63
|
+
keyA: z.ZodString;
|
|
64
|
+
keyB: z.ZodString;
|
|
65
|
+
}, z.core.$strip>>;
|
|
66
|
+
}, z.core.$strip>;
|
|
67
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const machinesSmoke = z.object({
|
|
3
|
+
pageOrder: z.number(),
|
|
4
|
+
modelId: z.string().min(1),
|
|
5
|
+
machineName: z.string().min(1),
|
|
6
|
+
cutRate: z.number(),
|
|
7
|
+
featurePrimary: z.string().optional()
|
|
8
|
+
});
|
|
9
|
+
export const example = z.object({
|
|
10
|
+
slug: z.string().min(1).max(100),
|
|
11
|
+
pageOrder: z.coerce.number().int(),
|
|
12
|
+
number: z.coerce.number(),
|
|
13
|
+
string: z.string().min(1).max(100),
|
|
14
|
+
enumString: z.enum(["foo", "bar", "baz"]),
|
|
15
|
+
literalString: z.literal("foo"),
|
|
16
|
+
unionString: z.union([z.literal("foo"), z.literal("bar")]),
|
|
17
|
+
unionStringArray: z.union([z.literal("foo"), z.literal("bar")]).array(),
|
|
18
|
+
exclusiveUnionString: z.xor([z.literal("foo"), z.literal("bar")]),
|
|
19
|
+
stringArray: z.string().array(),
|
|
20
|
+
boolean: z.coerce.boolean(),
|
|
21
|
+
object: z.object({
|
|
22
|
+
key1: z.string().min(1),
|
|
23
|
+
key2: z.string().min(1)
|
|
24
|
+
}),
|
|
25
|
+
objectArray: z.array(
|
|
26
|
+
z.object({
|
|
27
|
+
keyA: z.string().min(1),
|
|
28
|
+
keyB: z.string().min(1)
|
|
29
|
+
})
|
|
30
|
+
)
|
|
31
|
+
});
|
|
32
|
+
export const schemas = {
|
|
33
|
+
machinesSmoke,
|
|
34
|
+
example
|
|
35
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref } from "vue";
|
|
3
|
+
const sheetsList = ref([
|
|
4
|
+
{ id: "1NKS0cTX6u5urtgQ3Q4Z2motiR2-9JmyPxcd05yVc1bc", label: "Metzner" },
|
|
5
|
+
{ id: "1tGZCEoiikXfg3mOpfVWWTS1SSSsj18xv6Z3owrnnt4s", label: "Example Sheet" }
|
|
6
|
+
]);
|
|
7
|
+
</script>
|
|
8
|
+
|
|
9
|
+
<template>
|
|
10
|
+
<UContainer>
|
|
11
|
+
<GoogleSheetsImportSchemaGuide />
|
|
12
|
+
<GoogleSheetsImportSource :google-sheets="sheetsList" />
|
|
13
|
+
</UContainer>
|
|
14
|
+
</template>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { resolveCollectionTypeBySchema } from "../utils/collectionType.js";
|
|
3
|
+
import { defineEventHandler, getValidatedQuery } from "h3";
|
|
4
|
+
import { useRuntimeConfig } from "#imports";
|
|
5
|
+
const querySchema = z.object({
|
|
6
|
+
schema: z.string().optional()
|
|
7
|
+
});
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
const { schema } = await getValidatedQuery(event, (query) => querySchema.parse(query));
|
|
10
|
+
const config = useRuntimeConfig(event);
|
|
11
|
+
const moduleConfig = config.googleSheetsImport;
|
|
12
|
+
const fromConfig = moduleConfig.collectionTypeBySchema ?? {};
|
|
13
|
+
const collectionType = await resolveCollectionTypeBySchema(schema, fromConfig);
|
|
14
|
+
const baseContentDir = collectionType === "page" ? "content" : collectionType === "data" ? "content/data" : moduleConfig.defaultContentDir;
|
|
15
|
+
return {
|
|
16
|
+
schema: schema ?? null,
|
|
17
|
+
collectionType,
|
|
18
|
+
baseContentDir
|
|
19
|
+
};
|
|
20
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
|
|
2
|
+
schema: null;
|
|
3
|
+
schemas: string[];
|
|
4
|
+
columns: never[];
|
|
5
|
+
collectionType: string;
|
|
6
|
+
pageOverrideColumns: never[];
|
|
7
|
+
} | {
|
|
8
|
+
schema: string;
|
|
9
|
+
schemas: string[];
|
|
10
|
+
columns: string[];
|
|
11
|
+
collectionType: "unknown" | ("page" | "data");
|
|
12
|
+
pageOverrideColumns: string[];
|
|
13
|
+
}>>;
|
|
14
|
+
export default _default;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { resolveCollectionTypeBySchema } from "../utils/collectionType.js";
|
|
3
|
+
import { getSchemaColumns, PAGE_SCHEMA_OVERRIDE_COLUMNS } from "../utils/schemaColumns.js";
|
|
4
|
+
import { getValidatedQuery, defineEventHandler, createError } from "h3";
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
6
|
+
const querySchema = z.object({
|
|
7
|
+
schema: z.string().optional()
|
|
8
|
+
});
|
|
9
|
+
export default defineEventHandler(async (event) => {
|
|
10
|
+
const { schema } = await getValidatedQuery(event, (query) => querySchema.parse(query));
|
|
11
|
+
const config = useRuntimeConfig(event);
|
|
12
|
+
const moduleConfig = config.googleSheetsImport;
|
|
13
|
+
const schemaMap = {};
|
|
14
|
+
const availableSchemas = Object.keys(schemaMap).sort((left, right) => left.localeCompare(right));
|
|
15
|
+
if (!schema) {
|
|
16
|
+
return {
|
|
17
|
+
schema: null,
|
|
18
|
+
schemas: availableSchemas,
|
|
19
|
+
columns: [],
|
|
20
|
+
collectionType: "unknown",
|
|
21
|
+
pageOverrideColumns: []
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const collectionType = await resolveCollectionTypeBySchema(schema, moduleConfig.collectionTypeBySchema ?? {});
|
|
25
|
+
const selectedSchema = schemaMap[schema];
|
|
26
|
+
if (!selectedSchema) {
|
|
27
|
+
throw createError({
|
|
28
|
+
statusCode: 400,
|
|
29
|
+
statusMessage: `Unknown schema: ${schema}`
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
schema,
|
|
34
|
+
schemas: availableSchemas,
|
|
35
|
+
columns: getSchemaColumns(selectedSchema),
|
|
36
|
+
collectionType,
|
|
37
|
+
pageOverrideColumns: collectionType === "page" ? PAGE_SCHEMA_OVERRIDE_COLUMNS : []
|
|
38
|
+
};
|
|
39
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { columnCountToRange } from "../utils/googleSheets.js";
|
|
3
|
+
import { getValidatedQuery, defineEventHandler, createError } from "h3";
|
|
4
|
+
import { useRuntimeConfig } from "#imports";
|
|
5
|
+
const querySchema = z.object({
|
|
6
|
+
spreadsheetId: z.string().length(44)
|
|
7
|
+
});
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
const { googleSheetsImport } = useRuntimeConfig();
|
|
10
|
+
const apiKey = googleSheetsImport?.googleApiKeyRuntimeKey;
|
|
11
|
+
const { spreadsheetId } = await getValidatedQuery(event, (query) => querySchema.parse(query));
|
|
12
|
+
if (!apiKey || typeof apiKey !== "string") {
|
|
13
|
+
throw createError({ statusCode: 500, statusMessage: `Missing Google API key in nuxt.config googleSheetsImport: { googleApiKeyRuntimeKey: '${apiKey}' }` });
|
|
14
|
+
}
|
|
15
|
+
const response = await $fetch(`https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}?key=${apiKey}`);
|
|
16
|
+
return {
|
|
17
|
+
spreadsheetId,
|
|
18
|
+
sheets: (response.sheets ?? []).map((sheet) => {
|
|
19
|
+
const columnCount = sheet.properties?.gridProperties?.columnCount ?? 1;
|
|
20
|
+
return {
|
|
21
|
+
label: sheet.properties?.title ?? "",
|
|
22
|
+
range: columnCountToRange(columnCount)
|
|
23
|
+
};
|
|
24
|
+
}).filter((sheet) => sheet.label)
|
|
25
|
+
};
|
|
26
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { transformAndValidateRows } from "../utils/transform.js";
|
|
3
|
+
import { readBody, defineEventHandler, createError } from "h3";
|
|
4
|
+
import { useRuntimeConfig } from "#imports";
|
|
5
|
+
import { schemas } from "../../import/schemas.js";
|
|
6
|
+
const bodySchema = z.object({
|
|
7
|
+
spreadsheetId: z.string().length(44),
|
|
8
|
+
sheetTitle: z.string().min(1),
|
|
9
|
+
range: z.string().min(1),
|
|
10
|
+
schema: z.string().min(1)
|
|
11
|
+
});
|
|
12
|
+
export default defineEventHandler(async (event) => {
|
|
13
|
+
const body = bodySchema.parse(await readBody(event));
|
|
14
|
+
const { googleSheetsImport } = useRuntimeConfig();
|
|
15
|
+
const apiKey = googleSheetsImport?.googleApiKeyRuntimeKey;
|
|
16
|
+
if (!apiKey || typeof apiKey !== "string") {
|
|
17
|
+
throw createError({ statusCode: 500, statusMessage: `Missing Google API key in nuxt.config googleSheetsImport: { googleApiKeyRuntimeKey: '${apiKey}' }` });
|
|
18
|
+
}
|
|
19
|
+
const encodedRange = encodeURIComponent(`${body.sheetTitle}!${body.range}`);
|
|
20
|
+
const googleResponse = await $fetch(`https://sheets.googleapis.com/v4/spreadsheets/${body.spreadsheetId}/values/${encodedRange}?key=${apiKey}`);
|
|
21
|
+
const values = googleResponse.values ?? [];
|
|
22
|
+
const schemaMap = schemas;
|
|
23
|
+
const schema = schemaMap[body.schema];
|
|
24
|
+
if (!schema) {
|
|
25
|
+
throw createError({ statusCode: 400, statusMessage: `Unknown schema: ${body.schema}` });
|
|
26
|
+
}
|
|
27
|
+
const transformed = transformAndValidateRows(values, schema);
|
|
28
|
+
return {
|
|
29
|
+
headers: values[0] ?? [],
|
|
30
|
+
records: transformed.records,
|
|
31
|
+
errors: transformed.errors
|
|
32
|
+
};
|
|
33
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
|
|
2
|
+
count: number;
|
|
3
|
+
logs: string[];
|
|
4
|
+
contentDir: string;
|
|
5
|
+
summary: {
|
|
6
|
+
written: number;
|
|
7
|
+
overwritten: number;
|
|
8
|
+
skipped: number;
|
|
9
|
+
};
|
|
10
|
+
}>>;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { writeRecordAsFrontmatter } from "../utils/writeFrontmatter.js";
|
|
3
|
+
import { resolveCollectionTypeBySchema } from "../utils/collectionType.js";
|
|
4
|
+
import { defineEventHandler, readBody } from "h3";
|
|
5
|
+
import { useRuntimeConfig } from "#imports";
|
|
6
|
+
const bodySchema = z.object({
|
|
7
|
+
records: z.array(z.record(z.string(), z.unknown())),
|
|
8
|
+
schema: z.string().optional(),
|
|
9
|
+
folder: z.string().min(1),
|
|
10
|
+
slugKey: z.string().min(1),
|
|
11
|
+
orderKey: z.string().optional(),
|
|
12
|
+
contentDir: z.string().optional(),
|
|
13
|
+
outputFormat: z.enum(["frontmatter", "json", "yaml"]).optional().default("frontmatter"),
|
|
14
|
+
overwriteMode: z.enum(["skip", "overwrite", "overwrite-frontmatter"]).optional().default("overwrite")
|
|
15
|
+
});
|
|
16
|
+
function resolveContentDirByCollectionType(collectionType, schemaKey, fallback) {
|
|
17
|
+
if (collectionType === "page") {
|
|
18
|
+
return "content";
|
|
19
|
+
}
|
|
20
|
+
if (collectionType === "data") {
|
|
21
|
+
return "content/data";
|
|
22
|
+
}
|
|
23
|
+
if (!schemaKey) {
|
|
24
|
+
return fallback;
|
|
25
|
+
}
|
|
26
|
+
const normalizedSchemaKey = schemaKey.trim();
|
|
27
|
+
console.warn(
|
|
28
|
+
`[google-sheets-import] No collection type mapping found for schema "${normalizedSchemaKey}". Using fallback content directory "${fallback}". Add googleSheetsImport.collectionTypeBySchema["` + normalizedSchemaKey + '"] in nuxt.config.ts to route writes automatically.'
|
|
29
|
+
);
|
|
30
|
+
return fallback;
|
|
31
|
+
}
|
|
32
|
+
export default defineEventHandler(async (event) => {
|
|
33
|
+
const body = bodySchema.parse(await readBody(event));
|
|
34
|
+
const config = useRuntimeConfig(event);
|
|
35
|
+
const moduleConfig = config.googleSheetsImport;
|
|
36
|
+
const mappedInConfig = moduleConfig.collectionTypeBySchema ?? {};
|
|
37
|
+
const resolvedCollectionType = await resolveCollectionTypeBySchema(body.schema, mappedInConfig);
|
|
38
|
+
const resolvedContentDir = body.contentDir ?? resolveContentDirByCollectionType(resolvedCollectionType, body.schema, moduleConfig.defaultContentDir);
|
|
39
|
+
const logs = [];
|
|
40
|
+
const summary = {
|
|
41
|
+
written: 0,
|
|
42
|
+
overwritten: 0,
|
|
43
|
+
skipped: 0
|
|
44
|
+
};
|
|
45
|
+
for (const record of body.records) {
|
|
46
|
+
const result = await writeRecordAsFrontmatter({
|
|
47
|
+
record,
|
|
48
|
+
baseContentDir: resolvedContentDir,
|
|
49
|
+
folder: body.folder,
|
|
50
|
+
slugKey: body.slugKey,
|
|
51
|
+
orderKey: body.orderKey,
|
|
52
|
+
outputFormat: body.outputFormat,
|
|
53
|
+
overwriteMode: body.overwriteMode
|
|
54
|
+
});
|
|
55
|
+
logs.push(result.filePath);
|
|
56
|
+
summary[result.action]++;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
count: logs.length,
|
|
60
|
+
logs,
|
|
61
|
+
contentDir: resolvedContentDir,
|
|
62
|
+
summary
|
|
63
|
+
};
|
|
64
|
+
});
|