-
Notifications
You must be signed in to change notification settings - Fork 1
refactor: refactored api routes to be centeral #834
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| import { ApiRoutes } from "@/routes/route"; | ||
|
|
||
| type ApiBase = keyof typeof ApiRoutes; | ||
|
|
||
| type RouteDescriptor< | ||
| BASE extends ApiBase, | ||
| SUB extends keyof (typeof ApiRoutes)[BASE]["routes"], | ||
| > = { | ||
| base: BASE; | ||
| subRoute?: SUB; | ||
| }; | ||
|
|
||
| export class ApiRoutesManager { | ||
| private readonly apiBasePath: string; | ||
|
|
||
| constructor() { | ||
| this.apiBasePath = "/api/v1"; | ||
| } | ||
|
|
||
| private replaceParams<Path extends string>( | ||
| path: Path, | ||
| params: Record<string, string | number> | ||
| ): string { | ||
| return path.replace(/:([a-zA-Z0-9]+)/g, (_, key) => { | ||
| const value = params[key]; | ||
| if (value === undefined) { | ||
| throw new Error(`Missing parameter: ${key}`); | ||
| } | ||
| return value.toString(); | ||
| }); | ||
| } | ||
|
|
||
| generateUrl< | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's not a full URL... let call it
|
||
| BASE extends ApiBase, | ||
| SUB extends keyof (typeof ApiRoutes)[BASE]["routes"], | ||
| >({ | ||
| route, | ||
| params = {}, | ||
| type = "full", | ||
| }: { | ||
| route: RouteDescriptor<BASE, SUB>; | ||
| params?: Record<string, string | number>; | ||
| type?: "full" | "base"; | ||
| }): string { | ||
| const baseRoute = ApiRoutes[route.base].base; | ||
| const subPath = route.subRoute | ||
| ? ApiRoutes[route.base].routes[route.subRoute] | ||
| : ""; | ||
|
|
||
| let pathSegment = ""; | ||
| switch (type) { | ||
| case "full": | ||
| pathSegment = `${baseRoute}${subPath}`; | ||
| break; | ||
| case "base": | ||
| pathSegment = baseRoute; | ||
| break; | ||
| } | ||
|
|
||
| const replacedPath = this.replaceParams(pathSegment, params); | ||
| return `${this.apiBasePath}${replacedPath}`; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,15 @@ | ||
| export { Web, Landing, Dashboard, StudentSettingsTabId } from "@/routes/route"; | ||
| export { | ||
| Web, | ||
| Landing, | ||
| Dashboard, | ||
| StudentSettingsTabId, | ||
| ApiRoutes, | ||
| } from "@/routes/route"; | ||
| export { clients } from "@/routes/clients"; | ||
| export { | ||
| RoutesManager, | ||
| isValidRoute, | ||
| PayloadOf, | ||
| UrlParamsOf, | ||
| } from "@/routes/core"; | ||
| export { ApiRoutesManager } from "@/routes/api"; |
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I propose exporting all these types and constant in one scope, say ApiRoutesType -> ApiRoutesMap -> ApiRoute.Map |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -70,3 +70,252 @@ export type StudentSettingsTabId = | |
| | "password" | ||
| | "notifications" | ||
| | "topics"; | ||
|
|
||
| export const ApiRouteBaseRoute = "/api/v1"; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The naming here is quiet repetitive. We may call it |
||
|
|
||
| type ApiRoutesType = Record< | ||
| string, | ||
| { base: ApiRouteBase; routes: Record<string, string> } | ||
| >; | ||
|
Comment on lines
+76
to
+79
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's call this |
||
|
|
||
| export type ApiRouteBase = | ||
| | "/auth" | ||
| | "/contact-request" | ||
| | "/user" | ||
| | "/lesson" | ||
| | "/interview" | ||
| | "/availability-slot" | ||
| | "/rating" | ||
| | "/chat" | ||
| | "/plan" | ||
| | "/coupon" | ||
| | "/invite" | ||
| | "/invoice" | ||
| | "/topic" | ||
| | "/asset" | ||
| | "/cache" | ||
| | "/session" | ||
| | "/fawry" | ||
| | "/tx" | ||
| | "/sub" | ||
| | "/confirmation-code" | ||
| | "/report"; | ||
|
|
||
| export const ApiRoutes: ApiRoutesType = { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
| auth: { | ||
| base: "/auth", | ||
| routes: { | ||
| loginWithPassword: "/password", | ||
| loginWithGoogle: "/google", | ||
| refreshToken: "/refresh-token", | ||
| }, | ||
| }, | ||
| contactRequest: { | ||
| base: "/contact-request", | ||
| routes: { | ||
| create: "/", | ||
| }, | ||
| }, | ||
| user: { | ||
| base: "/user", | ||
| routes: { | ||
| create: "/", | ||
| selectInterviewer: "/interviewer/select", | ||
| findCurrentUser: "/current", | ||
| findUsers: "/list", | ||
| uploadUserImage: "/asset", | ||
| uploadTutorAssets: "/asset/tutor", | ||
| findTutorMeta: "/tutor/meta", | ||
| findTutorInfo: "/tutor/info/:tutorId", | ||
| findOnboardedTutors: "/tutor/list/onboarded", | ||
| findPersonalizedTutorStats: "/tutor/stats/personalized", | ||
| findUncontactedTutors: "/tutor/list/uncontacted", | ||
| findTutorStats: "/tutor/stats/:tutor", | ||
| findTutorActivityScores: "/tutor/activity/:tutor", | ||
| findStudioTutors: "/tutor/all/for/studio", | ||
| findStudioTutor: "/tutor/:tutorId/for/studio", | ||
| findFullTutors: "/tutor/full-tutors", | ||
| findTutoringMinutes: "/tutor/tutoring-minutes", | ||
| findPersonalizedStudentStats: "/student/stats/personalized", | ||
| findStudentStats: "/student/stats/:student", | ||
| findStudios: "/studio/list", | ||
| findById: "/:id", | ||
| update: "/:id", | ||
| }, | ||
| }, | ||
| lesson: { | ||
| base: "/lesson", | ||
| routes: { | ||
| create: "/", | ||
| update: "/", | ||
| findAttendedLessonsStats: "/attended/stats", | ||
| findLessons: "/list", | ||
| findLessonById: "/:id", | ||
| cancel: "/:lessonId", | ||
| }, | ||
| }, | ||
| interview: { | ||
| base: "/interview", | ||
| routes: { | ||
| create: "/", | ||
| update: "/", | ||
| find: "/list", | ||
| selectInterviewer: "/select", | ||
| }, | ||
| }, | ||
| availabilitySlot: { | ||
| base: "/availability-slot", | ||
| routes: { | ||
| find: "/", | ||
| set: "/", | ||
| }, | ||
| }, | ||
| rating: { | ||
| base: "/rating", | ||
| routes: { | ||
| createRating: "/", | ||
| findRaterRatings: "/list/rater/:id", | ||
| findRatings: "/list", | ||
| findRateeRatings: "/list/ratee/:id", | ||
| findTutorRatings: "/list/tutor/:id", | ||
| findRatingById: "/:id", | ||
| updateRating: "/:id", | ||
| deleteRating: "/:id", | ||
| }, | ||
| }, | ||
| chat: { | ||
| base: "/chat", | ||
| routes: { | ||
| createRoom: "/new", | ||
| findUserRooms: "/list/rooms/:userId", | ||
| findRoomMessages: "/list/:roomId/messages", | ||
| findRoomByMembers: "/room/by/members/", | ||
| findRoomMembers: "/room/members/:roomId", | ||
| updateRoom: "/room/:roomId", | ||
| }, | ||
| }, | ||
| plan: { | ||
| base: "/plan", | ||
| routes: { | ||
| create: "/", | ||
| find: "/list", | ||
| findById: "/:id", | ||
| update: "/:id", | ||
| delete: "/:id", | ||
| }, | ||
| }, | ||
| coupon: { | ||
| base: "/coupon", | ||
| routes: { | ||
| create: "/", | ||
| findAll: "/list", | ||
| findByCode: "/code/:code", | ||
| findById: "/:id", | ||
| update: "/:id", | ||
| delete: "/:id", | ||
| }, | ||
| }, | ||
| invite: { | ||
| base: "/invite", | ||
| routes: { | ||
| create: "/", | ||
| findAll: "/list", | ||
| findById: "/:id", | ||
| update: "/:id", | ||
| delete: "/:id", | ||
| }, | ||
| }, | ||
| invoice: { | ||
| base: "/invoice", | ||
| routes: { | ||
| find: "/", | ||
| stats: "/stats/:tutorId", | ||
| create: "/", | ||
| update: "/:invoiceId", | ||
| }, | ||
| }, | ||
| topic: { | ||
| base: "/topic", | ||
| routes: { | ||
| createTopic: "/", | ||
| updateTopic: "/:id", | ||
| deleteTopic: "/:id", | ||
| findTopics: "/list", | ||
| findUserTopics: "/of/user", | ||
| addUserTopics: "/of/user", | ||
| deleteUserTopics: "/of/user", | ||
| replaceUserTopics: "/of/user", | ||
| }, | ||
| }, | ||
| asset: { | ||
| base: "/asset", | ||
| routes: { | ||
| sample: "/sample", | ||
| }, | ||
| }, | ||
| cache: { | ||
| base: "/cache", | ||
| routes: { | ||
| flush: "/flush", | ||
| }, | ||
| }, | ||
| session: { | ||
| base: "/session", | ||
| routes: { | ||
| getSessionToken: "/token", | ||
| findSessionMembers: "/:sessionId", | ||
| }, | ||
| }, | ||
| fawry: { | ||
| base: "/fawry", | ||
| routes: { | ||
| payWithCard: "/pay/card", | ||
| payWithRefNum: "/pay/ref-num", | ||
| payWithEWallet: "/pay/e-wallet", | ||
| payWithBankInstallments: "/pay/bank-installments", | ||
| cancelUnpaidOrder: "/cancel-unpaid-order", | ||
| refund: "/refund", | ||
| getAddCardTokenUrl: "/card-token/url", | ||
| findCardTokens: "/card-token/list", | ||
| deleteCardToken: "/card-token", | ||
| getPaymentStatus: "/payment-status", | ||
| setPaymentStatus: "/payment-status", | ||
| syncPaymentStatus: "/payment-status/sync", | ||
| }, | ||
| }, | ||
| transaction: { | ||
| base: "/tx", | ||
| routes: { | ||
| findLast: "/last", | ||
| find: "/list", | ||
| findById: "/:id", | ||
| }, | ||
| }, | ||
| subscription: { | ||
| base: "/sub", | ||
| routes: { | ||
| findUserSubscription: "/user", | ||
| find: "/list", | ||
| findById: "/:id", | ||
| }, | ||
| }, | ||
| confirmationCode: { | ||
| base: "/confirmation-code", | ||
| routes: { | ||
| sendVerifyPhoneCode: "/phone/send", | ||
| verifyPhoneCode: "/phone/verify", | ||
| sendForgetPasswordCode: "/password/send", | ||
| confirmForgetPasswordCode: "/password/confirm", | ||
| sendEmailVerificationCode: "/email/send", | ||
| confirmEmailVerificationCode: "/email/confirm", | ||
| }, | ||
| }, | ||
| report: { | ||
| base: "/report", | ||
| routes: { | ||
| find: "/list", | ||
| create: "/", | ||
| update: "/", | ||
| }, | ||
| }, | ||
| }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think TS convention involves writing generics all in capitals letters.