Skip to content

Commit

Permalink
Feature: Generic data model and Supabase data service
Browse files Browse the repository at this point in the history
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
  • Loading branch information
yeahlowflicker committed Nov 23, 2024
1 parent 7134cae commit d3b748f
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 0 deletions.
12 changes: 12 additions & 0 deletions src/models/GenericModel.ts
Original file line number Diff line number Diff line change
@@ -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.");
}
}
36 changes: 36 additions & 0 deletions src/models/User.ts
Original file line number Diff line number Diff line change
@@ -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
}
}
89 changes: 89 additions & 0 deletions src/routes/home/index.tsx
Original file line number Diff line number Diff line change
@@ -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: () => <div>
<section className='h-[200px] mb-[40px] bg-yellow-50 relative border-b-[5px] border-gray-600'>

<div className='flex flex-col absolute left-[10px] bottom-[5px]'>
<h1 className='font-bold text-4xl text-black'>這裡最多七個字</h1>
<h2 className='font-bold text-black'>中文系 二年級</h2>
</div>

{/* <div className='
w-[125px] h-[125px]
absolute right-[15px] bottom-[-25px]
border-[4px] border-gray-600 rounded-full bg-gray-500
'></div> */}

<div className="avatar absolute right-[15px] bottom-[-25px]">
<div className="ring-gray-300 w-[125px] rounded-full ring">
<img src="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp" />
</div>
</div>

<span className='
absolute top-[20px] right-[15px]
text-black font-bold text-xs
'>
變更個人檔案
</span>
</section>

<VStack className='px-[10px] gap-[20px]'>


{/* {homeItems.map((item, index) => (
<WelcomeCard title={item.title} description={item.description} />
))} */}

{/* <NcuInput />
<NcuTextarea>
Hello
</NcuTextarea>
<NcuSwitch label="test">X</NcuSwitch>
<NcuButton>Test</NcuButton> */}
</VStack>
</div>,
})
84 changes: 84 additions & 0 deletions src/services/SupabaseService.ts
Original file line number Diff line number Diff line change
@@ -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<T extends GenericModel>(
modelClass: new() => T,
targetID: String,
criteria?: Partial<Record<string, any>>,
): Promise<T | null> {
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<T extends GenericModel>(
modelClass: new() => T,
): Promise<Array<T>> {
const model = (modelClass as any)
const tableName = model.TABLE_NAME

const data = await supabase.from(tableName)
.select('*')

const records = data.data

const results: Array<T> = []

records?.forEach(record => {
const user = model.parseJson(record)
results.push(user)
})

return results
}
}

0 comments on commit d3b748f

Please sign in to comment.