From d3b748ff598985744ca86c688ae9169ed427d53e Mon Sep 17 00:00:00 2001 From: Henry C Date: Sat, 23 Nov 2024 16:57:34 +0800 Subject: [PATCH 1/2] Feature: Generic data model and Supabase data service A generic data template model has been implemented. This model can be extended to other classes, such as User. It includes the TABLE_NAME and DATABASE_MAPPING properties for a more efficient database access. The SupabaseService has been implemented to support general data fetching. It currently supports the fetching of a single object and an array of objects. There is no need to implement the data fetching logic repeatedly for each model. - Added SupabaseService - Added GenericModel - Added User model - Added data fetching tests during Homepage initialization --- src/models/GenericModel.ts | 12 +++++ src/models/User.ts | 36 +++++++++++++ src/routes/home/index.tsx | 89 +++++++++++++++++++++++++++++++++ src/services/SupabaseService.ts | 84 +++++++++++++++++++++++++++++++ 4 files changed, 221 insertions(+) create mode 100644 src/models/GenericModel.ts create mode 100644 src/models/User.ts create mode 100644 src/routes/home/index.tsx create mode 100644 src/services/SupabaseService.ts diff --git a/src/models/GenericModel.ts b/src/models/GenericModel.ts new file mode 100644 index 0000000..46daf54 --- /dev/null +++ b/src/models/GenericModel.ts @@ -0,0 +1,12 @@ +// GenericModel class +export default abstract class GenericModel { + // Abstract property for table name + static TABLE_NAME: string; + + static DATABASE_MAP: Object; + + // Abstract method for parsing JSON + static parseJson(json: any): GenericModel { + throw new Error("parseJson method not implemented."); + } +} diff --git a/src/models/User.ts b/src/models/User.ts new file mode 100644 index 0000000..8c71a71 --- /dev/null +++ b/src/models/User.ts @@ -0,0 +1,36 @@ +import GenericModel from "./GenericModel" + +export default class User extends GenericModel { + + static TABLE_NAME = "members" + static DATABASE_MAP: Object = { + id: "uuid", + username: "username", + email: "fk_email", + identity: "fk_identity", + avatar: "avatar", + } + + public id: String = "" + public username: String = "" + public email: String = "" + public phone: String = "" + public avatar: String = "" + public profileBackground: String = "" + public joinedAt: Date = new Date() + public identity: String = "" + public department: String = "" + public grade: String = "" + public bio: String = "" + + + static parseJson(json: any) : User { + const user = new User() + + for (const [userProperty, jsonField] of Object.entries(this.DATABASE_MAP)) + if (json[jsonField] !== undefined) + (user as any)[userProperty] = json[jsonField]; + + return user + } +} \ No newline at end of file diff --git a/src/routes/home/index.tsx b/src/routes/home/index.tsx new file mode 100644 index 0000000..2e358c7 --- /dev/null +++ b/src/routes/home/index.tsx @@ -0,0 +1,89 @@ +import { createFileRoute } from '@tanstack/react-router' +import { VStack } from '../../components' + +const homeItems = [ + { title: "test", description: "Hello world." }, + { title: "test", description: "Hello world." }, + { title: "test", description: "Hello world." }, + { title: "test", description: "Hello world." }, + { title: "test", description: "Hello world." }, + { title: "test", description: "Hello world." }, + { title: "test", description: "Hello world." }, + { title: "test", description: "Hello world." }, + { title: "test", description: "Hello world." }, + { title: "test", description: "Hello world." }, + { title: "test", description: "Hello world." }, +] + +// User.fetchSingle().then(user => { +// console.log(user) +// }) + +// const supabaseService = new SupabaseService() +// SupabaseService.fetchSingle(User, "c7e80cb0-7d3d-411f-9983-5e3addf62980", { email: "ncuapp@test.com" }).then( +// response => { +// console.log(response) +// } +// ) + +// SupabaseService.fetchMultiple(User).then( +// response => { +// console.log(response) +// } +// ) + +// async function fetch() { +// const { data } = await supabase.from('members').select('*') +// console.log(data) +// } +// fetch() + +export const Route = createFileRoute('/home/')({ + + component: () =>
+
+ +
+

這裡最多七個字

+

中文系 二年級

+
+ + {/*
*/} + +
+
+ +
+
+ + + 變更個人檔案 + +
+ + + + + {/* {homeItems.map((item, index) => ( + + ))} */} + + {/* + + + Hello + + + X + + Test */} + +
, +}) diff --git a/src/services/SupabaseService.ts b/src/services/SupabaseService.ts new file mode 100644 index 0000000..a54806b --- /dev/null +++ b/src/services/SupabaseService.ts @@ -0,0 +1,84 @@ +import GenericModel from "../models/GenericModel"; +import { supabase } from "../utils/supabase"; + +// Supabase service for quickly accessing data +export default class SupabaseService { + + + /* Fetch a single object + * + * Parameters + * ---------- + * modelClass: class + * The target model class + * targetID: str + * The target record ID + * criteria: Dictionary + * Extra search criteria, please follow the DATABASE_MAPPING of the target model + * + * Returns + * ------- + * The target object (of the target model type). Null if record is not found. + */ + public static async fetchSingle( + modelClass: new() => T, + targetID: String, + criteria?: Partial>, + ): Promise { + const model = (modelClass as any) + const tableName = model.TABLE_NAME + + var query = supabase.from(tableName) + .select('*') + .eq("uuid", targetID) + + for (const key in criteria) + if (model.DATABASE_MAP[key]) + query = query.eq(model.DATABASE_MAP[key], criteria[key]) + + const data = await query; + + if (!data.data || data.data.length == 0) { + return null + } + const record = data.data[0] + + return model.parseJson(record) + } + + + + + /* Fetch an array of objects + * + * Parameters + * ---------- + * modelClass: class + * The target model class + * + * Returns + * ------- + * Array of objects (of the target model type). + * Returns empty array if no records can be found. + */ + public static async fetchMultiple( + modelClass: new() => T, + ): Promise> { + const model = (modelClass as any) + const tableName = model.TABLE_NAME + + const data = await supabase.from(tableName) + .select('*') + + const records = data.data + + const results: Array = [] + + records?.forEach(record => { + const user = model.parseJson(record) + results.push(user) + }) + + return results + } +} \ No newline at end of file From d14c41784cf026e9554cd0211ab194f73ed2f3d3 Mon Sep 17 00:00:00 2001 From: Henry C Date: Sat, 23 Nov 2024 17:11:44 +0800 Subject: [PATCH 2/2] Fix: Import errors in Homepage and json property in GenericModel - Removed VStack imports and unused template components --- src/models/GenericModel.ts | 1 + src/routeTree.gen.ts | 25 +++++++++++++++++++ src/routes/home/index.tsx | 50 ++++++++++++-------------------------- 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/models/GenericModel.ts b/src/models/GenericModel.ts index 46daf54..7cc48e8 100644 --- a/src/models/GenericModel.ts +++ b/src/models/GenericModel.ts @@ -8,5 +8,6 @@ export default abstract class GenericModel { // Abstract method for parsing JSON static parseJson(json: any): GenericModel { throw new Error("parseJson method not implemented."); + return json } } diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts index e4b276f..4137013 100644 --- a/src/routeTree.gen.ts +++ b/src/routeTree.gen.ts @@ -15,6 +15,7 @@ import { Route as LoginImport } from './routes/login' import { Route as IndexImport } from './routes/index' import { Route as SalesIndexImport } from './routes/sales/index' import { Route as MapIndexImport } from './routes/map/index' +import { Route as HomeIndexImport } from './routes/home/index' import { Route as EventsIndexImport } from './routes/events/index' import { Route as DinnerIndexImport } from './routes/dinner/index' import { Route as CalendarIndexImport } from './routes/calendar/index' @@ -44,6 +45,11 @@ const MapIndexRoute = MapIndexImport.update({ getParentRoute: () => rootRoute, } as any) +const HomeIndexRoute = HomeIndexImport.update({ + path: '/home/', + getParentRoute: () => rootRoute, +} as any) + const EventsIndexRoute = EventsIndexImport.update({ path: '/events/', getParentRoute: () => rootRoute, @@ -134,6 +140,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof EventsIndexImport parentRoute: typeof rootRoute } + '/home/': { + id: '/home/' + path: '/home' + fullPath: '/home' + preLoaderRoute: typeof HomeIndexImport + parentRoute: typeof rootRoute + } '/map/': { id: '/map/' path: '/map' @@ -162,6 +175,7 @@ export interface FileRoutesByFullPath { '/calendar': typeof CalendarIndexRoute '/dinner': typeof DinnerIndexRoute '/events': typeof EventsIndexRoute + '/home': typeof HomeIndexRoute '/map': typeof MapIndexRoute '/sales': typeof SalesIndexRoute } @@ -175,6 +189,7 @@ export interface FileRoutesByTo { '/calendar': typeof CalendarIndexRoute '/dinner': typeof DinnerIndexRoute '/events': typeof EventsIndexRoute + '/home': typeof HomeIndexRoute '/map': typeof MapIndexRoute '/sales': typeof SalesIndexRoute } @@ -189,6 +204,7 @@ export interface FileRoutesById { '/calendar/': typeof CalendarIndexRoute '/dinner/': typeof DinnerIndexRoute '/events/': typeof EventsIndexRoute + '/home/': typeof HomeIndexRoute '/map/': typeof MapIndexRoute '/sales/': typeof SalesIndexRoute } @@ -204,6 +220,7 @@ export interface FileRouteTypes { | '/calendar' | '/dinner' | '/events' + | '/home' | '/map' | '/sales' fileRoutesByTo: FileRoutesByTo @@ -216,6 +233,7 @@ export interface FileRouteTypes { | '/calendar' | '/dinner' | '/events' + | '/home' | '/map' | '/sales' id: @@ -228,6 +246,7 @@ export interface FileRouteTypes { | '/calendar/' | '/dinner/' | '/events/' + | '/home/' | '/map/' | '/sales/' fileRoutesById: FileRoutesById @@ -242,6 +261,7 @@ export interface RootRouteChildren { CalendarIndexRoute: typeof CalendarIndexRoute DinnerIndexRoute: typeof DinnerIndexRoute EventsIndexRoute: typeof EventsIndexRoute + HomeIndexRoute: typeof HomeIndexRoute MapIndexRoute: typeof MapIndexRoute SalesIndexRoute: typeof SalesIndexRoute } @@ -255,6 +275,7 @@ const rootRouteChildren: RootRouteChildren = { CalendarIndexRoute: CalendarIndexRoute, DinnerIndexRoute: DinnerIndexRoute, EventsIndexRoute: EventsIndexRoute, + HomeIndexRoute: HomeIndexRoute, MapIndexRoute: MapIndexRoute, SalesIndexRoute: SalesIndexRoute, } @@ -279,6 +300,7 @@ export const routeTree = rootRoute "/calendar/", "/dinner/", "/events/", + "/home/", "/map/", "/sales/" ] @@ -307,6 +329,9 @@ export const routeTree = rootRoute "/events/": { "filePath": "events/index.tsx" }, + "/home/": { + "filePath": "home/index.tsx" + }, "/map/": { "filePath": "map/index.tsx" }, diff --git a/src/routes/home/index.tsx b/src/routes/home/index.tsx index 2e358c7..8cf6ea8 100644 --- a/src/routes/home/index.tsx +++ b/src/routes/home/index.tsx @@ -1,19 +1,19 @@ import { createFileRoute } from '@tanstack/react-router' -import { VStack } from '../../components' - -const homeItems = [ - { title: "test", description: "Hello world." }, - { title: "test", description: "Hello world." }, - { title: "test", description: "Hello world." }, - { title: "test", description: "Hello world." }, - { title: "test", description: "Hello world." }, - { title: "test", description: "Hello world." }, - { title: "test", description: "Hello world." }, - { title: "test", description: "Hello world." }, - { title: "test", description: "Hello world." }, - { title: "test", description: "Hello world." }, - { title: "test", description: "Hello world." }, -] +// import { VStack } from '../../components' + +// const homeItems = [ +// { title: "test", description: "Hello world." }, +// { title: "test", description: "Hello world." }, +// { title: "test", description: "Hello world." }, +// { title: "test", description: "Hello world." }, +// { title: "test", description: "Hello world." }, +// { title: "test", description: "Hello world." }, +// { title: "test", description: "Hello world." }, +// { title: "test", description: "Hello world." }, +// { title: "test", description: "Hello world." }, +// { title: "test", description: "Hello world." }, +// { title: "test", description: "Hello world." }, +// ] // User.fetchSingle().then(user => { // console.log(user) @@ -67,23 +67,5 @@ export const Route = createFileRoute('/home/')({ 變更個人檔案 - - - - - {/* {homeItems.map((item, index) => ( - - ))} */} - - {/* - - - Hello - - - X - - Test */} - - , + , })