sveltekit-admin 0.2.1 → 0.5.3
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/LICENSE +21 -0
- package/README.md +80 -2
- package/dist/index.d.ts +2 -7
- package/dist/index.js +2 -13
- package/dist/server/auth.d.ts +7 -0
- package/dist/server/auth.js +19 -0
- package/dist/server/data.d.ts +38 -0
- package/dist/server/data.js +116 -0
- package/dist/server/handler.d.ts +123 -4
- package/dist/server/handler.js +600 -819
- package/dist/server/introspection/parser.d.ts +19 -12
- package/dist/server/introspection/parser.js +71 -61
- package/dist/server/introspection/relations.d.ts +49 -0
- package/dist/server/introspection/relations.js +128 -0
- package/dist/server/query/filterDetection.d.ts +71 -0
- package/dist/server/query/filterDetection.js +153 -0
- package/dist/server/query/listQuery.d.ts +92 -0
- package/dist/server/query/listQuery.js +442 -0
- package/dist/server/query/urls.d.ts +33 -0
- package/dist/server/query/urls.js +58 -0
- package/dist/server/router.d.ts +6 -0
- package/dist/server/router.js +27 -0
- package/dist/server/views/Dashboard.svelte +29 -0
- package/dist/server/views/Dashboard.svelte.d.ts +15 -0
- package/dist/server/views/FieldInput.svelte +63 -0
- package/dist/server/views/FieldInput.svelte.d.ts +9 -0
- package/dist/server/views/Form.svelte +127 -0
- package/dist/server/views/Form.svelte.d.ts +12 -0
- package/dist/server/views/Layout.svelte +78 -0
- package/dist/server/views/Layout.svelte.d.ts +13 -0
- package/dist/server/views/List.svelte +240 -0
- package/dist/server/views/List.svelte.d.ts +27 -0
- package/dist/server/views/ListFilters.svelte +257 -0
- package/dist/server/views/ListFilters.svelte.d.ts +18 -0
- package/dist/server/views/ModelCard.svelte +11 -0
- package/dist/server/views/ModelCard.svelte.d.ts +8 -0
- package/dist/server/views/NotFound.svelte +7 -0
- package/dist/server/views/NotFound.svelte.d.ts +7 -0
- package/dist/server/views/RelatedBlock.svelte +74 -0
- package/dist/server/views/RelatedBlock.svelte.d.ts +11 -0
- package/dist/server/views/RelationCheckboxes.svelte +53 -0
- package/dist/server/views/RelationCheckboxes.svelte.d.ts +9 -0
- package/dist/server/views/RelationSelect.svelte +53 -0
- package/dist/server/views/RelationSelect.svelte.d.ts +12 -0
- package/dist/server/views/StatCard.svelte +18 -0
- package/dist/server/views/StatCard.svelte.d.ts +8 -0
- package/dist/server/views/html.d.ts +5 -0
- package/dist/server/views/html.js +41 -0
- package/dist/server/views/theme.d.ts +1 -0
- package/dist/server/views/theme.js +537 -0
- package/dist/server/views/types.d.ts +57 -0
- package/dist/server/views/types.js +1 -0
- package/package.json +24 -26
- package/dist/admin.d.ts +0 -227
- package/dist/admin.js +0 -369
- package/dist/components/AdminForm.svelte +0 -423
- package/dist/components/AdminForm.svelte.d.ts +0 -30
- package/dist/components/AdminLayout.svelte +0 -328
- package/dist/components/AdminLayout.svelte.d.ts +0 -20
- package/dist/components/DataTable.svelte +0 -573
- package/dist/components/DataTable.svelte.d.ts +0 -25
- package/dist/components/index.d.ts +0 -3
- package/dist/components/index.js +0 -3
- package/dist/server/auth/guard.d.ts +0 -36
- package/dist/server/auth/guard.js +0 -38
- package/dist/server/auth/index.d.ts +0 -1
- package/dist/server/auth/index.js +0 -1
- package/dist/server/crud/index.d.ts +0 -1
- package/dist/server/crud/index.js +0 -1
- package/dist/server/crud/operations.d.ts +0 -87
- package/dist/server/crud/operations.js +0 -276
- package/dist/server/introspection/index.d.ts +0 -1
- package/dist/server/introspection/index.js +0 -1
package/dist/admin.d.ts
DELETED
|
@@ -1,227 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SvelteKit Admin - Core Admin Factory
|
|
3
|
-
* Creates all the necessary handlers for the admin panel
|
|
4
|
-
*/
|
|
5
|
-
import { type PrismaSchema, type PrismaModel } from './server/introspection/parser.js';
|
|
6
|
-
export interface AdminConfig {
|
|
7
|
-
/** Prisma client instance */
|
|
8
|
-
prisma: any;
|
|
9
|
-
/** Path to Prisma schema file */
|
|
10
|
-
schemaPath?: string;
|
|
11
|
-
/** Parsed schema (alternative to schemaPath) */
|
|
12
|
-
schema?: PrismaSchema;
|
|
13
|
-
/** Base path for admin (default: /admin) */
|
|
14
|
-
basePath?: string;
|
|
15
|
-
/** Models to exclude */
|
|
16
|
-
exclude?: string[];
|
|
17
|
-
/** Per-model configuration */
|
|
18
|
-
models?: Record<string, {
|
|
19
|
-
hidden?: string[];
|
|
20
|
-
readonly?: string[];
|
|
21
|
-
listFields?: string[];
|
|
22
|
-
label?: string;
|
|
23
|
-
}>;
|
|
24
|
-
/** Branding options */
|
|
25
|
-
branding?: {
|
|
26
|
-
title?: string;
|
|
27
|
-
logo?: string;
|
|
28
|
-
primaryColor?: string;
|
|
29
|
-
};
|
|
30
|
-
/** Auth check function */
|
|
31
|
-
checkAdmin?: (user: unknown) => boolean | Promise<boolean>;
|
|
32
|
-
/** Admin role name (if using default check) */
|
|
33
|
-
adminRole?: string;
|
|
34
|
-
}
|
|
35
|
-
export interface AdminContext {
|
|
36
|
-
config: AdminConfig;
|
|
37
|
-
schema: PrismaSchema;
|
|
38
|
-
models: PrismaModel[];
|
|
39
|
-
getModel: (name: string) => PrismaModel | undefined;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* Create admin context with all necessary data
|
|
43
|
-
*/
|
|
44
|
-
export declare function createAdmin(config: AdminConfig): AdminContext;
|
|
45
|
-
/**
|
|
46
|
-
* Layout data loader - provides models list and config to layout
|
|
47
|
-
*/
|
|
48
|
-
export declare function createLayoutLoad(ctx: AdminContext): ({ locals }: {
|
|
49
|
-
locals: any;
|
|
50
|
-
}) => Promise<{
|
|
51
|
-
models: {
|
|
52
|
-
name: string;
|
|
53
|
-
label: string;
|
|
54
|
-
}[];
|
|
55
|
-
user: {
|
|
56
|
-
name: any;
|
|
57
|
-
email: any;
|
|
58
|
-
} | undefined;
|
|
59
|
-
config: {
|
|
60
|
-
basePath: string;
|
|
61
|
-
branding: {
|
|
62
|
-
title: string;
|
|
63
|
-
logo: string | undefined;
|
|
64
|
-
primaryColor: string;
|
|
65
|
-
};
|
|
66
|
-
};
|
|
67
|
-
}>;
|
|
68
|
-
/**
|
|
69
|
-
* Dashboard data loader - provides model counts
|
|
70
|
-
*/
|
|
71
|
-
export declare function createDashboardLoad(ctx: AdminContext): () => Promise<{
|
|
72
|
-
models: {
|
|
73
|
-
name: string;
|
|
74
|
-
label: string;
|
|
75
|
-
count: number;
|
|
76
|
-
}[];
|
|
77
|
-
stats: {
|
|
78
|
-
totalRecords: number;
|
|
79
|
-
modelsCount: number;
|
|
80
|
-
};
|
|
81
|
-
}>;
|
|
82
|
-
/**
|
|
83
|
-
* Model list data loader
|
|
84
|
-
*/
|
|
85
|
-
export declare function createModelListLoad(ctx: AdminContext): ({ params, url }: {
|
|
86
|
-
params: {
|
|
87
|
-
model: string;
|
|
88
|
-
};
|
|
89
|
-
url: URL;
|
|
90
|
-
}) => Promise<{
|
|
91
|
-
model: {
|
|
92
|
-
name: string;
|
|
93
|
-
label: string;
|
|
94
|
-
fields: {
|
|
95
|
-
name: string;
|
|
96
|
-
type: string;
|
|
97
|
-
label: string;
|
|
98
|
-
}[];
|
|
99
|
-
primaryKey: string;
|
|
100
|
-
};
|
|
101
|
-
items: unknown[];
|
|
102
|
-
total: number;
|
|
103
|
-
page: number;
|
|
104
|
-
perPage: number;
|
|
105
|
-
orderBy: string;
|
|
106
|
-
orderDir: "asc" | "desc";
|
|
107
|
-
search: string;
|
|
108
|
-
config: {
|
|
109
|
-
basePath: string;
|
|
110
|
-
hidden: string[];
|
|
111
|
-
listFields: string[] | undefined;
|
|
112
|
-
};
|
|
113
|
-
}>;
|
|
114
|
-
/**
|
|
115
|
-
* Model create page loader
|
|
116
|
-
*/
|
|
117
|
-
export declare function createModelNewLoad(ctx: AdminContext): ({ params }: {
|
|
118
|
-
params: {
|
|
119
|
-
model: string;
|
|
120
|
-
};
|
|
121
|
-
}) => Promise<{
|
|
122
|
-
model: {
|
|
123
|
-
name: string;
|
|
124
|
-
label: string;
|
|
125
|
-
fields: {
|
|
126
|
-
name: string;
|
|
127
|
-
type: string;
|
|
128
|
-
required: boolean;
|
|
129
|
-
label: string;
|
|
130
|
-
}[];
|
|
131
|
-
};
|
|
132
|
-
config: {
|
|
133
|
-
basePath: string;
|
|
134
|
-
hidden: string[];
|
|
135
|
-
readonly: string[];
|
|
136
|
-
};
|
|
137
|
-
relationOptions: Record<string, {
|
|
138
|
-
id: string | number;
|
|
139
|
-
label: string;
|
|
140
|
-
}[]>;
|
|
141
|
-
}>;
|
|
142
|
-
/**
|
|
143
|
-
* Model create action
|
|
144
|
-
*/
|
|
145
|
-
export declare function createModelNewAction(ctx: AdminContext): ({ params, request }: {
|
|
146
|
-
params: {
|
|
147
|
-
model: string;
|
|
148
|
-
};
|
|
149
|
-
request: Request;
|
|
150
|
-
}) => Promise<{
|
|
151
|
-
success: boolean;
|
|
152
|
-
error?: undefined;
|
|
153
|
-
fieldErrors?: undefined;
|
|
154
|
-
} | {
|
|
155
|
-
success: boolean;
|
|
156
|
-
error: any;
|
|
157
|
-
fieldErrors: {};
|
|
158
|
-
}>;
|
|
159
|
-
/**
|
|
160
|
-
* Model edit page loader
|
|
161
|
-
*/
|
|
162
|
-
export declare function createModelEditLoad(ctx: AdminContext): ({ params }: {
|
|
163
|
-
params: {
|
|
164
|
-
model: string;
|
|
165
|
-
id: string;
|
|
166
|
-
};
|
|
167
|
-
}) => Promise<{
|
|
168
|
-
model: {
|
|
169
|
-
name: string;
|
|
170
|
-
label: string;
|
|
171
|
-
primaryKey: string;
|
|
172
|
-
fields: {
|
|
173
|
-
name: string;
|
|
174
|
-
type: string;
|
|
175
|
-
required: boolean;
|
|
176
|
-
label: string;
|
|
177
|
-
}[];
|
|
178
|
-
};
|
|
179
|
-
item: {};
|
|
180
|
-
config: {
|
|
181
|
-
basePath: string;
|
|
182
|
-
hidden: string[];
|
|
183
|
-
readonly: string[];
|
|
184
|
-
};
|
|
185
|
-
relationOptions: Record<string, {
|
|
186
|
-
id: string | number;
|
|
187
|
-
label: string;
|
|
188
|
-
}[]>;
|
|
189
|
-
}>;
|
|
190
|
-
/**
|
|
191
|
-
* Model update action
|
|
192
|
-
*/
|
|
193
|
-
export declare function createModelEditAction(ctx: AdminContext): ({ params, request }: {
|
|
194
|
-
params: {
|
|
195
|
-
model: string;
|
|
196
|
-
id: string;
|
|
197
|
-
};
|
|
198
|
-
request: Request;
|
|
199
|
-
}) => Promise<{
|
|
200
|
-
success: boolean;
|
|
201
|
-
error?: undefined;
|
|
202
|
-
} | {
|
|
203
|
-
success: boolean;
|
|
204
|
-
error: any;
|
|
205
|
-
}>;
|
|
206
|
-
/**
|
|
207
|
-
* Model delete action
|
|
208
|
-
*/
|
|
209
|
-
export declare function createModelDeleteAction(ctx: AdminContext): ({ params, request }: {
|
|
210
|
-
params: {
|
|
211
|
-
model: string;
|
|
212
|
-
};
|
|
213
|
-
request: Request;
|
|
214
|
-
}) => Promise<{
|
|
215
|
-
success: boolean;
|
|
216
|
-
error?: undefined;
|
|
217
|
-
} | {
|
|
218
|
-
success: boolean;
|
|
219
|
-
error: any;
|
|
220
|
-
}>;
|
|
221
|
-
/**
|
|
222
|
-
* Auth guard hook
|
|
223
|
-
*/
|
|
224
|
-
export declare function createAdminGuard(ctx: AdminContext): ({ event, resolve }: {
|
|
225
|
-
event: any;
|
|
226
|
-
resolve: Function;
|
|
227
|
-
}) => Promise<any>;
|
package/dist/admin.js
DELETED
|
@@ -1,369 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SvelteKit Admin - Core Admin Factory
|
|
3
|
-
* Creates all the necessary handlers for the admin panel
|
|
4
|
-
*/
|
|
5
|
-
import { parsePrismaSchema, getDisplayFields, getEditableFields, getInputType, fieldToLabel } from './server/introspection/parser.js';
|
|
6
|
-
import { createListOperation, createGetOperation, createCreateOperation, createUpdateOperation, createDeleteOperation } from './server/crud/operations.js';
|
|
7
|
-
import { defaultAdminCheck } from './server/auth/guard.js';
|
|
8
|
-
/**
|
|
9
|
-
* Create admin context with all necessary data
|
|
10
|
-
*/
|
|
11
|
-
export function createAdmin(config) {
|
|
12
|
-
// Parse schema if not provided
|
|
13
|
-
let schema = config.schema;
|
|
14
|
-
if (!schema && config.schemaPath) {
|
|
15
|
-
schema = parsePrismaSchema(config.schemaPath);
|
|
16
|
-
}
|
|
17
|
-
if (!schema) {
|
|
18
|
-
throw new Error('Either schema or schemaPath must be provided');
|
|
19
|
-
}
|
|
20
|
-
// Filter excluded models
|
|
21
|
-
const exclude = config.exclude || [];
|
|
22
|
-
const models = schema.models.filter(m => !exclude.includes(m.name));
|
|
23
|
-
return {
|
|
24
|
-
config,
|
|
25
|
-
schema,
|
|
26
|
-
models,
|
|
27
|
-
getModel: (name) => models.find(m => m.name.toLowerCase() === name.toLowerCase())
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
/**
|
|
31
|
-
* Layout data loader - provides models list and config to layout
|
|
32
|
-
*/
|
|
33
|
-
export function createLayoutLoad(ctx) {
|
|
34
|
-
return async ({ locals }) => {
|
|
35
|
-
const user = locals.user;
|
|
36
|
-
return {
|
|
37
|
-
models: ctx.models.map(m => ({
|
|
38
|
-
name: m.name,
|
|
39
|
-
label: ctx.config.models?.[m.name]?.label || m.name
|
|
40
|
-
})),
|
|
41
|
-
user: user ? { name: user.name, email: user.email } : undefined,
|
|
42
|
-
config: {
|
|
43
|
-
basePath: ctx.config.basePath || '/admin',
|
|
44
|
-
branding: {
|
|
45
|
-
title: ctx.config.branding?.title || 'Admin',
|
|
46
|
-
logo: ctx.config.branding?.logo,
|
|
47
|
-
primaryColor: ctx.config.branding?.primaryColor || '#6366f1'
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Dashboard data loader - provides model counts
|
|
55
|
-
*/
|
|
56
|
-
export function createDashboardLoad(ctx) {
|
|
57
|
-
return async () => {
|
|
58
|
-
const modelCounts = await Promise.all(ctx.models.map(async (model) => {
|
|
59
|
-
const modelKey = model.name.charAt(0).toLowerCase() + model.name.slice(1);
|
|
60
|
-
const prismaModel = ctx.config.prisma[modelKey];
|
|
61
|
-
let count = 0;
|
|
62
|
-
if (prismaModel?.count) {
|
|
63
|
-
try {
|
|
64
|
-
count = await prismaModel.count();
|
|
65
|
-
}
|
|
66
|
-
catch (e) {
|
|
67
|
-
// Model might not exist in DB yet
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return {
|
|
71
|
-
name: model.name,
|
|
72
|
-
label: ctx.config.models?.[model.name]?.label || model.name,
|
|
73
|
-
count
|
|
74
|
-
};
|
|
75
|
-
}));
|
|
76
|
-
const totalRecords = modelCounts.reduce((sum, m) => sum + m.count, 0);
|
|
77
|
-
return {
|
|
78
|
-
models: modelCounts,
|
|
79
|
-
stats: {
|
|
80
|
-
totalRecords,
|
|
81
|
-
modelsCount: ctx.models.length
|
|
82
|
-
}
|
|
83
|
-
};
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
/**
|
|
87
|
-
* Model list data loader
|
|
88
|
-
*/
|
|
89
|
-
export function createModelListLoad(ctx) {
|
|
90
|
-
return async ({ params, url }) => {
|
|
91
|
-
const model = ctx.getModel(params.model);
|
|
92
|
-
if (!model) {
|
|
93
|
-
throw new Error(`Model ${params.model} not found`);
|
|
94
|
-
}
|
|
95
|
-
const modelConfig = ctx.config.models?.[model.name] || {};
|
|
96
|
-
const hidden = modelConfig.hidden || [];
|
|
97
|
-
// Parse query params
|
|
98
|
-
const page = parseInt(url.searchParams.get('page') || '1');
|
|
99
|
-
const perPage = parseInt(url.searchParams.get('perPage') || '20');
|
|
100
|
-
const search = url.searchParams.get('search') || '';
|
|
101
|
-
const orderBy = url.searchParams.get('orderBy') || model.primaryKey || 'id';
|
|
102
|
-
const orderDir = (url.searchParams.get('orderDir') || 'desc');
|
|
103
|
-
// Get display fields
|
|
104
|
-
let displayFields = getDisplayFields(model).filter(f => !hidden.includes(f.name));
|
|
105
|
-
if (modelConfig.listFields?.length) {
|
|
106
|
-
displayFields = displayFields.filter(f => modelConfig.listFields.includes(f.name));
|
|
107
|
-
}
|
|
108
|
-
// Execute list query
|
|
109
|
-
const listOp = createListOperation(model);
|
|
110
|
-
const result = await listOp.execute(ctx.config.prisma, {
|
|
111
|
-
page,
|
|
112
|
-
perPage,
|
|
113
|
-
search,
|
|
114
|
-
orderBy,
|
|
115
|
-
orderDir
|
|
116
|
-
});
|
|
117
|
-
return {
|
|
118
|
-
model: {
|
|
119
|
-
name: model.name,
|
|
120
|
-
label: modelConfig.label || model.name,
|
|
121
|
-
fields: displayFields.map(f => ({
|
|
122
|
-
name: f.name,
|
|
123
|
-
type: f.type,
|
|
124
|
-
label: fieldToLabel(f.name)
|
|
125
|
-
})),
|
|
126
|
-
primaryKey: model.primaryKey || 'id'
|
|
127
|
-
},
|
|
128
|
-
items: result.items,
|
|
129
|
-
total: result.total,
|
|
130
|
-
page: result.page,
|
|
131
|
-
perPage: result.perPage,
|
|
132
|
-
orderBy,
|
|
133
|
-
orderDir,
|
|
134
|
-
search,
|
|
135
|
-
config: {
|
|
136
|
-
basePath: ctx.config.basePath || '/admin',
|
|
137
|
-
hidden,
|
|
138
|
-
listFields: modelConfig.listFields
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
};
|
|
142
|
-
}
|
|
143
|
-
/**
|
|
144
|
-
* Model create page loader
|
|
145
|
-
*/
|
|
146
|
-
export function createModelNewLoad(ctx) {
|
|
147
|
-
return async ({ params }) => {
|
|
148
|
-
const model = ctx.getModel(params.model);
|
|
149
|
-
if (!model) {
|
|
150
|
-
throw new Error(`Model ${params.model} not found`);
|
|
151
|
-
}
|
|
152
|
-
const modelConfig = ctx.config.models?.[model.name] || {};
|
|
153
|
-
const hidden = modelConfig.hidden || [];
|
|
154
|
-
const readonly = modelConfig.readonly || [];
|
|
155
|
-
const editableFields = getEditableFields(model).filter(f => !hidden.includes(f.name));
|
|
156
|
-
// Load relation options
|
|
157
|
-
const relationOptions = {};
|
|
158
|
-
for (const field of editableFields) {
|
|
159
|
-
if (field.relation) {
|
|
160
|
-
const relatedModel = ctx.getModel(field.relation.model);
|
|
161
|
-
if (relatedModel) {
|
|
162
|
-
const relKey = field.relation.model.charAt(0).toLowerCase() + field.relation.model.slice(1);
|
|
163
|
-
const relPrisma = ctx.config.prisma[relKey];
|
|
164
|
-
if (relPrisma?.findMany) {
|
|
165
|
-
try {
|
|
166
|
-
const items = await relPrisma.findMany({ take: 100 });
|
|
167
|
-
relationOptions[field.name] = items.map((item) => ({
|
|
168
|
-
id: item.id,
|
|
169
|
-
label: item.name || item.title || item.email || String(item.id)
|
|
170
|
-
}));
|
|
171
|
-
}
|
|
172
|
-
catch (e) {
|
|
173
|
-
// Ignore errors
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return {
|
|
180
|
-
model: {
|
|
181
|
-
name: model.name,
|
|
182
|
-
label: modelConfig.label || model.name,
|
|
183
|
-
fields: editableFields.map(f => ({
|
|
184
|
-
name: f.name,
|
|
185
|
-
type: f.type,
|
|
186
|
-
required: f.isRequired && !f.hasDefault,
|
|
187
|
-
label: fieldToLabel(f.name)
|
|
188
|
-
}))
|
|
189
|
-
},
|
|
190
|
-
config: {
|
|
191
|
-
basePath: ctx.config.basePath || '/admin',
|
|
192
|
-
hidden,
|
|
193
|
-
readonly
|
|
194
|
-
},
|
|
195
|
-
relationOptions
|
|
196
|
-
};
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Model create action
|
|
201
|
-
*/
|
|
202
|
-
export function createModelNewAction(ctx) {
|
|
203
|
-
return async ({ params, request }) => {
|
|
204
|
-
const model = ctx.getModel(params.model);
|
|
205
|
-
if (!model) {
|
|
206
|
-
throw new Error(`Model ${params.model} not found`);
|
|
207
|
-
}
|
|
208
|
-
const formData = await request.formData();
|
|
209
|
-
const data = {};
|
|
210
|
-
formData.forEach((value, key) => {
|
|
211
|
-
data[key] = value;
|
|
212
|
-
});
|
|
213
|
-
const createOp = createCreateOperation(model);
|
|
214
|
-
try {
|
|
215
|
-
await createOp.execute(ctx.config.prisma, data);
|
|
216
|
-
return { success: true };
|
|
217
|
-
}
|
|
218
|
-
catch (error) {
|
|
219
|
-
return {
|
|
220
|
-
success: false,
|
|
221
|
-
error: error.message,
|
|
222
|
-
fieldErrors: {}
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* Model edit page loader
|
|
229
|
-
*/
|
|
230
|
-
export function createModelEditLoad(ctx) {
|
|
231
|
-
return async ({ params }) => {
|
|
232
|
-
const model = ctx.getModel(params.model);
|
|
233
|
-
if (!model) {
|
|
234
|
-
throw new Error(`Model ${params.model} not found`);
|
|
235
|
-
}
|
|
236
|
-
const modelConfig = ctx.config.models?.[model.name] || {};
|
|
237
|
-
const hidden = modelConfig.hidden || [];
|
|
238
|
-
const readonly = modelConfig.readonly || [];
|
|
239
|
-
// Get the record
|
|
240
|
-
const getOp = createGetOperation(model);
|
|
241
|
-
const item = await getOp.execute(ctx.config.prisma, params.id);
|
|
242
|
-
if (!item) {
|
|
243
|
-
throw new Error(`Record not found`);
|
|
244
|
-
}
|
|
245
|
-
const allFields = model.fields.filter(f => !hidden.includes(f.name));
|
|
246
|
-
// Load relation options for editable relation fields
|
|
247
|
-
const relationOptions = {};
|
|
248
|
-
for (const field of allFields) {
|
|
249
|
-
if (field.relation && !field.isList) {
|
|
250
|
-
const relatedModel = ctx.getModel(field.relation.model);
|
|
251
|
-
if (relatedModel) {
|
|
252
|
-
const relKey = field.relation.model.charAt(0).toLowerCase() + field.relation.model.slice(1);
|
|
253
|
-
const relPrisma = ctx.config.prisma[relKey];
|
|
254
|
-
if (relPrisma?.findMany) {
|
|
255
|
-
try {
|
|
256
|
-
const items = await relPrisma.findMany({ take: 100 });
|
|
257
|
-
relationOptions[field.name] = items.map((item) => ({
|
|
258
|
-
id: item.id,
|
|
259
|
-
label: item.name || item.title || item.email || String(item.id)
|
|
260
|
-
}));
|
|
261
|
-
}
|
|
262
|
-
catch (e) {
|
|
263
|
-
// Ignore errors
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
return {
|
|
270
|
-
model: {
|
|
271
|
-
name: model.name,
|
|
272
|
-
label: modelConfig.label || model.name,
|
|
273
|
-
primaryKey: model.primaryKey || 'id',
|
|
274
|
-
fields: allFields.map(f => ({
|
|
275
|
-
name: f.name,
|
|
276
|
-
type: f.type,
|
|
277
|
-
required: f.isRequired,
|
|
278
|
-
label: fieldToLabel(f.name)
|
|
279
|
-
}))
|
|
280
|
-
},
|
|
281
|
-
item,
|
|
282
|
-
config: {
|
|
283
|
-
basePath: ctx.config.basePath || '/admin',
|
|
284
|
-
hidden,
|
|
285
|
-
readonly
|
|
286
|
-
},
|
|
287
|
-
relationOptions
|
|
288
|
-
};
|
|
289
|
-
};
|
|
290
|
-
}
|
|
291
|
-
/**
|
|
292
|
-
* Model update action
|
|
293
|
-
*/
|
|
294
|
-
export function createModelEditAction(ctx) {
|
|
295
|
-
return async ({ params, request }) => {
|
|
296
|
-
const model = ctx.getModel(params.model);
|
|
297
|
-
if (!model) {
|
|
298
|
-
throw new Error(`Model ${params.model} not found`);
|
|
299
|
-
}
|
|
300
|
-
const formData = await request.formData();
|
|
301
|
-
const data = {};
|
|
302
|
-
formData.forEach((value, key) => {
|
|
303
|
-
data[key] = value;
|
|
304
|
-
});
|
|
305
|
-
const updateOp = createUpdateOperation(model);
|
|
306
|
-
try {
|
|
307
|
-
await updateOp.execute(ctx.config.prisma, params.id, data);
|
|
308
|
-
return { success: true };
|
|
309
|
-
}
|
|
310
|
-
catch (error) {
|
|
311
|
-
return {
|
|
312
|
-
success: false,
|
|
313
|
-
error: error.message
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
/**
|
|
319
|
-
* Model delete action
|
|
320
|
-
*/
|
|
321
|
-
export function createModelDeleteAction(ctx) {
|
|
322
|
-
return async ({ params, request }) => {
|
|
323
|
-
const model = ctx.getModel(params.model);
|
|
324
|
-
if (!model) {
|
|
325
|
-
throw new Error(`Model ${params.model} not found`);
|
|
326
|
-
}
|
|
327
|
-
const formData = await request.formData();
|
|
328
|
-
const id = formData.get('id');
|
|
329
|
-
if (!id) {
|
|
330
|
-
throw new Error('Missing ID');
|
|
331
|
-
}
|
|
332
|
-
const deleteOp = createDeleteOperation(model);
|
|
333
|
-
try {
|
|
334
|
-
await deleteOp.execute(ctx.config.prisma, String(id));
|
|
335
|
-
return { success: true };
|
|
336
|
-
}
|
|
337
|
-
catch (error) {
|
|
338
|
-
return {
|
|
339
|
-
success: false,
|
|
340
|
-
error: error.message
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
};
|
|
344
|
-
}
|
|
345
|
-
/**
|
|
346
|
-
* Auth guard hook
|
|
347
|
-
*/
|
|
348
|
-
export function createAdminGuard(ctx) {
|
|
349
|
-
const basePath = ctx.config.basePath || '/admin';
|
|
350
|
-
return async ({ event, resolve }) => {
|
|
351
|
-
if (!event.url.pathname.startsWith(basePath)) {
|
|
352
|
-
return resolve(event);
|
|
353
|
-
}
|
|
354
|
-
const user = event.locals.user;
|
|
355
|
-
if (!user) {
|
|
356
|
-
return new Response(null, {
|
|
357
|
-
status: 302,
|
|
358
|
-
headers: { Location: '/login' }
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
const checkFn = ctx.config.checkAdmin ||
|
|
362
|
-
((u) => defaultAdminCheck(u, ctx.config.adminRole || 'admin'));
|
|
363
|
-
const isAdmin = await checkFn(user);
|
|
364
|
-
if (!isAdmin) {
|
|
365
|
-
return new Response('Forbidden', { status: 403 });
|
|
366
|
-
}
|
|
367
|
-
return resolve(event);
|
|
368
|
-
};
|
|
369
|
-
}
|