-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature: Clean architecture entities, controllers and services
This commit implements the clean architecture concept. The "features" folder has been introduced, containing controllers, entities, and services for different modules. Currently the event and user modules have been implemented with basic fetch functions (get all and find by ID). The ErrorHandler class is introduced for universal error handling. - Added User, UserController and UserService - Added Event, EventController and EventService - Added ErrorHandler (2024/12/14)
- Loading branch information
1 parent
51c298f
commit 07f9fbd
Showing
7 changed files
with
344 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
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 && rangeEnd) | ||
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 | ||
} | ||
|
||
// Type conversion: DBEvent -> Event | ||
const event : Event = EventService.parseEvent(data) | ||
|
||
return event | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 = ""; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import Event, { DBEvent } from "../Entities/Event" | ||
|
||
export default class EventService { | ||
|
||
public static parseEvent(record: DBEvent) : Event { | ||
const event = new Event() | ||
|
||
event.id = record.id | ||
event.name = record.name ? record.name : "" | ||
event.type = record.type ? record.type : 0 | ||
event.description = record.description ? record.description : "" | ||
event.startTime = record.start_time ? record.start_time : "" | ||
event.endTime = record.end_time ? record.end_time : "" | ||
event.location = record.location ? record.location : "" | ||
event.fee = record.fee ? record.fee : 0 | ||
event.userID = record.user_id | ||
event.createdAt = record.created_at | ||
|
||
return event | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
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 && rangeEnd) | ||
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 | ||
} | ||
|
||
// Type conversion: DBUser -> User | ||
const user : User = UserService.parseUser(data) | ||
|
||
return user | ||
} | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
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) { | ||
default: return "用戶" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import User, { DBUser } from "../Entities/User"; | ||
|
||
export default class 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) | ||
*/ | ||
public static parseUser(record: DBUser) : User { | ||
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 | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
|
||
} |