diff --git a/bun.lockb b/bun.lockb index 2b53a02..8c79618 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/api/package.json b/packages/api/package.json index 369830e..ea6943f 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -23,15 +23,15 @@ "typescript": "^5.0.0" }, "dependencies": { - "@elysiajs/cors": "^0.7.2", - "@elysiajs/swagger": "^0.8.5", + "@elysiajs/cors": "^1.3.0", + "@elysiajs/swagger": "^1.3.0", "@ftoggle/common": "workspace:*", "@ftoggle/db": "workspace:*", "@lucia-auth/adapter-postgresql": "^3.0.0", "arctic": "^1.1.6", "dotenv": "^16.4.4", "drizzle-orm": "^0.29.1", - "elysia": "^0.8.17", + "elysia": "^1.3.0", "lucia": "^3.0.1" } } diff --git a/packages/api/src/controllers/usersController.ts b/packages/api/src/controllers/usersController.ts index b246008..d060f53 100644 --- a/packages/api/src/controllers/usersController.ts +++ b/packages/api/src/controllers/usersController.ts @@ -100,6 +100,7 @@ export class UsersController { } return user; } catch (err) { + console.log('Hallo'); // Catch any errors thrown and log them console.error(err); // Throw a generic validation error diff --git a/packages/api/src/handlers/githubLoginHandlers.ts b/packages/api/src/handlers/githubLoginHandlers.ts index 8705af5..d6c2bbf 100644 --- a/packages/api/src/handlers/githubLoginHandlers.ts +++ b/packages/api/src/handlers/githubLoginHandlers.ts @@ -34,7 +34,7 @@ export const githubLoginHandler = new Elysia() async ({ cookie, query, set }) => { const code = query.code; const state = query.state; - const storedState = cookie.github_oauth_state.get(); + const storedState = cookie.github_oauth_state.value; if (!code || !state || !storedState || state !== storedState) { return new Response(null, { status: HttpStatus.BadRequest }); diff --git a/packages/api/src/hooks/errorHook.ts b/packages/api/src/hooks/errorHook.ts index 30785f1..b49486e 100644 --- a/packages/api/src/hooks/errorHook.ts +++ b/packages/api/src/hooks/errorHook.ts @@ -1,4 +1,4 @@ -import { ErrorHandler } from 'elysia'; +import type { ErrorHandler } from 'elysia'; import postgres from 'postgres'; import { AuthenticationError, @@ -13,23 +13,31 @@ import { /** Hook to handle errors thrown by the api. */ export const errorHook: ErrorHandler = (context) => { const { code, error } = context; + if (code === 'VALIDATION') { + return new Response(JSON.stringify(error), { status: 400 }); + } if ( - code === 'VALIDATION' || error instanceof DuplicateRecordError || error instanceof BadRequestError ) { return new Response(error.message, { status: 400 }); - } else if (error instanceof AuthenticationError) { + } + if (error instanceof AuthenticationError) { return new Response(error.message, { status: 401 }); - } else if (error instanceof AuthorizationError) { + } + if (error instanceof AuthorizationError) { return new Response(error.message, { status: 403 }); - } else if (error instanceof RecordDoesNotExistError) { + } + if (error instanceof RecordDoesNotExistError) { return new Response(error.message, { status: 404 }); - } else if (error instanceof postgres.PostgresError) { + } + if (error instanceof postgres.PostgresError) { return handlePostgresErrors(context); } - console.error(error.message); - console.error(error.stack); + if (error instanceof Error) { + console.error(error.message); + console.error(error.stack); + } return new Response('Internal Server Error', { status: 500 }); }; @@ -40,16 +48,19 @@ const POSTGRES_DUPLICATE_PROJECT_USER_ERROR_MESSAGE = 'duplicate key value violates unique constraint "projects_users_project_id_userId_pk"'; const handlePostgresErrors: ErrorHandler = ({ error }) => { - if (error.message === POSTGRES_DUPLICATE_USERNAME_ERROR_MESSAGE) { - return new Response('User with that username already exists.', { - status: 400, - }); - } else if (error.message === POSTGRES_DUPLICATE_PROJECT_USER_ERROR_MESSAGE) { - return new Response('User is already a project user of the project.', { - status: 400, - }); - } - console.error(error.message); - console.error(error.stack); + if (error instanceof Error) { + if (error.message === POSTGRES_DUPLICATE_USERNAME_ERROR_MESSAGE) { + return new Response('User with that username already exists.', { + status: 400, + }); + } + if (error.message === POSTGRES_DUPLICATE_PROJECT_USER_ERROR_MESSAGE) { + return new Response('User is already a project user of the project.', { + status: 400, + }); + } + console.error(error.message); + console.error(error.stack); + } return new Response('Internal Server Error', { status: 500 }); }; diff --git a/packages/api/src/hooks/requestUserHooks.ts b/packages/api/src/hooks/requestUserHooks.ts index 0351ac1..9726b4a 100644 --- a/packages/api/src/hooks/requestUserHooks.ts +++ b/packages/api/src/hooks/requestUserHooks.ts @@ -9,7 +9,7 @@ import { AuthenticationError, AuthorizationError } from '../errors/apiErrors'; * @throws An {@link AuthenticationError} if user cannot be validated */ const validateUserToken = async (context: Context) => { - const accessToken = context.cookie['accessToken'].get(); + const accessToken = context.cookie['accessToken'].value; if (!accessToken) { throw new AuthenticationError('Missing cookie: "accessToken"'); @@ -23,7 +23,7 @@ const validateUserToken = async (context: Context) => { export const requestUserHooks = new Elysia({ name: 'hooks:getRequestUser', -}).derive((context) => ({ +}).derive({ as: 'global' }, (context) => ({ /** * Checks if user is signed in. * @throws An {@link AuthenticationError} if user cannot be validated diff --git a/packages/api/src/hooks/requiresPermissionHook.ts b/packages/api/src/hooks/requiresPermissionHook.ts index b6035be..5b2d054 100644 --- a/packages/api/src/hooks/requiresPermissionHook.ts +++ b/packages/api/src/hooks/requiresPermissionHook.ts @@ -10,7 +10,7 @@ export const hasPermissions = new Elysia({ name: 'hooks:hasPermissions', }) .use(requestUserHooks) - .derive((context) => { + .derive({ as: 'global' }, (context) => { return { hasUserPermissions: async (requiredPermissions: UserPermission[]) => { const { user } = await context.getRequestUser(); diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 70e7bb5..7784f72 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -8,7 +8,7 @@ const port = Bun.env.API_PORT ?? 8080; const app = new Elysia() .use(cors()) .use(swagger()) - .onError(errorHook) + .onError((context) => errorHook(context)) .use(routes) .listen(port, (server) => { console.log(`Started server on http://${server?.hostname}:${server?.port}`); diff --git a/packages/ui/package.json b/packages/ui/package.json index 404b8e4..4464a6e 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -27,7 +27,7 @@ "@angular/platform-browser": "^17.0.0", "@angular/platform-browser-dynamic": "^17.0.0", "@angular/router": "^17.0.0", - "@elysiajs/eden": "^0.7.7", + "@elysiajs/eden": "^1.3.2", "@ftoggle/api": "workspace:*", "@ftoggle/common": "workspace:*", "luxon": "^3.4.4", diff --git a/packages/ui/src/app/services/api-token.service.ts b/packages/ui/src/app/services/api-token.service.ts index a82938f..cb5ed6c 100644 --- a/packages/ui/src/app/services/api-token.service.ts +++ b/packages/ui/src/app/services/api-token.service.ts @@ -20,7 +20,7 @@ export class ApiTokenService { }) { const { projectId, name, environmentId, type } = fields; try { - await this.apiService.api.projects[projectId].apiTokens.post({ + await this.apiService.api.projects({ projectId }).apiTokens.post({ name, environmentId, type, @@ -32,8 +32,9 @@ export class ApiTokenService { async getApiTokens(projectId: string) { try { - const response = - await this.apiService.api.projects[projectId].apiTokens.get(); + const response = await this.apiService.api + .projects({ projectId }) + .apiTokens.get(); this._apiTokens.set(response.data?.tokens ?? []); } catch (err) { console.error('Error getting api tokens', err); @@ -42,9 +43,10 @@ export class ApiTokenService { async deleteApiToken(projectId: string, apiTokenId: string) { try { - await this.apiService.api.projects[projectId].apiTokens[ - apiTokenId - ].delete(); + await this.apiService.api + .projects({ projectId }) + .apiTokens({ apiTokenId }) + .delete(); } catch (err) { console.error('Error creating api token', err); } diff --git a/packages/ui/src/app/services/api.service.ts b/packages/ui/src/app/services/api.service.ts index 5d97fce..bd8eadf 100644 --- a/packages/ui/src/app/services/api.service.ts +++ b/packages/ui/src/app/services/api.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; -import { edenTreaty } from '@elysiajs/eden'; +import { treaty } from '@elysiajs/eden'; import { App } from '@ftoggle/api'; import { environment } from '../../environments/environment'; import { paths } from '../app.routes'; @@ -11,8 +11,8 @@ import { paths } from '../app.routes'; export class ApiService { constructor(private router: Router) {} - private treaty = edenTreaty(environment.apiBaseUrl, { - transform: (response) => { + private treaty = treaty(environment.apiBaseUrl, { + onResponse: (response) => { // Fails authentication, old/invalid token. Clear token and send to login if (response.status === 401) { this.router.navigate([paths.login]); diff --git a/packages/ui/src/app/services/conditions.service.ts b/packages/ui/src/app/services/conditions.service.ts index db01ad6..6bddfe7 100644 --- a/packages/ui/src/app/services/conditions.service.ts +++ b/packages/ui/src/app/services/conditions.service.ts @@ -21,11 +21,13 @@ export class ConditionsService { value: string; }[], ) { - return await this.apiService.api.projects[projectId].features[ - featureName - ].environments[environmentName].conditions.post({ - conditions, - }); + return await this.apiService.api + .projects({ projectId }) + .features({ featureName }) + .environments({ environmentName }) + .conditions.post({ + conditions, + }); } async getConditions( @@ -33,10 +35,11 @@ export class ConditionsService { featureName: string, environmentName: string, ) { - const res = - await this.apiService.api.projects[projectId].features[ - featureName - ].environments[environmentName].conditions.get(); + const res = await this.apiService.api + .projects({ projectId }) + .features({ featureName }) + .environments({ environmentName }) + .conditions.get(); return res.data?.conditions ?? []; } @@ -47,14 +50,17 @@ export class ConditionsService { environmentName: string, changes: { operator: string; value: string; values: string[] }, ) { - await this.apiService.api.projects[projectId].features[ - featureName - ].environments[environmentName].conditions[conditionToEdit.id].patch({ - operator: changes.operator, - ...(SingleValueOperatorsValues.includes(changes.operator) - ? { value: changes.value } - : { values: changes.values }), - }); + await this.apiService.api + .projects({ projectId }) + .features({ featureName }) + .environments({ environmentName }) + .conditions({ conditionId: conditionToEdit.id }) + .patch({ + operator: changes.operator, + ...(SingleValueOperatorsValues.includes(changes.operator) + ? { value: changes.value } + : { values: changes.values }), + }); } async deleteCondition( @@ -62,8 +68,11 @@ export class ConditionsService { featureName: string, environmentName: string, ) { - await this.apiService.api.projects[condition.projectId].features[ - featureName - ].environments[environmentName].conditions[condition.id].delete(); + await this.apiService.api + .projects({ projectId: condition.projectId }) + .features({ featureName }) + .environments({ environmentName }) + .conditions({ conditionId: condition.id }) + .delete(); } } diff --git a/packages/ui/src/app/services/contextFields.service.ts b/packages/ui/src/app/services/contextFields.service.ts index 15b55d9..a9acb16 100644 --- a/packages/ui/src/app/services/contextFields.service.ts +++ b/packages/ui/src/app/services/contextFields.service.ts @@ -17,7 +17,7 @@ export class ContextFieldsService { description?: string, ) { try { - await this.apiService.api.projects[projectId].contextFields.post({ + await this.apiService.api.projects({ projectId }).contextFields.post({ name, description, }); @@ -36,8 +36,9 @@ export class ContextFieldsService { ): Promise { let cFields: ContextFieldsTableItem[] = []; try { - const { data, error, response } = - await this.apiService.api.projects[projectId].contextFields.get(); + const { data, error, response } = await this.apiService.api + .projects({ projectId }) + .contextFields.get(); if (!response.ok) { throw error; } @@ -54,9 +55,12 @@ export class ContextFieldsService { contextField: ContextFieldsTableItem, ) { try { - await this.apiService.api.projects[projectId].contextFields[ - contextField.name - ].delete(); + await this.apiService.api + .projects({ projectId }) + .contextFields({ + contextFieldName: contextField.name, + }) + .delete(); } catch (error) { console.error('Error deleting context field', error); } diff --git a/packages/ui/src/app/services/environments.service.ts b/packages/ui/src/app/services/environments.service.ts index 77b5350..59ce16b 100644 --- a/packages/ui/src/app/services/environments.service.ts +++ b/packages/ui/src/app/services/environments.service.ts @@ -13,8 +13,7 @@ export class EnvironmentsService { async createEnvironment(projectId: string, environment: { name: string }) { try { - await this.apiService.api.projects[projectId].environments.post({ - $query: { projectId }, + await this.apiService.api.projects({ projectId }).environments.post({ environmentName: environment.name, }); } catch (err) { @@ -24,8 +23,9 @@ export class EnvironmentsService { async getEnvironments(projectId: string) { try { - const response = - await this.apiService.api.projects[projectId].environments.get(); + const response = await this.apiService.api + .projects({ projectId }) + .environments.get(); this._environments.set(response.data?.data.environments ?? []); } catch (err) { @@ -35,9 +35,10 @@ export class EnvironmentsService { async deleteEnvironment(projectId: string, environmentName: string) { try { - await this.apiService.api.projects[projectId].environments[ - environmentName - ].delete(); + await this.apiService.api + .projects({ projectId }) + .environments({ environmentName }) + .delete(); } catch (err) { console.error('Error deleting environment', err); } diff --git a/packages/ui/src/app/services/features.service.ts b/packages/ui/src/app/services/features.service.ts index b629d88..15c1804 100644 --- a/packages/ui/src/app/services/features.service.ts +++ b/packages/ui/src/app/services/features.service.ts @@ -13,8 +13,7 @@ export class FeaturesService { async createFeature(projectId: string, feature: { name: string }) { try { - await this.apiService.api.projects[projectId].features.post({ - $query: { projectId }, + await this.apiService.api.projects({ projectId }).features.post({ name: feature.name, }); } catch (err) { @@ -24,8 +23,9 @@ export class FeaturesService { async getFeatures(projectId: string) { try { - const response = - await this.apiService.api.projects[projectId].features.get(); + const response = await this.apiService.api + .projects({ projectId }) + .features.get(); this._features.set(response.data?.features ?? []); return; } catch (error) { @@ -39,9 +39,11 @@ export class FeaturesService { environmentName: string, ) { try { - await this.apiService.api.projects[projectId].features[ - featureName - ].environments[environmentName].put(); + await this.apiService.api + .projects({ projectId }) + .features({ featureName }) + .environments({ environmentName }) + .put(); } catch (err) { console.error('Error toggling feature', err); } @@ -49,9 +51,10 @@ export class FeaturesService { async deleteFeature(featureName: string, projectId: string) { try { - await this.apiService.api.projects[projectId].features[ - featureName - ].delete(); + await this.apiService.api + .projects({ projectId }) + .features({ featureName }) + .delete(); } catch (error) { console.error('Error deleting feature', error); } diff --git a/packages/ui/src/app/services/projects.service.ts b/packages/ui/src/app/services/projects.service.ts index 38b10dd..8ba1ea3 100644 --- a/packages/ui/src/app/services/projects.service.ts +++ b/packages/ui/src/app/services/projects.service.ts @@ -32,7 +32,7 @@ export class ProjectsService { async deleteProject(projectId: string) { try { - await this.apiService.api.projects[projectId].delete(); + await this.apiService.api.projects({ projectId }).delete(); } catch (err) { console.error('Error deleting project', err); } diff --git a/packages/ui/src/app/services/users.service.ts b/packages/ui/src/app/services/users.service.ts index 4c1373b..714ca65 100644 --- a/packages/ui/src/app/services/users.service.ts +++ b/packages/ui/src/app/services/users.service.ts @@ -17,34 +17,34 @@ export class UsersService { const { data, response, error } = await this.apiService.api.users.roles.get(); if (!response.ok) { - console.error('Error getting users', error?.message); + console.error('Error getting users', error?.value); } this._users.set(data?.users ?? []); } async setApproval(user: UsersTableItem, approval: boolean) { - const { response, data } = await this.apiService.api.users[user.id].patch({ - isApproved: approval, - }); + const { response, data } = await this.apiService.api + .users({ userId: user.id }) + .patch({ isApproved: approval }); if (!response.ok) { console.error('Error setting user approval', data); } } async addRole(user: UsersTableItem, role: UserRole) { - const { response, error } = await this.apiService.api.users[ - user.id - ].roles.post({ - role, - }); + const { response, error } = await this.apiService.api + .users({ userId: user.id }) + .roles.post({ role }); if (!response.ok) { - console.error('Error adding role', error?.message); + console.error('Error adding role', error?.value); } } async removeRole(user: UsersTableItem, role: RolesTableItem) { - const { response, data } = - await this.apiService.api.users[user.id].roles[role.id].delete(); + const { response, data } = await this.apiService.api + .users({ userId: user.id }) + .roles({ roleId: role.id }) + .delete(); if (!response.ok) { console.error('Error removing role:', data); }