Add additional error handling for endpoint

This commit is contained in:
Nate Kelley 2025-07-17 12:06:57 -06:00
parent e55632e074
commit ee8cce71f8
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
9 changed files with 85 additions and 48 deletions

View File

@ -0,0 +1,16 @@
import { getOrganization } from '@buster/database';
import type { GetOrganizationResponse } from '@buster/server-shared/organization';
import { Hono } from 'hono';
import { requireOrganization } from '../../../middleware/auth';
const app = new Hono().use('*', requireOrganization).get('/', async (c) => {
const userOrg = c.get('userOrganizationInfo');
const organization: GetOrganizationResponse = await getOrganization({
organizationId: userOrg.organizationId,
});
return c.json(organization);
});
export default app;

View File

@ -14,7 +14,7 @@ import { requireOrganizationAdmin } from '../../../middleware/auth';
* Updates organization settings
* Currently supports updating organization color palettes
*/
export async function updateOrganizationHandler(
async function updateOrganizationHandler(
organizationId: string,
request: UpdateOrganizationRequest,
user: User
@ -48,32 +48,34 @@ export async function updateOrganizationHandler(
}
// Create route module for the update endpoint
const app = new Hono().put(
'/',
requireOrganizationAdmin,
zValidator('json', UpdateOrganizationRequestSchema),
async (c) => {
const request = c.req.valid('json');
const app = new Hono()
.use('*', requireOrganizationAdmin)
.put('/', zValidator('json', UpdateOrganizationRequestSchema), async (c) => {
const request = await c.req.valid('json');
const user = c.get('busterUser');
const userOrg = c.get('userOrganizationInfo');
const organizationId = userOrg.organizationId;
//const role = userOrg.role;
// if (!canEditOrganization(role)) {
// throw new HTTPException(403, {
// message: 'User does not have permission to edit organization',
// });
// }
try {
const response: UpdateOrganizationResponse = await updateOrganizationHandler(
organizationId,
request,
user
);
return c.json(response);
} catch (error) {
console.error('Error in updateOrganizationHandler:', {
organizationId,
userId: user.id,
requestFields: Object.keys(request),
error: error instanceof Error ? error.message : error,
});
const response: UpdateOrganizationResponse = await updateOrganizationHandler(
organizationId,
request,
user
);
return c.json(response);
}
);
throw new HTTPException(500, {
message: 'Failed to update organization',
});
}
});
export default app;

View File

@ -1,11 +1,13 @@
import { Hono } from 'hono';
import { requireAuth } from '../../../middleware/auth';
import GET from './GET';
import PUT from './PUT';
const app = new Hono()
// Apply authentication globally to ALL routes in this router
.use('*', requireAuth)
// Mount the modular routes
.route('/', GET)
.route('/', PUT);
export default app;

View File

@ -97,7 +97,8 @@ export async function requireUser(c: Context, next: Next) {
}
}
export const requireOrganization = async (c: Context) => {
// Utility function to get user organization (can be called from other middleware)
const getUserOrganization = async (c: Context) => {
const user = c.get('busterUser');
const userOrganizationInfo = c.get('userOrganizationInfo');
@ -116,28 +117,28 @@ export const requireOrganization = async (c: Context) => {
return userOrg;
};
export const requireOrganizationAdmin = async (c: Context) => {
// Middleware version that can be used in route chains
export const requireOrganization = async (c: Context, next: Next) => {
await getUserOrganization(c);
await next();
};
export const requireOrganizationAdmin = async (c: Context, next: Next) => {
const user = c.get('busterUser');
if (!user?.id) {
console.warn('This is likely an issue where requireAuth middleware was not called first');
return c.json({
message: 'User not authenticated',
});
return c.json({ message: 'User not authenticated' }, 401);
}
const userOrg = await requireOrganization(c);
const userOrg = await getUserOrganization(c);
const isAdmin = isOrganizationAdmin(userOrg.role);
if (!isAdmin) {
return c.json(
{
message: 'User is not an organization admin',
},
403
);
return c.json({ message: 'User is not an organization admin' }, 403);
}
return true;
// If all checks pass, continue to the next middleware/handler
return await next();
};

View File

@ -1,13 +1,13 @@
import { type InferSelectModel, and, eq, isNull } from 'drizzle-orm';
import { and, eq, isNull } from 'drizzle-orm';
import { z } from 'zod';
import { db } from '../../connection';
import { organizations } from '../../schema';
import { getUserOrganizationId } from './organizations';
import type { OrganizationColorPalettes } from '../../schema-types';
// Organization Color Palette schema
const OrganizationColorPaletteSchema = z.object({
id: z.string(),
color: z.array(z.string()),
colors: z.array(z.string()),
});
// Input validation schema
@ -30,7 +30,7 @@ export const updateOrganization = async (params: UpdateOrganizationInput): Promi
// Build update data
const updateData: {
updatedAt: string;
organizationColorPalettes?: Array<{ id: string; color: string[] }>;
organizationColorPalettes?: OrganizationColorPalettes;
} = {
updatedAt: new Date().toISOString(),
};
@ -44,12 +44,6 @@ export const updateOrganization = async (params: UpdateOrganizationInput): Promi
.update(organizations)
.set(updateData)
.where(and(eq(organizations.id, organizationId), isNull(organizations.deletedAt)));
console.info('Organization updated successfully:', {
organizationId,
updatedFields: organizationColorPalettes !== undefined ? ['organizationColorPalettes'] : [],
});
} catch (error) {
console.error('Error updating organization:', {
organizationId,

View File

@ -1,7 +1,7 @@
// Organization Color Palette Types
export type OrganizationColorPalette = {
id: string;
color: string[];
colors: string[];
};
export type OrganizationColorPalettes = OrganizationColorPalette[];

View File

@ -35,4 +35,14 @@ This structure ensures a consistent and organized approach to managing types acr
### Pagination
If we need to use pagination for a type we can take advantage of the generic types found in the type-utilities/pagination file
If we need to use pagination for a type we can take advantage of the generic types found in the type-utilities/pagination file
## Database partity
There are cirumstances where we need to ensure that a schema in server-shared and a database type are the same. We should use a pattern like this:
```
type _OrganizationEqualityCheck = Expect<Equal<Organization, typeof organizations.$inferSelect>>;
```
Where organizations is imported as a "type" from @buster/database. This ensure that we are maintaining type parity between the two packages.

View File

@ -3,9 +3,17 @@ import { z } from 'zod';
import type { Equal, Expect } from '../type-utilities';
import { OrganizationRoleSchema } from './roles.types';
// Hex color validation schema for 3 or 6 digit hex codes
const HexColorSchema = z
.string()
.regex(
/^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/,
'Must be a valid 3 or 6 digit hex color code (e.g., #fff or #ffffff)'
);
export const OrganizationColorPaletteSchema = z.object({
id: z.string(),
color: z.array(z.string()),
colors: z.array(HexColorSchema).min(1).max(25),
});
export const OrganizationSchema = z.object({

View File

@ -1,6 +1,10 @@
import type { z } from 'zod';
import { OrganizationSchema } from './organization.types';
export const GetOrganizationResponseSchema = OrganizationSchema;
export type GetOrganizationResponse = z.infer<typeof GetOrganizationResponseSchema>;
export const UpdateOrganizationResponseSchema = OrganizationSchema;
export type UpdateOrganizationResponse = z.infer<typeof UpdateOrganizationResponseSchema>;