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