Skip to content

Commit

Permalink
Merge branch 'main' into xCk27x
Browse files Browse the repository at this point in the history
  • Loading branch information
xCk27x authored Dec 21, 2024
2 parents 907e7b8 + e198ba8 commit 3b84979
Show file tree
Hide file tree
Showing 7 changed files with 371 additions and 0 deletions.
116 changes: 116 additions & 0 deletions src/backend/event/Controllers/EventController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import ErrorHandler from "../../../utils/ErrorHandler";
import { supabase } from "../../../utils/supabase";

import Event, { DBEvent } from '../Entities/Event';
import EventService from "../Services/EventService";


const EVENT_TABLE_NAME = "events"


export default class EventController {


/**
* Get an array of events
*
* @usage eventController.getEvents(<PARAMS>).then(
* (events: Array<Events>) => { ... }
* )
*
* @param {string} fields - The columns to retrieve (comma-separated)
* @param {string} orderBy - Which field to order by (leave blank if not needed)
* @param {boolean} orderDescending - Whether to order in descending order (defaults to false)
* @param {number} rangeStart - Starting index of fetch (defaults to 0)
* @param {number} rangeEnd - Ending index of fetch (defaults to 100)
*
* @returns {Array<Event>} - Array of events
*
* @see [https://supabase.com/docs/reference/javascript/order]
* @see [https://supabase.com/docs/reference/javascript/range]
*
* @author Henry C. (@yeahlowflicker)
*/
public async getEvents(
fields: string,
orderBy?: string,
orderDescending?: boolean,
rangeStart?: number,
rangeEnd?: number
) : Promise<Array<Event> | null> {

const query = supabase
.from(EVENT_TABLE_NAME)
.select(fields)
.returns<Array<DBEvent>>()

if (orderBy)
query.order(orderBy, { ascending: !orderDescending })

if (rangeStart !== undefined && rangeEnd !== undefined)
query.range(rangeStart, rangeEnd)

const { data, error } = await query

// Error handling
if (error) {
ErrorHandler.handleSupabaseError(error)
return null
}

// Initialize result array
const events : Array<Event> = []


// For each found DBEvent, convert to Event and append to result array
data.forEach((record: DBEvent) => {
events.push(
EventService.parseEvent(record)
)
})

return events
}



/**
* Find a single event by ID
*
* @usage eventController.FindEventByID(<PARAMS>).then(
* (event: Event) => { ... }
* )
*
* @param {string} eventID - Target event ID
* @param {string} fields - The columns to retrieve
*
* @returns {Event} - The target event entity (null if not found)
*
* @author Henry C. (@yeahlowflicker)
*/
public async findEventByID(eventID: string, fields?: string) : Promise<Event | null> {

const { data, error } = await supabase
.from(EVENT_TABLE_NAME)
.select(fields)
.eq("id", eventID)
.returns<DBEvent>()
.limit(1)
.single()

// Error handling
if (error) {
ErrorHandler.handleSupabaseError(error)
return null
}

if (!data)
return null

// Type conversion: DBEvent -> Event
const event : Event = EventService.parseEvent(data)

return event
}

}
22 changes: 22 additions & 0 deletions src/backend/event/Entities/Event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Database } from "../../../utils/database.types";

/**
* This is a dummy-type inherited from the generated Supabase type
*/
export type DBEvent = Database['public']['Tables']['events']['Row'];


export default class Event {

public id: number = 0;
public name: string = "";
public type: number = 0;
public description: string = "";
public startTime: string = "";
public endTime: string = "";
public location: string = "";
public fee: number = 0;
public userID: string = "";
public createdAt: string = "";

}
30 changes: 30 additions & 0 deletions src/backend/event/Services/EventService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import Event, { DBEvent } from "../Entities/Event";

const EventService = {

parseEvent(record: DBEvent) : Event {
if (!record || typeof record !== 'object')
throw new Error('Invalid record provided')

if (!record.id)
throw new Error('id is a required field')

const event = new Event()

event.id = record.id
event.name = record.name ?? ""
event.type = typeof record.type === 'number' ? record.type : 0
event.description = record.description ?? ""
event.startTime = record.start_time ? new Date(record.start_time).toISOString() : ""
event.endTime = record.end_time ? new Date(record.end_time).toISOString() : ""
event.location = record.location ?? ""
event.fee = typeof record.fee === 'number' ? record.fee : 0
event.userID = record.user_id
event.createdAt = record.created_at ? new Date(record.created_at).toISOString() : ""

return event
}

}

export default EventService
116 changes: 116 additions & 0 deletions src/backend/user/Controllers/UserController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import ErrorHandler from "../../../utils/ErrorHandler";
import { supabase } from "../../../utils/supabase";

import User, { DBUser } from '../Entities/User';
import UserService from '../Services/UserService';


const USER_TABLE_NAME = "members"


export default class UserController {

/**
* Get an array of users
*
* @usage userController.getUsers(<PARAMS>).then(
* (users: Array<Users>) => { ... }
* )
*
* @param {string} fields - The columns to retrieve (comma-separated)
* @param {string} orderBy - Which field to order by (leave blank if not needed)
* @param {boolean} orderDescending - Whether to order in descending order (defaults to false)
* @param {number} rangeStart - Starting index of fetch (defaults to 0)
* @param {number} rangeEnd - Ending index of fetch (defaults to 100)
*
* @returns {Array<User>} - Array of users
*
* @see [https://supabase.com/docs/reference/javascript/order]
* @see [https://supabase.com/docs/reference/javascript/range]
*
* @author Henry C. (@yeahlowflicker)
*/
public async getUsers(
fields: string,
orderBy?: string,
orderDescending?: boolean,
rangeStart?: number,
rangeEnd?: number
) : Promise<Array<User> | null> {

const query = supabase
.from(USER_TABLE_NAME)
.select(fields)
.returns<Array<DBUser>>()

if (orderBy)
query.order(orderBy, { ascending: !orderDescending })

if (rangeStart !== undefined && rangeEnd !== undefined)
query.range(rangeStart, rangeEnd)

const { data, error } = await query

// Error handling
if (error) {
ErrorHandler.handleSupabaseError(error)
return null
}

// Initialize result array
const users : Array<User> = []

// For each found DBUser, convert to User and append to result array
data.forEach((record: DBUser) => {
users.push(
UserService.parseUser(record)
)
})

return users
}



/**
* Find a single user by ID
*
* @usage userController.FindUserByID(<PARAMS>).then(
* (user: User) => { ... }
* )
*
* @param {string} userID - Target user ID
* @param {string} fields - The columns to retrieve
*
* @returns {User} - The target user entity (null if not found)
*
* @author Henry C. (@yeahlowflicker)
*/
public async findUserByID(userID: string, fields?: string) : Promise<User | null> {

const { data, error } = await supabase
.from(USER_TABLE_NAME)
.select(fields)
.eq("uuid", userID)
.returns<DBUser>()
.limit(1)
.single()


// Error handling
if (error) {
ErrorHandler.handleSupabaseError(error)
return null
}

if (!data)
return null

// Type conversion: DBUser -> User
const user : User = UserService.parseUser(data)

return user
}


}
37 changes: 37 additions & 0 deletions src/backend/user/Entities/User.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Database } from "../../../utils/database.types";

/**
* This is a dummy-type inherited from the generated Supabase type
*/
export type DBUser = Database['public']['Tables']['members']['Row'];


/**
* The User entity model
*
* @author Henry C. (@yeahlowflicker)
*/
export default class User {
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: number = 0
public department: string = ""
public grade: string = ""
public bio: string = ""


public convertIdentity(): string {
switch (this.identity) {
case 1: return "管理員"
case 2: return "學生"
case 3: return "校友"
case 4: return "教職員"
default: return "用戶"
}
}
}
33 changes: 33 additions & 0 deletions src/backend/user/Services/UserService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import User, { DBUser } from "../Entities/User";

const UserService = {

/**
* Convert a user from default Supabase type to User entity
*
* @param {DBUser} record - The record retrieved from Supabase
* @returns {User} - Converted user entity
*
* @author Henry C. (@yeahlowflicker)
*/
parseUser(record: DBUser) : User {
if (!record || typeof record !== 'object')
throw new Error('Invalid record provided')

if (!record.uuid)
throw new Error('uuid is a required field');

const user = new User()

user.id = record.uuid
user.username = record.name
user.email = record.fk_email
user.identity = record.fk_identity
user.avatar = record.avatar

return user
}

}

export default UserService
17 changes: 17 additions & 0 deletions src/utils/ErrorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { PostgrestError } from "@supabase/supabase-js";

/**
* A universal error handler class.
*
* This will be called by all controllers and is useful for general
* error-handling logic.
*
* @author Henry C. (@yeahlowflicker)
*/
export default class ErrorHandler {

public static handleSupabaseError(error: PostgrestError) {
console.error(error)
}

}

0 comments on commit 3b84979

Please sign in to comment.