diff --git a/src/backend/event/Controllers/EventController.ts b/src/backend/event/Controllers/EventController.ts new file mode 100644 index 0000000..6a6d986 --- /dev/null +++ b/src/backend/event/Controllers/EventController.ts @@ -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().then( + * (events: Array) => { ... } + * ) + * + * @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} - 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 | null> { + + const query = supabase + .from(EVENT_TABLE_NAME) + .select(fields) + .returns>() + + 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 = [] + + + // 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().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 { + + const { data, error } = await supabase + .from(EVENT_TABLE_NAME) + .select(fields) + .eq("id", eventID) + .returns() + .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 + } + +} \ No newline at end of file diff --git a/src/backend/event/Entities/Event.ts b/src/backend/event/Entities/Event.ts new file mode 100644 index 0000000..d9219c6 --- /dev/null +++ b/src/backend/event/Entities/Event.ts @@ -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 = ""; + +} \ No newline at end of file diff --git a/src/backend/event/Services/EventService.ts b/src/backend/event/Services/EventService.ts new file mode 100644 index 0000000..251f896 --- /dev/null +++ b/src/backend/event/Services/EventService.ts @@ -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 \ No newline at end of file diff --git a/src/backend/user/Controllers/UserController.ts b/src/backend/user/Controllers/UserController.ts new file mode 100644 index 0000000..53ed579 --- /dev/null +++ b/src/backend/user/Controllers/UserController.ts @@ -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().then( + * (users: Array) => { ... } + * ) + * + * @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} - 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 | null> { + + const query = supabase + .from(USER_TABLE_NAME) + .select(fields) + .returns>() + + 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 = [] + + // 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().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 { + + const { data, error } = await supabase + .from(USER_TABLE_NAME) + .select(fields) + .eq("uuid", userID) + .returns() + .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 + } + + +} \ No newline at end of file diff --git a/src/backend/user/Entities/User.ts b/src/backend/user/Entities/User.ts new file mode 100644 index 0000000..0e595f0 --- /dev/null +++ b/src/backend/user/Entities/User.ts @@ -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 "用戶" + } + } +} \ No newline at end of file diff --git a/src/backend/user/Services/UserService.ts b/src/backend/user/Services/UserService.ts new file mode 100644 index 0000000..f3b552d --- /dev/null +++ b/src/backend/user/Services/UserService.ts @@ -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 \ No newline at end of file diff --git a/src/utils/ErrorHandler.ts b/src/utils/ErrorHandler.ts new file mode 100644 index 0000000..3a54f08 --- /dev/null +++ b/src/utils/ErrorHandler.ts @@ -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) + } + +} \ No newline at end of file