Skip to content
Open
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
13 changes: 13 additions & 0 deletions src/modules/event-category/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,16 @@ export const getEventCategories: ApiRequestHandler<
const result = await service.getEventCategories();
return ok(res, result);
};

export const updateEventCategory: ApiRequestHandler<{ id: number }> = async (req, res) => {
const params = schemas.eventCategoryScopedSchema.parse(req.params);
const body = schemas.updateEventCategorySchema.parse(req.body);
const result = await service.updateEventCategory(params.id, body);
return ok(res, result);
};

export const deleteEventCategory: ApiRequestHandler<true> = async (req, res) => {
const params = schemas.eventCategoryScopedSchema.parse(req.params);
await service.deleteEventCategory(params.id);
return ok(res, true);
};
27 changes: 26 additions & 1 deletion src/modules/event-category/repository.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { and, asc, eq, isNull } from "drizzle-orm";
import { and, asc, eq, isNull, sql } from "drizzle-orm";
import { db, schema } from "@/db/index.js";
import { dbAction, unreachable } from "@/lib/helpers.js";

Expand All @@ -24,3 +24,28 @@ export const findMany = dbAction(async () => {
.where(and(eq(schema.eventCategory.isActive, true), isNull(schema.eventCategory.deletedAt)))
.orderBy(asc(schema.eventCategory.name));
});

export const updateEventCategory = dbAction(
async (
id: number,
data: {
name?: string | undefined;
isActive?: boolean | undefined;
},
) => {
const [updated] = await db
.update(schema.eventCategory)
.set(data)
.where(and(eq(schema.eventCategory.id, id), isNull(schema.eventCategory.deletedAt)))
.returning({ id: schema.eventCategory.id });
return updated;
},
);

export const deleteEventCategory = dbAction(async (id: number) => {
const result = await db
.update(schema.eventCategory)
.set({ deletedAt: sql`NOW()` })
.where(and(eq(schema.eventCategory.id, id), isNull(schema.eventCategory.deletedAt)));
return result;
});
3 changes: 3 additions & 0 deletions src/modules/event-category/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,7 @@ const router: Router = Router();
router.get("/", controller.getEventCategories);
router.post("/", requireUserType("admin"), controller.createEventType);

router.patch("/:id", requireUserType("admin"), controller.updateEventCategory);
router.delete("/:id", requireUserType("admin"), controller.deleteEventCategory);

export default router;
22 changes: 22 additions & 0 deletions src/modules/event-category/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,26 @@ export const createEventCategorySchema = z.object({
.max(256, { error: "Name cannot be longer than 256 characters" }),
});

export const updateEventCategorySchema = z
.object({
name: z
.string({ error: "Expected a string as category name" })
.trim()
.min(3, { error: "Name is too short" })
.max(256, { error: "Name cannot be longer than 256 characters" })
.optional(),
isActive: z.boolean({ error: "isActive must be a boolean" }).optional(),
})
.strict()
.refine((d) => d.name !== undefined || d.isActive !== undefined, {
error: "At least one of name or isActive must be provided",
});

export const eventCategoryScopedSchema = z
.object({
id: z.coerce.number({ error: "Invalid category ID" }).int({ error: "Invalid category ID" }),
})
.strict();

export type CreateEventCategorySchema = z.output<typeof createEventCategorySchema>;
export type UpdateEventCategorySchema = z.output<typeof updateEventCategorySchema>;
12 changes: 12 additions & 0 deletions src/modules/event-category/service.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { NotFoundError } from "@/lib/errors.js";
import * as repository from "./repository.js";
import type * as schemas from "./schema.js";

Expand All @@ -8,3 +9,14 @@ export async function createEventCategory(input: schemas.CreateEventCategorySche
export async function getEventCategories() {
return await repository.findMany();
}

export async function updateEventCategory(id: number, input: schemas.UpdateEventCategorySchema) {
const updated = await repository.updateEventCategory(id, input);
if (updated == null) throw new NotFoundError("Event category not found");
return updated;
}

export async function deleteEventCategory(id: number) {
const result = await repository.deleteEventCategory(id);
if ((result.rowCount ?? 0) === 0) throw new NotFoundError("Event category not found");
}
7 changes: 7 additions & 0 deletions src/modules/event-type/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,13 @@ export const createEventType: ApiRequestHandler<{
return ok(res, result);
};

export const updateEventType: ApiRequestHandler<{ id: number }> = async (req, res) => {
const params = schemas.eventTypeScopedSchema.parse(req.params);
const body = schemas.updateEventTypeSchema.parse(req.body);
const result = await service.updateEventType(params.id, body);
return ok(res, result);
};

export const deleteEventType: ApiRequestHandler<true> = async (req, res) => {
const params = schemas.eventTypeScopedSchema.parse(req.params);
await service.deleteEventType(params.id);
Expand Down
20 changes: 20 additions & 0 deletions src/modules/event-type/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,23 @@ export const deleteEventType = dbAction(async (id: number) => {
.where(and(eq(schema.eventType.id, id), isNull(schema.eventType.deletedAt)));
return result;
});

export const updateEventType = dbAction(
async (
id: number,
data: {
name?: string | undefined;
isActive?: boolean | undefined;
venuePolicy?: EventTypeVenuePolicy | undefined;
collaborationPolicy?: EventTypeCollaborationPolicy | undefined;
workflowTemplateId?: number | undefined;
},
) => {
const [updated] = await db
.update(schema.eventType)
.set(data)
.where(and(eq(schema.eventType.id, id), isNull(schema.eventType.deletedAt)))
.returning({ id: schema.eventType.id });
return updated;
},
);
1 change: 1 addition & 0 deletions src/modules/event-type/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ router.get("/", controller.getEventTypes);
router.post("/", requireUserType("admin"), controller.createEventType);

router.get("/:id", controller.getEventType);
router.patch("/:id", requireUserType("admin"), controller.updateEventType);
router.delete("/:id", requireUserType("admin"), controller.deleteEventType);

router.use("/:id/children", childrenRouter);
Expand Down
30 changes: 30 additions & 0 deletions src/modules/event-type/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,40 @@ export const createEventTypeSchema = z
})
.strict();

export const updateEventTypeSchema = z
.object({
name: z
.string({ error: "Invalid name value" })
.trim()
.nonempty({ error: "Name cannot be empty" })
.max(256, { error: "Name cannot exceed 256 characters" })
.optional(),
isActive: z.boolean({ error: "isActive must be a boolean" }).optional(),
venuePolicy: z.enum(EVENT_TYPE_VENUE_POLICY, { error: "Invalid venue policy" }).optional(),
collaborationPolicy: z
.enum(EVENT_TYPE_COLLABORATION_POLICY, { error: "Invalid collaboration policy" })
.optional(),
workflowTemplateId: z.coerce
.number({ error: "Invalid workflow template ID" })
.int({ error: "Invalid workflow template ID" })
.optional(),
})
.strict()
.refine(
(d) =>
d.name !== undefined ||
d.isActive !== undefined ||
d.venuePolicy !== undefined ||
d.collaborationPolicy !== undefined ||
d.workflowTemplateId !== undefined,
{ error: "At least one field must be provided" },
);

export const eventTypeScopedSchema = z
.object({
id: z.coerce.number({ error: "Invalid event type ID" }).int({ error: "Invalid event type ID" }),
})
.strict();

export type CreateEventTypeSchema = z.output<typeof createEventTypeSchema>;
export type UpdateEventTypeSchema = z.output<typeof updateEventTypeSchema>;
6 changes: 6 additions & 0 deletions src/modules/event-type/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ export async function createEventType(input: schemas.CreateEventTypeSchema) {
});
}

export async function updateEventType(id: number, input: schemas.UpdateEventTypeSchema) {
const updated = await repository.updateEventType(id, input);
if (updated == null) throw new NotFoundError("Event type not found");
return updated;
}

export async function deleteEventType(eventTypeId: number) {
const result = await repository.deleteEventType(eventTypeId);
if ((result.rowCount ?? 0) === 0) throw new NotFoundError("Event type not found");
Expand Down
17 changes: 17 additions & 0 deletions src/modules/facility/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,20 @@ export const findFacilityanagedEntity = dbAction(async (facilityId: number) => {

return relatedManagedEntity;
});

export const updateFacility = dbAction(async (id: number, data: { name: string }) => {
const [updated] = await db
.update(schema.facility)
.set({ name: data.name })
.where(and(eq(schema.facility.id, id), isNull(schema.facility.deletedAt)))
.returning({ id: schema.facility.id });
return updated;
});

export const softDeleteFacility = dbAction(async (id: number) => {
const result = await db
.update(schema.facility)
.set({ deletedAt: sql`NOW()` })
.where(and(eq(schema.facility.id, id), isNull(schema.facility.deletedAt)));
return result;
});
13 changes: 13 additions & 0 deletions src/modules/organization/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,16 @@ export const getOrganization: ApiRequestHandler<{
const result = await service.getOrganization(params.id);
return ok(res, result);
};

export const updateOrganization: ApiRequestHandler<{ id: number }> = async (req, res) => {
const params = schemas.organizationScopedSchema.parse(req.params);
const body = schemas.updateOrganizationSchema.parse(req.body);
const result = await service.updateOrganization(params.id, body);
return ok(res, result);
};

export const deleteOrganization: ApiRequestHandler<true> = async (req, res) => {
const params = schemas.organizationScopedSchema.parse(req.params);
await service.deleteOrganization(params.id);
return ok(res, true);
};
19 changes: 18 additions & 1 deletion src/modules/organization/member/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,12 @@ export const assignOrganizationMemberRoles = dbAction(
if (newRoleIds.has(userRole.roleId)) {
roleIdToId.set(userRole.roleId, userRole.id);
} else {
// currently has, but not in the new list.
toBeDeletedPks.push(userRole.id);
}
}

const deletedUserRoles = userRoles.filter((ur) => toBeDeletedPks.includes(ur.id));

const existingRoleIds = new Set(userRoles.map((ur) => ur.roleId));
const toBeAdded = data.roleIds.filter((roleId) => !existingRoleIds.has(roleId));

Expand Down Expand Up @@ -113,6 +114,22 @@ export const assignOrganizationMemberRoles = dbAction(
}
}

for (const oldUr of deletedUserRoles) {
const newUrId = roleIdToId.get(oldUr.roleId);
if (newUrId != null) {
await tx
.update(schema.workflowInstanceStepAssignment)
.set({ userRoleId: newUrId })
.where(
and(
eq(schema.workflowInstanceStepAssignment.userRoleId, oldUr.id),
eq(schema.workflowInstanceStepAssignment.status, "pending"),
isNull(schema.workflowInstanceStepAssignment.deletedAt),
),
);
}
}

return data.roleIds
.map((roleId) => ({ id: roleIdToId.get(roleId), roleId }))
.filter((entry): entry is { id: number; roleId: number } => entry.id != null);
Expand Down
Loading