nuxt-auto-crud 1.4.0 → 1.6.0
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 +113 -84
- package/dist/module.d.mts +26 -22
- package/dist/module.json +1 -1
- package/dist/module.mjs +52 -9
- package/dist/runtime/composables/useRelationDisplay.d.ts +12 -0
- package/dist/runtime/composables/useRelationDisplay.js +48 -0
- package/dist/runtime/composables/useResourceSchemas.d.ts +19 -0
- package/dist/runtime/composables/useResourceSchemas.js +17 -0
- package/dist/runtime/server/api/[model]/[id].patch.js +3 -0
- package/dist/runtime/server/api/[model]/index.get.js +2 -1
- package/dist/runtime/server/api/_relations.get.d.ts +2 -0
- package/dist/runtime/server/api/_relations.get.js +24 -0
- package/dist/runtime/server/api/_schema/[table].get.d.ts +10 -0
- package/dist/runtime/server/api/_schema/[table].get.js +32 -0
- package/dist/runtime/server/api/_schema/index.get.d.ts +2 -0
- package/dist/runtime/server/api/_schema/index.get.js +24 -0
- package/dist/runtime/server/plugins/seed.d.ts +2 -0
- package/dist/runtime/server/plugins/seed.js +36 -0
- package/dist/runtime/server/utils/auth.js +1 -1
- package/dist/runtime/server/utils/config.d.ts +2 -2
- package/dist/runtime/server/utils/modelMapper.js +16 -8
- package/dist/runtime/server/utils/schema.d.ts +20 -0
- package/dist/runtime/server/utils/schema.js +70 -0
- package/package.json +8 -5
- package/src/runtime/composables/useRelationDisplay.ts +67 -0
- package/src/runtime/composables/useResourceSchemas.ts +42 -0
- package/src/runtime/server/api/[model]/[id].patch.ts +5 -0
- package/src/runtime/server/api/[model]/index.get.ts +5 -2
- package/src/runtime/server/api/_relations.get.ts +31 -0
- package/src/runtime/server/api/_schema/[table].get.ts +41 -0
- package/src/runtime/server/api/_schema/index.get.ts +31 -0
- package/src/runtime/server/plugins/seed.ts +55 -0
- package/src/runtime/server/utils/auth.ts +1 -1
- package/src/runtime/server/utils/config.ts +3 -3
- package/src/runtime/server/utils/modelMapper.ts +25 -11
- package/src/runtime/server/utils/schema.ts +96 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { eventHandler, createError, getRouterParam } from "h3";
|
|
2
|
+
import { getSchema } from "../../utils/schema.js";
|
|
3
|
+
import { useAutoCrudConfig } from "../../utils/config.js";
|
|
4
|
+
import { verifyJwtToken } from "../../utils/jwt.js";
|
|
5
|
+
export default eventHandler(async (event) => {
|
|
6
|
+
const { auth } = useAutoCrudConfig();
|
|
7
|
+
const tableName = getRouterParam(event, "table");
|
|
8
|
+
if (auth?.authentication) {
|
|
9
|
+
let isAuthenticated = false;
|
|
10
|
+
if (auth.type === "jwt" && auth.jwtSecret) {
|
|
11
|
+
isAuthenticated = await verifyJwtToken(event, auth.jwtSecret);
|
|
12
|
+
} else {
|
|
13
|
+
try {
|
|
14
|
+
await requireUserSession(event);
|
|
15
|
+
isAuthenticated = true;
|
|
16
|
+
} catch {
|
|
17
|
+
isAuthenticated = false;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
if (!isAuthenticated) {
|
|
21
|
+
throw createError({ statusCode: 401, message: "Unauthorized" });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (!tableName) {
|
|
25
|
+
throw createError({ statusCode: 400, message: "Table name is required" });
|
|
26
|
+
}
|
|
27
|
+
const schema = await getSchema(tableName);
|
|
28
|
+
if (!schema) {
|
|
29
|
+
throw createError({ statusCode: 404, message: `Table '${tableName}' not found` });
|
|
30
|
+
}
|
|
31
|
+
return schema;
|
|
32
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { eventHandler, createError } from "h3";
|
|
2
|
+
import { getAllSchemas } from "../../utils/schema.js";
|
|
3
|
+
import { useAutoCrudConfig } from "../../utils/config.js";
|
|
4
|
+
import { verifyJwtToken } from "../../utils/jwt.js";
|
|
5
|
+
export default eventHandler(async (event) => {
|
|
6
|
+
const { auth } = useAutoCrudConfig();
|
|
7
|
+
if (auth?.authentication) {
|
|
8
|
+
let isAuthenticated = false;
|
|
9
|
+
if (auth.type === "jwt" && auth.jwtSecret) {
|
|
10
|
+
isAuthenticated = await verifyJwtToken(event, auth.jwtSecret);
|
|
11
|
+
} else {
|
|
12
|
+
try {
|
|
13
|
+
await requireUserSession(event);
|
|
14
|
+
isAuthenticated = true;
|
|
15
|
+
} catch {
|
|
16
|
+
isAuthenticated = false;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (!isAuthenticated) {
|
|
20
|
+
throw createError({ statusCode: 401, message: "Unauthorized" });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return getAllSchemas();
|
|
24
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { eq } from "drizzle-orm";
|
|
2
|
+
import { useDrizzle } from "#site/drizzle";
|
|
3
|
+
import * as tables from "#site/schema";
|
|
4
|
+
import { useAutoCrudConfig } from "../utils/config.js";
|
|
5
|
+
import { defineNitroPlugin } from "#imports";
|
|
6
|
+
export default defineNitroPlugin(async () => {
|
|
7
|
+
if (typeof onHubReady === "function") {
|
|
8
|
+
onHubReady(async () => {
|
|
9
|
+
const { auth } = useAutoCrudConfig();
|
|
10
|
+
if (!auth?.authentication || !tables.users) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const db = useDrizzle();
|
|
14
|
+
const existingAdmin = await db.select().from(tables.users).where(eq(tables.users.email, "admin@example.com")).get();
|
|
15
|
+
if (!existingAdmin) {
|
|
16
|
+
console.log("Seeding admin user...");
|
|
17
|
+
let hashedPassword = "$1Password";
|
|
18
|
+
try {
|
|
19
|
+
hashedPassword = await hashPassword("$1Password");
|
|
20
|
+
} catch {
|
|
21
|
+
console.warn("hashPassword not available, using plain text (insecure)");
|
|
22
|
+
}
|
|
23
|
+
await db.insert(tables.users).values({
|
|
24
|
+
email: "admin@example.com",
|
|
25
|
+
password: hashedPassword,
|
|
26
|
+
name: "Admin User",
|
|
27
|
+
avatar: "https://i.pravatar.cc/150?u=admin",
|
|
28
|
+
role: "admin",
|
|
29
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
30
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
31
|
+
});
|
|
32
|
+
console.log("Admin user seeded.");
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
@@ -3,7 +3,7 @@ import { useAutoCrudConfig } from "./config.js";
|
|
|
3
3
|
import { verifyJwtToken } from "./jwt.js";
|
|
4
4
|
export async function checkAdminAccess(event, model, action) {
|
|
5
5
|
const { auth } = useAutoCrudConfig();
|
|
6
|
-
if (!auth?.
|
|
6
|
+
if (!auth?.authentication) {
|
|
7
7
|
return true;
|
|
8
8
|
}
|
|
9
9
|
if (auth.type === "jwt") {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare const useAutoCrudConfig: () =>
|
|
1
|
+
import type { RuntimeModuleOptions } from '../../../types.js';
|
|
2
|
+
export declare const useAutoCrudConfig: () => RuntimeModuleOptions;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as schema from "#site/schema";
|
|
2
2
|
import pluralize from "pluralize";
|
|
3
3
|
import { pascalCase } from "scule";
|
|
4
|
-
import { getTableColumns as getDrizzleTableColumns } from "drizzle-orm";
|
|
4
|
+
import { getTableColumns as getDrizzleTableColumns, getTableName } from "drizzle-orm";
|
|
5
5
|
import { createError } from "h3";
|
|
6
6
|
import { useRuntimeConfig } from "#imports";
|
|
7
|
-
const PROTECTED_FIELDS = ["id", "createdAt", "
|
|
7
|
+
const PROTECTED_FIELDS = ["id", "created_at", "updated_at", "createdAt", "updatedAt"];
|
|
8
8
|
const HIDDEN_FIELDS = ["password", "secret", "token"];
|
|
9
9
|
export const customUpdatableFields = {
|
|
10
10
|
// Add custom field restrictions here if needed
|
|
@@ -17,11 +17,12 @@ function buildModelTableMap() {
|
|
|
17
17
|
const tableMap = {};
|
|
18
18
|
for (const [key, value] of Object.entries(schema)) {
|
|
19
19
|
if (value && typeof value === "object") {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
try {
|
|
21
|
+
const tableName = getTableName(value);
|
|
22
|
+
if (tableName) {
|
|
23
|
+
tableMap[key] = value;
|
|
24
|
+
}
|
|
25
|
+
} catch {
|
|
25
26
|
}
|
|
26
27
|
}
|
|
27
28
|
}
|
|
@@ -60,9 +61,16 @@ export function getUpdatableFields(modelName) {
|
|
|
60
61
|
export function filterUpdatableFields(modelName, data) {
|
|
61
62
|
const allowedFields = getUpdatableFields(modelName);
|
|
62
63
|
const filtered = {};
|
|
64
|
+
const table = modelTableMap[modelName];
|
|
65
|
+
const columns = table ? getDrizzleTableColumns(table) : {};
|
|
63
66
|
for (const field of allowedFields) {
|
|
64
67
|
if (data[field] !== void 0) {
|
|
65
|
-
|
|
68
|
+
let value = data[field];
|
|
69
|
+
const column = columns[field];
|
|
70
|
+
if (column && column.mode === "timestamp" && typeof value === "string") {
|
|
71
|
+
value = new Date(value);
|
|
72
|
+
}
|
|
73
|
+
filtered[field] = value;
|
|
66
74
|
}
|
|
67
75
|
}
|
|
68
76
|
return filtered;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare function drizzleTableToFields(table: any, resourceName: string): {
|
|
2
|
+
resource: string;
|
|
3
|
+
fields: {
|
|
4
|
+
name: string;
|
|
5
|
+
type: string;
|
|
6
|
+
required: any;
|
|
7
|
+
selectOptions: undefined;
|
|
8
|
+
}[];
|
|
9
|
+
};
|
|
10
|
+
export declare function getRelations(): Promise<Record<string, Record<string, string>>>;
|
|
11
|
+
export declare function getAllSchemas(): Promise<Record<string, any>>;
|
|
12
|
+
export declare function getSchema(tableName: string): Promise<{
|
|
13
|
+
resource: string;
|
|
14
|
+
fields: {
|
|
15
|
+
name: string;
|
|
16
|
+
type: string;
|
|
17
|
+
required: any;
|
|
18
|
+
selectOptions: undefined;
|
|
19
|
+
}[];
|
|
20
|
+
} | undefined>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { getTableColumns } from "drizzle-orm";
|
|
2
|
+
import { getTableConfig } from "drizzle-orm/sqlite-core";
|
|
3
|
+
import { modelTableMap } from "./modelMapper.js";
|
|
4
|
+
export function drizzleTableToFields(table, resourceName) {
|
|
5
|
+
const columns = getTableColumns(table);
|
|
6
|
+
const fields = [];
|
|
7
|
+
for (const [key, col] of Object.entries(columns)) {
|
|
8
|
+
const column = col;
|
|
9
|
+
const isRequired = column.notNull;
|
|
10
|
+
let type = "string";
|
|
11
|
+
const selectOptions = void 0;
|
|
12
|
+
if (column.dataType === "number" || column.columnType === "SQLiteInteger" || column.columnType === "SQLiteReal") {
|
|
13
|
+
type = "number";
|
|
14
|
+
if (column.name.endsWith("_at") || column.name.endsWith("At")) {
|
|
15
|
+
type = "date";
|
|
16
|
+
}
|
|
17
|
+
} else if (column.dataType === "boolean") {
|
|
18
|
+
type = "boolean";
|
|
19
|
+
} else if (column.dataType === "date" || column.dataType === "string" && (column.name.endsWith("_at") || column.name.endsWith("At"))) {
|
|
20
|
+
type = "date";
|
|
21
|
+
}
|
|
22
|
+
fields.push({
|
|
23
|
+
name: key,
|
|
24
|
+
type,
|
|
25
|
+
required: isRequired,
|
|
26
|
+
selectOptions
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
resource: resourceName,
|
|
31
|
+
fields
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export async function getRelations() {
|
|
35
|
+
const relations = {};
|
|
36
|
+
for (const [tableName, table] of Object.entries(modelTableMap)) {
|
|
37
|
+
try {
|
|
38
|
+
const config = getTableConfig(table);
|
|
39
|
+
if (config.foreignKeys.length > 0) {
|
|
40
|
+
const tableRelations = {};
|
|
41
|
+
relations[tableName] = tableRelations;
|
|
42
|
+
const columns = getTableColumns(table);
|
|
43
|
+
const columnToProperty = {};
|
|
44
|
+
for (const [key, col] of Object.entries(columns)) {
|
|
45
|
+
columnToProperty[col.name] = key;
|
|
46
|
+
}
|
|
47
|
+
config.foreignKeys.forEach((fk) => {
|
|
48
|
+
const sourceColumnName = fk.reference().columns[0].name;
|
|
49
|
+
const sourceProperty = columnToProperty[sourceColumnName] || sourceColumnName;
|
|
50
|
+
const targetTable = fk.reference().foreignTable[Symbol.for("drizzle:Name")];
|
|
51
|
+
tableRelations[sourceProperty] = targetTable;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return relations;
|
|
58
|
+
}
|
|
59
|
+
export async function getAllSchemas() {
|
|
60
|
+
const schemas = {};
|
|
61
|
+
for (const [tableName, table] of Object.entries(modelTableMap)) {
|
|
62
|
+
schemas[tableName] = drizzleTableToFields(table, tableName);
|
|
63
|
+
}
|
|
64
|
+
return schemas;
|
|
65
|
+
}
|
|
66
|
+
export async function getSchema(tableName) {
|
|
67
|
+
const table = modelTableMap[tableName];
|
|
68
|
+
if (!table) return void 0;
|
|
69
|
+
return drizzleTableToFields(table, tableName);
|
|
70
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-auto-crud",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "Exposes RESTful CRUD APIs for your Nuxt app based solely on your database migrations.",
|
|
5
5
|
"author": "Cliford Pereira",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,15 +35,15 @@
|
|
|
35
35
|
],
|
|
36
36
|
"scripts": {
|
|
37
37
|
"prepack": "nuxt-module-build build",
|
|
38
|
-
"dev": "npm run dev:prepare && nuxi dev playground
|
|
39
|
-
"dev:build": "nuxi build playground
|
|
40
|
-
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground
|
|
38
|
+
"dev": "npm run dev:prepare && nuxi dev playground",
|
|
39
|
+
"dev:build": "nuxi build playground",
|
|
40
|
+
"dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground",
|
|
41
41
|
"release": "npm run lint && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",
|
|
42
42
|
"lint": "eslint .",
|
|
43
43
|
"test": "vitest run",
|
|
44
44
|
"test:api": "node scripts/test-api.mjs",
|
|
45
45
|
"test:watch": "vitest watch",
|
|
46
|
-
"test:types": "vue-tsc --noEmit && cd playground
|
|
46
|
+
"test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit",
|
|
47
47
|
"link": "npm link"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
@@ -58,6 +58,9 @@
|
|
|
58
58
|
"drizzle-orm": "^0.30.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
|
+
"@iconify-json/heroicons": "^1.2.3",
|
|
62
|
+
"@iconify-json/lucide": "^1.2.77",
|
|
63
|
+
"@iconify-json/simple-icons": "^1.2.61",
|
|
61
64
|
"@nuxt/devtools": "^3.1.0",
|
|
62
65
|
"@nuxt/eslint-config": "^1.10.0",
|
|
63
66
|
"@nuxt/module-builder": "^1.0.2",
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { ref, useFetch, useRequestHeaders } from '#imports'
|
|
2
|
+
|
|
3
|
+
export const useRelationDisplay = (
|
|
4
|
+
schema: {
|
|
5
|
+
resource: string
|
|
6
|
+
fields: { name: string, type: string, required?: boolean }[]
|
|
7
|
+
},
|
|
8
|
+
) => {
|
|
9
|
+
const resourceName = schema.resource
|
|
10
|
+
const relationsMap = ref<Record<string, Record<string, string>>>({})
|
|
11
|
+
const displayValues = ref<Record<string, Record<string, string>>>({})
|
|
12
|
+
const headers = useRequestHeaders(['cookie'])
|
|
13
|
+
|
|
14
|
+
const fetchRelations = async () => {
|
|
15
|
+
// 1. Fetch relations metadata
|
|
16
|
+
const { data: relations } = await useFetch<Record<string, Record<string, string>>>('/api/_relations')
|
|
17
|
+
if (relations.value) {
|
|
18
|
+
relationsMap.value = relations.value
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// 2. Identify relation fields for this resource
|
|
22
|
+
const resourceRelations = relationsMap.value[resourceName] || {}
|
|
23
|
+
const relationFields = Object.keys(resourceRelations)
|
|
24
|
+
|
|
25
|
+
if (relationFields.length === 0) return
|
|
26
|
+
|
|
27
|
+
// 3. Fetch data for each relation
|
|
28
|
+
await Promise.all(
|
|
29
|
+
relationFields.map(async (fieldName) => {
|
|
30
|
+
const targetTable = resourceRelations[fieldName]
|
|
31
|
+
// We assume the API for targetTable is /api/[targetTable]
|
|
32
|
+
try {
|
|
33
|
+
const relatedData = await $fetch<Record<string, unknown>[]>(`/api/${targetTable}`, { headers })
|
|
34
|
+
|
|
35
|
+
if (relatedData) {
|
|
36
|
+
displayValues.value[fieldName] = relatedData.reduce<Record<string, string>>(
|
|
37
|
+
(acc, item) => {
|
|
38
|
+
const id = item.id as number
|
|
39
|
+
// Try to find a good display name
|
|
40
|
+
const label = (item.name || item.title || item.email || item.username || `#${item.id}`) as string
|
|
41
|
+
acc[id] = label
|
|
42
|
+
return acc
|
|
43
|
+
},
|
|
44
|
+
{},
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error(`Failed to fetch relation data for ${targetTable}:`, error)
|
|
50
|
+
}
|
|
51
|
+
}),
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const getDisplayValue = (key: string, value: unknown) => {
|
|
56
|
+
if (displayValues.value[key] && (typeof value === 'number' || typeof value === 'string')) {
|
|
57
|
+
return displayValues.value[key][value as string] || value
|
|
58
|
+
}
|
|
59
|
+
return value
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
fetchRelations,
|
|
64
|
+
getDisplayValue,
|
|
65
|
+
relationsMap,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { useAsyncData, useRequestHeaders } from '#imports'
|
|
2
|
+
import type { Ref } from 'vue'
|
|
3
|
+
|
|
4
|
+
export interface ResourceField {
|
|
5
|
+
name: string
|
|
6
|
+
type: string
|
|
7
|
+
required?: boolean
|
|
8
|
+
selectOptions?: string[]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ResourceSchema {
|
|
12
|
+
resource: string
|
|
13
|
+
fields: ResourceField[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ResourceSchemas = Record<string, ResourceSchema>
|
|
17
|
+
|
|
18
|
+
export const useResourceSchemas = async (): Promise<{
|
|
19
|
+
schemas: Ref<ResourceSchemas | null | undefined>
|
|
20
|
+
getSchema: (resource: string) => ResourceSchema | undefined
|
|
21
|
+
status: Ref<'idle' | 'pending' | 'success' | 'error'>
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
23
|
+
error: Ref<any>
|
|
24
|
+
refresh: () => Promise<void>
|
|
25
|
+
}> => {
|
|
26
|
+
const { data: schemas, status, error, refresh } = await useAsyncData<ResourceSchemas>('resource-schemas', () => $fetch('/api/_schema', {
|
|
27
|
+
headers: useRequestHeaders(['cookie']),
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
const getSchema = (resource: string) => {
|
|
31
|
+
if (!schemas.value) return undefined
|
|
32
|
+
return schemas.value[resource]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
schemas,
|
|
37
|
+
getSchema,
|
|
38
|
+
status,
|
|
39
|
+
error,
|
|
40
|
+
refresh: refresh as unknown as () => Promise<void>,
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -34,6 +34,11 @@ export default eventHandler(async (event) => {
|
|
|
34
34
|
const body = await readBody(event)
|
|
35
35
|
const payload = filterUpdatableFields(model, body)
|
|
36
36
|
|
|
37
|
+
// Automatically update updatedAt if it exists
|
|
38
|
+
if ('updatedAt' in table) {
|
|
39
|
+
payload.updatedAt = new Date()
|
|
40
|
+
}
|
|
41
|
+
|
|
37
42
|
const updatedRecord = await useDrizzle()
|
|
38
43
|
.update(table)
|
|
39
44
|
.set(payload)
|
|
@@ -8,6 +8,9 @@ import { useDrizzle } from '#site/drizzle'
|
|
|
8
8
|
import { useAutoCrudConfig } from '../../utils/config'
|
|
9
9
|
import { checkAdminAccess } from '../../utils/auth'
|
|
10
10
|
|
|
11
|
+
import { desc } from 'drizzle-orm'
|
|
12
|
+
import type { TableWithId } from '../../types'
|
|
13
|
+
|
|
11
14
|
export default eventHandler(async (event) => {
|
|
12
15
|
console.log('[GET] Request received', event.path)
|
|
13
16
|
const { resources } = useAutoCrudConfig()
|
|
@@ -28,9 +31,9 @@ export default eventHandler(async (event) => {
|
|
|
28
31
|
}
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
const table = getTableForModel(model)
|
|
34
|
+
const table = getTableForModel(model) as TableWithId
|
|
32
35
|
|
|
33
|
-
const results = await useDrizzle().select().from(table).all()
|
|
36
|
+
const results = await useDrizzle().select().from(table).orderBy(desc(table.id)).all()
|
|
34
37
|
|
|
35
38
|
return results.map((item: Record<string, unknown>) => {
|
|
36
39
|
if (isAdmin) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { eventHandler, createError } from 'h3'
|
|
2
|
+
import { getRelations } from '../utils/schema'
|
|
3
|
+
import { useAutoCrudConfig } from '../utils/config'
|
|
4
|
+
import { verifyJwtToken } from '../utils/jwt'
|
|
5
|
+
|
|
6
|
+
export default eventHandler(async (event) => {
|
|
7
|
+
const { auth } = useAutoCrudConfig()
|
|
8
|
+
|
|
9
|
+
if (auth?.authentication) {
|
|
10
|
+
let isAuthenticated = false
|
|
11
|
+
if (auth.type === 'jwt' && auth.jwtSecret) {
|
|
12
|
+
isAuthenticated = await verifyJwtToken(event, auth.jwtSecret)
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
try {
|
|
16
|
+
// @ts-expect-error - requireUserSession is auto-imported
|
|
17
|
+
await requireUserSession(event)
|
|
18
|
+
isAuthenticated = true
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
isAuthenticated = false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!isAuthenticated) {
|
|
26
|
+
throw createError({ statusCode: 401, message: 'Unauthorized' })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return getRelations()
|
|
31
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { eventHandler, createError, getRouterParam } from 'h3'
|
|
2
|
+
import { getSchema } from '../../utils/schema'
|
|
3
|
+
import { useAutoCrudConfig } from '../../utils/config'
|
|
4
|
+
import { verifyJwtToken } from '../../utils/jwt'
|
|
5
|
+
|
|
6
|
+
export default eventHandler(async (event) => {
|
|
7
|
+
const { auth } = useAutoCrudConfig()
|
|
8
|
+
const tableName = getRouterParam(event, 'table')
|
|
9
|
+
|
|
10
|
+
if (auth?.authentication) {
|
|
11
|
+
let isAuthenticated = false
|
|
12
|
+
if (auth.type === 'jwt' && auth.jwtSecret) {
|
|
13
|
+
isAuthenticated = await verifyJwtToken(event, auth.jwtSecret)
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
try {
|
|
17
|
+
// @ts-expect-error - requireUserSession is auto-imported
|
|
18
|
+
await requireUserSession(event)
|
|
19
|
+
isAuthenticated = true
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
isAuthenticated = false
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!isAuthenticated) {
|
|
27
|
+
throw createError({ statusCode: 401, message: 'Unauthorized' })
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!tableName) {
|
|
32
|
+
throw createError({ statusCode: 400, message: 'Table name is required' })
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const schema = await getSchema(tableName)
|
|
36
|
+
if (!schema) {
|
|
37
|
+
throw createError({ statusCode: 404, message: `Table '${tableName}' not found` })
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return schema
|
|
41
|
+
})
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { eventHandler, createError } from 'h3'
|
|
2
|
+
import { getAllSchemas } from '../../utils/schema'
|
|
3
|
+
import { useAutoCrudConfig } from '../../utils/config'
|
|
4
|
+
import { verifyJwtToken } from '../../utils/jwt'
|
|
5
|
+
|
|
6
|
+
export default eventHandler(async (event) => {
|
|
7
|
+
const { auth } = useAutoCrudConfig()
|
|
8
|
+
|
|
9
|
+
if (auth?.authentication) {
|
|
10
|
+
let isAuthenticated = false
|
|
11
|
+
if (auth.type === 'jwt' && auth.jwtSecret) {
|
|
12
|
+
isAuthenticated = await verifyJwtToken(event, auth.jwtSecret)
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
try {
|
|
16
|
+
// @ts-expect-error - requireUserSession is auto-imported
|
|
17
|
+
await requireUserSession(event)
|
|
18
|
+
isAuthenticated = true
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
isAuthenticated = false
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!isAuthenticated) {
|
|
26
|
+
throw createError({ statusCode: 401, message: 'Unauthorized' })
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return getAllSchemas()
|
|
31
|
+
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { eq } from 'drizzle-orm'
|
|
2
|
+
// @ts-expect-error - #site/drizzle is an alias defined by the module
|
|
3
|
+
import { useDrizzle } from '#site/drizzle'
|
|
4
|
+
// @ts-expect-error - #site/schema is an alias defined by the module
|
|
5
|
+
import * as tables from '#site/schema'
|
|
6
|
+
import { useAutoCrudConfig } from '../utils/config'
|
|
7
|
+
// @ts-expect-error - #imports is available in runtime
|
|
8
|
+
import { defineNitroPlugin } from '#imports'
|
|
9
|
+
|
|
10
|
+
export default defineNitroPlugin(async () => {
|
|
11
|
+
// @ts-expect-error - onHubReady is auto-imported from @nuxthub/core
|
|
12
|
+
if (typeof onHubReady === 'function') {
|
|
13
|
+
// @ts-expect-error - onHubReady is auto-imported from @nuxthub/core
|
|
14
|
+
onHubReady(async () => {
|
|
15
|
+
const { auth } = useAutoCrudConfig()
|
|
16
|
+
|
|
17
|
+
// Only seed if auth is enabled and we have a users table
|
|
18
|
+
if (!auth?.authentication || !tables.users) {
|
|
19
|
+
return
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const db = useDrizzle()
|
|
23
|
+
|
|
24
|
+
// Check if admin exists
|
|
25
|
+
const existingAdmin = await db.select().from(tables.users).where(eq(tables.users.email, 'admin@example.com')).get()
|
|
26
|
+
|
|
27
|
+
if (!existingAdmin) {
|
|
28
|
+
console.log('Seeding admin user...')
|
|
29
|
+
// Use hashPassword from nuxt-auth-utils if available, otherwise simple mock or error
|
|
30
|
+
// Since we can't guarantee nuxt-auth-utils is present in module context, we try to use it dynamically or assume it's there
|
|
31
|
+
// But wait, the module depends on nuxt-auth-utils being present in the user project for auth to work.
|
|
32
|
+
|
|
33
|
+
let hashedPassword = '$1Password'
|
|
34
|
+
try {
|
|
35
|
+
// @ts-expect-error - hashPassword is auto-imported
|
|
36
|
+
hashedPassword = await hashPassword('$1Password')
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
console.warn('hashPassword not available, using plain text (insecure)')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
await db.insert(tables.users).values({
|
|
43
|
+
email: 'admin@example.com',
|
|
44
|
+
password: hashedPassword,
|
|
45
|
+
name: 'Admin User',
|
|
46
|
+
avatar: 'https://i.pravatar.cc/150?u=admin',
|
|
47
|
+
role: 'admin',
|
|
48
|
+
createdAt: new Date(),
|
|
49
|
+
updatedAt: new Date(),
|
|
50
|
+
})
|
|
51
|
+
console.log('Admin user seeded.')
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
})
|
|
@@ -6,7 +6,7 @@ import { verifyJwtToken } from './jwt'
|
|
|
6
6
|
export async function checkAdminAccess(event: H3Event, model: string, action: string): Promise<boolean> {
|
|
7
7
|
const { auth } = useAutoCrudConfig()
|
|
8
8
|
|
|
9
|
-
if (!auth?.
|
|
9
|
+
if (!auth?.authentication) {
|
|
10
10
|
return true
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useRuntimeConfig } from '#imports'
|
|
2
|
-
import type {
|
|
2
|
+
import type { RuntimeModuleOptions } from '../../../types'
|
|
3
3
|
|
|
4
|
-
export const useAutoCrudConfig = ():
|
|
5
|
-
return useRuntimeConfig().autoCrud as
|
|
4
|
+
export const useAutoCrudConfig = (): RuntimeModuleOptions => {
|
|
5
|
+
return useRuntimeConfig().autoCrud as RuntimeModuleOptions
|
|
6
6
|
}
|