Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified bun.lockb
Binary file not shown.
6 changes: 3 additions & 3 deletions packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
1 change: 1 addition & 0 deletions packages/api/src/controllers/usersController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/handlers/githubLoginHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
49 changes: 30 additions & 19 deletions packages/api/src/hooks/errorHook.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ErrorHandler } from 'elysia';
import type { ErrorHandler } from 'elysia';
import postgres from 'postgres';
import {
AuthenticationError,
Expand All @@ -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 });
};

Expand All @@ -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 });
};
4 changes: 2 additions & 2 deletions packages/api/src/hooks/requestUserHooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"');
Expand All @@ -23,7 +23,7 @@ const validateUserToken = async (context: Context) => {

export const requestUserHooks = new Elysia({
name: 'hooks:getRequestUser',
}).derive((context) => ({
}).derive({ as: 'global' }, (context) => ({
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should double check that this is the correct way to do this.

/**
* Checks if user is signed in.
* @throws An {@link AuthenticationError} if user cannot be validated
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/hooks/requiresPermissionHook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const hasPermissions = new Elysia({
name: 'hooks:hasPermissions',
})
.use(requestUserHooks)
.derive((context) => {
.derive({ as: 'global' }, (context) => {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should double check that this is the correct way to do this.

return {
hasUserPermissions: async (requiredPermissions: UserPermission[]) => {
const { user } = await context.getRequestUser();
Expand Down
2 changes: 1 addition & 1 deletion packages/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}`);
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
14 changes: 8 additions & 6 deletions packages/ui/src/app/services/api-token.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand All @@ -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);
}
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/src/app/services/api.service.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -11,8 +11,8 @@ import { paths } from '../app.routes';
export class ApiService {
constructor(private router: Router) {}

private treaty = edenTreaty<App>(environment.apiBaseUrl, {
transform: (response) => {
private treaty = treaty<App>(environment.apiBaseUrl, {
onResponse: (response) => {
// Fails authentication, old/invalid token. Clear token and send to login
if (response.status === 401) {
this.router.navigate([paths.login]);
Expand Down
49 changes: 29 additions & 20 deletions packages/ui/src/app/services/conditions.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,25 @@ 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(
projectId: string,
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 ?? [];
}

Expand All @@ -47,23 +50,29 @@ 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(
condition: ConditionsTableItem,
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();
}
}
16 changes: 10 additions & 6 deletions packages/ui/src/app/services/contextFields.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
Expand All @@ -36,8 +36,9 @@ export class ContextFieldsService {
): Promise<ContextFieldsTableItem[]> {
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;
}
Expand All @@ -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);
}
Expand Down
15 changes: 8 additions & 7 deletions packages/ui/src/app/services/environments.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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) {
Expand All @@ -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);
}
Expand Down
Loading