Skip to content

Commit 978256d

Browse files
committed
chore: refactor dev demo
1 parent e02327a commit 978256d

File tree

6 files changed

+402
-1940
lines changed

6 files changed

+402
-1940
lines changed
Lines changed: 392 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,392 @@
1+
import { AdminForthDataTypes, AdminForthResourceColumn, AdminForthResourceInput, AdminUser, Filters } from '../../adminforth/index.js';
2+
import { ENGINE_TYPES, BODY_TYPES } from '../custom/cars_data.js';
3+
4+
import UploadPlugin from '../../plugins/adminforth-upload/index.js';
5+
import TwoFactorsAuthPlugin from '../../plugins/adminforth-two-factors-auth/index.js';
6+
import AuditLogPlugin from '../../plugins/adminforth-audit-log/index.js';
7+
import RichEditorPlugin from '../../plugins/adminforth-rich-editor/index.js';
8+
import TextCompletePlugin from '../../plugins/adminforth-text-complete/index.js';
9+
import importExport from '../../plugins/adminforth-import-export/index.js';
10+
import InlineCreatePlugin from '../../plugins/adminforth-inline-create/index.js';
11+
import ListInPlaceEditPlugin from "../../plugins/adminforth-list-in-place-edit/index.js";
12+
import BulkAiFlowPlugin from '../../plugins/adminforth-bulk-ai-flow/index.js';
13+
14+
15+
import CompletionAdapterOpenAIChatGPT from '../../adapters/adminforth-completion-adapter-open-ai-chat-gpt/index.js';
16+
import ImageGenerationAdapterOpenAI from '../../adapters/adminforth-image-generation-adapter-openai/index.js';
17+
import AdminForthStorageAdapterLocalFilesystem from "../../adapters/adminforth-storage-adapter-local/index.js";
18+
import AdminForthAdapterS3Storage from '../../adapters/adminforth-storage-adapter-amazon-s3/index.js';
19+
import AdminForthImageVisionAdapterOpenAi from '../../adapters/adminforth-image-vision-adapter-openai/index.js';
20+
21+
export default function carsResourseTemplate(resourceId: string, dataSource: string) {
22+
return {
23+
dataSource: dataSource,
24+
table: 'cars',
25+
resourceId: resourceId,
26+
label: 'Cars',
27+
recordLabel: (r) => `🚘 ${r.model} 🚗`,
28+
29+
/*********************************************************************************
30+
31+
32+
Columns
33+
34+
35+
*********************************************************************************/
36+
columns: [
37+
{
38+
name: 'id',
39+
type: AdminForthDataTypes.STRING,
40+
label: 'Identifier',
41+
showIn: {
42+
list: false,
43+
edit: false,
44+
create: false,
45+
},
46+
primaryKey: true,
47+
fillOnCreate: ({ initialRecord, adminUser }) => Math.random().toString(36).substring(7),
48+
},
49+
{
50+
name: 'model',
51+
required: true,
52+
showIn: { all: true },
53+
type: AdminForthDataTypes.STRING,
54+
maxLength: 255,
55+
minLength: 3,
56+
},
57+
{
58+
name: 'price',
59+
inputSuffix: 'USD',
60+
allowMinMaxQuery: true,
61+
editingNote: 'Price is in USD',
62+
type: AdminForthDataTypes.FLOAT,
63+
required: true,
64+
},
65+
{
66+
name: 'created_at',
67+
type: AdminForthDataTypes.DATETIME,
68+
allowMinMaxQuery: true,
69+
showIn: { create: false, edit: false },
70+
fillOnCreate: ({ initialRecord, adminUser }) => (new Date()).toISOString(),
71+
},
72+
{
73+
name: 'engine_type',
74+
type: AdminForthDataTypes.STRING,
75+
label: 'Engine Type',
76+
allowMinMaxQuery: true,
77+
enum: ENGINE_TYPES,
78+
},
79+
{
80+
name: 'engine_power',
81+
allowMinMaxQuery: true,
82+
type: AdminForthDataTypes.INTEGER,
83+
inputSuffix: 'HP',
84+
showIf: { engine_type: { $not: 'electric' } },
85+
required: true,
86+
},
87+
{
88+
name: 'production_year',
89+
type: AdminForthDataTypes.INTEGER,
90+
minValue: 1900,
91+
maxValue: new Date().getFullYear(),
92+
},
93+
{
94+
name: 'description',
95+
type: AdminForthDataTypes.TEXT,
96+
sortable: false,
97+
showIn: { list: false },
98+
},
99+
{
100+
name: 'listed',
101+
required: true,
102+
type: AdminForthDataTypes.BOOLEAN,
103+
},
104+
{
105+
name: 'mileage',
106+
allowMinMaxQuery: true,
107+
type: AdminForthDataTypes.FLOAT,
108+
},
109+
{
110+
name: 'color',
111+
type: AdminForthDataTypes.STRING,
112+
},
113+
{
114+
name: 'body_type',
115+
label: 'Body Type',
116+
enum: BODY_TYPES,
117+
type: AdminForthDataTypes.STRING,
118+
},
119+
{
120+
name: 'promo_picture',
121+
type: AdminForthDataTypes.STRING,
122+
label: 'Promo Picture',
123+
},
124+
{
125+
name: 'generated_promo_picture',
126+
type: AdminForthDataTypes.STRING,
127+
label: 'Generated Promo Picture',
128+
},
129+
{
130+
name: 'photos',
131+
type: AdminForthDataTypes.JSON,
132+
label: 'Photos',
133+
isArray: {
134+
enabled: true,
135+
itemType: AdminForthDataTypes.STRING,
136+
},
137+
showIn: {
138+
list: false
139+
},
140+
},
141+
{
142+
name: 'seller_id',
143+
type: AdminForthDataTypes.STRING,
144+
foreignResource: {
145+
resourceId: 'adminuser',
146+
searchableFields: ["id", "email"],
147+
}
148+
}
149+
],
150+
plugins: [
151+
152+
/*********************************************************************************
153+
154+
155+
Plugins
156+
157+
158+
*********************************************************************************/
159+
new UploadPlugin({
160+
// storageAdapter: new AdminForthStorageAdapterLocalFilesystem({
161+
// fileSystemFolder: "./images",
162+
// adminServeBaseUrl: "static/source",
163+
// mode: "public",
164+
// signingSecret: "TOP_SECRET",
165+
// }),
166+
storageAdapter: new AdminForthAdapterS3Storage({
167+
bucket: process.env.AWS_BUCKET_NAME as string,
168+
region: process.env.AWS_REGION as string,
169+
accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
170+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,
171+
}),
172+
pathColumnName: 'photos',
173+
allowedFileExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webm', 'webp'],
174+
maxFileSize: 1024 * 1024 * 20, // 20 MB
175+
filePath: ({originalFilename, originalExtension, contentType}) =>
176+
`sqlite/car_images/cars/${originalFilename}.${originalExtension}`,
177+
preview: {
178+
maxShowWidth: "300px",
179+
previewUrl: ({filePath}) => `/static/source/${filePath}`,
180+
},
181+
}),
182+
new UploadPlugin({
183+
// storageAdapter: new AdminForthStorageAdapterLocalFilesystem({
184+
// fileSystemFolder: "./images",
185+
// adminServeBaseUrl: "static/source",
186+
// mode: "public",
187+
// signingSecret: "TOP_SECRET",
188+
// }),
189+
storageAdapter: new AdminForthAdapterS3Storage({
190+
bucket: process.env.AWS_BUCKET_NAME as string,
191+
region: process.env.AWS_REGION as string,
192+
accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
193+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,
194+
}),
195+
pathColumnName: 'promo_picture',
196+
allowedFileExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webm', 'webp'],
197+
maxFileSize: 1024 * 1024 * 20, // 20 MB
198+
filePath: ({originalFilename, originalExtension, contentType}) =>
199+
`sqlite/car_images/cars_promo_images/${originalFilename}_${Date.now()}.${originalExtension}`,
200+
preview: {
201+
maxShowWidth: "300px",
202+
previewUrl: ({filePath}) => `/static/source/${filePath}`,
203+
},
204+
}),
205+
new RichEditorPlugin({
206+
htmlFieldName: 'description',
207+
attachments: {
208+
attachmentResource: "cars_description_images",
209+
attachmentFieldName: "image_path",
210+
attachmentRecordIdFieldName: "record_id",
211+
attachmentResourceIdFieldName: "resource_id",
212+
},
213+
...(process.env.OPENAI_API_KEY ? {
214+
completion: {
215+
adapter: new CompletionAdapterOpenAIChatGPT({
216+
openAiApiKey: process.env.OPENAI_API_KEY as string,
217+
model: 'gpt-4o',
218+
expert: {
219+
temperature: 0.7
220+
}
221+
}),
222+
expert: {
223+
debounceTime: 250,
224+
}
225+
}
226+
} : {}),
227+
}),
228+
new importExport({}),
229+
// new InlineCreatePlugin({}),
230+
new ListInPlaceEditPlugin({
231+
columns: ["model", "engine_type", "price"],
232+
}),
233+
234+
/*********************************************************************************
235+
236+
AI Plugins
237+
238+
*********************************************************************************/
239+
...(process.env.OPENAI_API_KEY ?
240+
[
241+
new UploadPlugin({
242+
// storageAdapter: new AdminForthStorageAdapterLocalFilesystem({
243+
// fileSystemFolder: "./images",
244+
// adminServeBaseUrl: "static/source",
245+
// mode: "public",
246+
// signingSecret: "TOP_SECRET",
247+
// }),
248+
storageAdapter: new AdminForthAdapterS3Storage({
249+
bucket: process.env.AWS_BUCKET_NAME as string,
250+
region: process.env.AWS_REGION as string,
251+
accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,
252+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,
253+
}),
254+
pathColumnName: 'generated_promo_picture',
255+
allowedFileExtensions: ['jpg', 'jpeg', 'png', 'gif', 'webm', 'webp'],
256+
maxFileSize: 1024 * 1024 * 20, // 20 MB
257+
filePath: ({originalFilename, originalExtension, contentType}) =>
258+
`sqlite/car_images/cars_promo_images_generated/${originalFilename}.${originalExtension}`,
259+
preview: {
260+
maxShowWidth: "300px",
261+
previewUrl: ({filePath}) => `/static/source/${filePath}`,
262+
},
263+
generation: {
264+
countToGenerate: 2, // how much images generate in one shot
265+
adapter: new ImageGenerationAdapterOpenAI({
266+
openAiApiKey: process.env.OPENAI_API_KEY as string,
267+
model: 'gpt-image-1',
268+
}),
269+
fieldsForContext: ['description', 'model', 'color', 'body_type', 'engine_type'],
270+
outputSize: '1536x1024' // size of generated image
271+
}
272+
}),
273+
new TextCompletePlugin({
274+
fieldName: 'model',
275+
adapter: new CompletionAdapterOpenAIChatGPT({
276+
openAiApiKey: process.env.OPENAI_API_KEY as string,
277+
model: 'gpt-4o', // default "gpt-4o-mini"
278+
expert: {
279+
temperature: 0.7 //Model temperature, default 0.7
280+
}
281+
}),
282+
}),
283+
new BulkAiFlowPlugin({
284+
actionName: 'Analyze',
285+
attachFiles: async ({ record }: { record: any }) => {
286+
if (!record.promo_picture) {
287+
return [];
288+
}
289+
console.log('Attaching file for analysis:', `http://localhost:3000/static/source/cars_promo_images/${record.promo_picture}`);
290+
return [`http://localhost:3000/static/source/${record.promo_picture}`];
291+
},
292+
visionAdapter: new AdminForthImageVisionAdapterOpenAi(
293+
{
294+
openAiApiKey: process.env.OPENAI_API_KEY as string,
295+
model: 'gpt-4.1-mini',
296+
}
297+
),
298+
imageGenerationAdapter: new ImageGenerationAdapterOpenAI({
299+
openAiApiKey: process.env.OPENAI_API_KEY as string,
300+
model: 'gpt-image-1',
301+
}),
302+
fillFieldsFromImages: {
303+
color: "Which color is the car in the image?",
304+
body_type: "What is the body type of the car in the image?",
305+
production_year: "What is the production year of the car in the image?",
306+
},
307+
// generateImages: {
308+
// generated_promo_picture: {
309+
// prompt: 'Transform this photo into a cartoon-style car picture. Imagine that we are in 90s japan and this car was tuned for the drifting competitions.',
310+
// outputSize: '1024x1024',
311+
// countToGenerate: 2,
312+
// rateLimit: '3/1h'
313+
// },
314+
// },
315+
}),
316+
] : []),
317+
],
318+
319+
320+
321+
322+
323+
324+
/*********************************************************************************
325+
326+
327+
Options
328+
329+
330+
*********************************************************************************/
331+
332+
options: {
333+
listPageSize: 12,
334+
allowedActions: {
335+
edit: true,
336+
delete: true,
337+
show: true,
338+
filter: true,
339+
},
340+
actions: [
341+
{
342+
name: 'Approve Listing',
343+
icon: 'flowbite:check-outline',
344+
action: async ({ recordId, adminUser, adminforth, extra }) => {
345+
//@ts-ignore
346+
const verificationResult = extra?.verificationResult
347+
if (!verificationResult) {
348+
return { ok: false, error: 'No verification result provided' };
349+
}
350+
const t2fa = adminforth.getPluginByClassName<TwoFactorsAuthPlugin>('TwoFactorsAuthPlugin');
351+
const result = await t2fa.verify(verificationResult, {
352+
adminUser: adminUser,
353+
userPk: adminUser.pk as string,
354+
cookies: extra.cookies
355+
});
356+
357+
358+
if (!result || 'error' in result) {
359+
return { ok: false, error: result?.error ?? 'Provided 2fa verification data is invalid' };
360+
}
361+
await adminforth
362+
.getPluginByClassName<AuditLogPlugin>('AuditLogPlugin')
363+
.logCustomAction({
364+
resourceId: resourceId,
365+
recordId: null,
366+
actionId: 'visitedDashboard',
367+
oldData: null,
368+
data: { dashboard: 'main' },
369+
user: adminUser,
370+
});
371+
372+
373+
374+
await adminforth.resource(resourceId).update(recordId, { listed: true });
375+
return {
376+
ok: true,
377+
successMessage: "Listed"
378+
};
379+
},
380+
showIn: {
381+
list: true,
382+
showButton: true,
383+
showThreeDotsMenu: true,
384+
},
385+
customComponent: {
386+
file: '@@/RequireTwoFaGate.vue'
387+
},
388+
}
389+
],
390+
},
391+
} as AdminForthResourceInput;
392+
}

0 commit comments

Comments
 (0)