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.
Files changed (36) hide show
  1. package/README.md +113 -84
  2. package/dist/module.d.mts +26 -22
  3. package/dist/module.json +1 -1
  4. package/dist/module.mjs +52 -9
  5. package/dist/runtime/composables/useRelationDisplay.d.ts +12 -0
  6. package/dist/runtime/composables/useRelationDisplay.js +48 -0
  7. package/dist/runtime/composables/useResourceSchemas.d.ts +19 -0
  8. package/dist/runtime/composables/useResourceSchemas.js +17 -0
  9. package/dist/runtime/server/api/[model]/[id].patch.js +3 -0
  10. package/dist/runtime/server/api/[model]/index.get.js +2 -1
  11. package/dist/runtime/server/api/_relations.get.d.ts +2 -0
  12. package/dist/runtime/server/api/_relations.get.js +24 -0
  13. package/dist/runtime/server/api/_schema/[table].get.d.ts +10 -0
  14. package/dist/runtime/server/api/_schema/[table].get.js +32 -0
  15. package/dist/runtime/server/api/_schema/index.get.d.ts +2 -0
  16. package/dist/runtime/server/api/_schema/index.get.js +24 -0
  17. package/dist/runtime/server/plugins/seed.d.ts +2 -0
  18. package/dist/runtime/server/plugins/seed.js +36 -0
  19. package/dist/runtime/server/utils/auth.js +1 -1
  20. package/dist/runtime/server/utils/config.d.ts +2 -2
  21. package/dist/runtime/server/utils/modelMapper.js +16 -8
  22. package/dist/runtime/server/utils/schema.d.ts +20 -0
  23. package/dist/runtime/server/utils/schema.js +70 -0
  24. package/package.json +8 -5
  25. package/src/runtime/composables/useRelationDisplay.ts +67 -0
  26. package/src/runtime/composables/useResourceSchemas.ts +42 -0
  27. package/src/runtime/server/api/[model]/[id].patch.ts +5 -0
  28. package/src/runtime/server/api/[model]/index.get.ts +5 -2
  29. package/src/runtime/server/api/_relations.get.ts +31 -0
  30. package/src/runtime/server/api/_schema/[table].get.ts +41 -0
  31. package/src/runtime/server/api/_schema/index.get.ts +31 -0
  32. package/src/runtime/server/plugins/seed.ts +55 -0
  33. package/src/runtime/server/utils/auth.ts +1 -1
  34. package/src/runtime/server/utils/config.ts +3 -3
  35. package/src/runtime/server/utils/modelMapper.ts +25 -11
  36. 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,2 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<Record<string, any>>>;
2
+ export default _default;
@@ -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,2 @@
1
+ declare const _default: any;
2
+ export default _default;
@@ -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?.enabled) {
6
+ if (!auth?.authentication) {
7
7
  return true;
8
8
  }
9
9
  if (auth.type === "jwt") {
@@ -1,2 +1,2 @@
1
- import type { ModuleOptions } from '../../../types.js';
2
- export declare const useAutoCrudConfig: () => ModuleOptions;
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", "created_at"];
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
- const hasTableSymbol = Symbol.for("drizzle:Name") in value;
21
- const hasUnderscore = "_" in value;
22
- const hasTableConfig = "table" in value || "$inferSelect" in value;
23
- if (hasTableSymbol || hasUnderscore || hasTableConfig) {
24
- tableMap[key] = value;
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
- filtered[field] = data[field];
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.4.0",
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-fullstack",
39
- "dev:build": "nuxi build playground-fullstack",
40
- "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground-fullstack",
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-fullstack && vue-tsc --noEmit",
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?.enabled) {
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 { ModuleOptions } from '../../../types'
2
+ import type { RuntimeModuleOptions } from '../../../types'
3
3
 
4
- export const useAutoCrudConfig = (): ModuleOptions => {
5
- return useRuntimeConfig().autoCrud as ModuleOptions
4
+ export const useAutoCrudConfig = (): RuntimeModuleOptions => {
5
+ return useRuntimeConfig().autoCrud as RuntimeModuleOptions
6
6
  }