Skip to content

Commit

Permalink
feat: add Zod schemas to all API endpoints (#1727)
Browse files Browse the repository at this point in the history
<!--
Thanks for opening a PR! Your contribution is much appreciated!
-->
  • Loading branch information
casperiv0 authored Jul 7, 2023
1 parent a29c6f3 commit cb518a9
Show file tree
Hide file tree
Showing 58 changed files with 250 additions and 129 deletions.
4 changes: 3 additions & 1 deletion apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"vitest": "^0.32.2"
},
"dependencies": {
"@anatine/zod-openapi": "^2.0.1",
"@discordjs/rest": "^1.7.1",
"@paralleldrive/cuid2": "^2.2.1",
"@prisma/client": "^4.16.1",
Expand Down Expand Up @@ -79,6 +80,7 @@
"sharp": "^0.32.1",
"socket.io": "^4.7.0",
"undici": "^5.22.1",
"use-intl": "^2.15.1"
"use-intl": "^2.15.1",
"zod": "^3.21.4"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { manyToManyHelper } from "lib/data/many-to-many";
import type * as APITypes from "@snailycad/types/api";
import { Permissions, UsePermissions } from "middlewares/use-permissions";
import { citizenInclude } from "controllers/citizen/CitizenController";
import { ZodSchema } from "~/lib/zod-schema";

@Controller("/admin/import/citizens")
@UseBeforeEach(IsAuth)
Expand All @@ -44,7 +45,7 @@ export class ImportCitizensController {
permissions: [Permissions.ImportCitizens, Permissions.ManageCitizens],
})
async importCitizensViaBodyData(
@BodyParams() body: any,
@BodyParams() @ZodSchema(IMPORT_CITIZENS_ARR) body: unknown,
): Promise<APITypes.PostImportCitizensData> {
return this.importCitizensHandler(body);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type { Prisma, VehicleInspectionStatus, VehicleTaxStatus } from "@prisma/
import { getLastOfArray, manyToManyHelper } from "lib/data/many-to-many";
import type * as APITypes from "@snailycad/types/api";
import { Permissions, UsePermissions } from "middlewares/use-permissions";
import { ZodSchema } from "~/lib/zod-schema";

const vehiclesInclude = { ...citizenInclude.vehicles.include, citizen: true };

Expand Down Expand Up @@ -116,7 +117,9 @@ export class ImportVehiclesController {
@UsePermissions({
permissions: [Permissions.ImportRegisteredVehicles],
})
async importVehicles(@BodyParams() body: any): Promise<APITypes.PostImportVehiclesData> {
async importVehicles(
@BodyParams() @ZodSchema(VEHICLE_SCHEMA_ARR) body: unknown,
): Promise<APITypes.PostImportVehiclesData> {
return importVehiclesHandler(body);
}

Expand Down Expand Up @@ -144,7 +147,7 @@ export class ImportVehiclesController {
}
}

export async function importVehiclesHandler(body: unknown[]) {
export async function importVehiclesHandler(body: unknown) {
const data = validateSchema(VEHICLE_SCHEMA_ARR, body);

return Promise.all(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { citizenInclude } from "controllers/citizen/CitizenController";
import type { Prisma } from "@prisma/client";
import type * as APITypes from "@snailycad/types/api";
import { Permissions, UsePermissions } from "middlewares/use-permissions";
import { ZodSchema } from "~/lib/zod-schema";

const weaponsInclude = { ...citizenInclude.weapons.include, citizen: true };

Expand Down Expand Up @@ -62,7 +63,9 @@ export class ImportWeaponsController {
@UsePermissions({
permissions: [Permissions.ImportRegisteredWeapons],
})
async importWeaponsViaBodyData(@BodyParams() body: any): Promise<APITypes.PostImportWeaponsData> {
async importWeaponsViaBodyData(
@BodyParams() @ZodSchema(WEAPON_SCHEMA_ARR) body: unknown,
): Promise<APITypes.PostImportWeaponsData> {
return importWeaponsHandler(body);
}

Expand Down Expand Up @@ -114,7 +117,7 @@ export class ImportWeaponsController {
}
}

export async function importWeaponsHandler(body: unknown[]) {
export async function importWeaponsHandler(body: unknown) {
const data = validateSchema(WEAPON_SCHEMA_ARR, body);

return prisma.$transaction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
import type { MiscCadSettings } from "@snailycad/types";
import { createFeaturesObject } from "middlewares/is-enabled";
import { hasPermission } from "@snailycad/permissions";
import { ZodSchema } from "~/lib/zod-schema";

@Controller("/admin/manage/cad-settings")
@ContentType("application/json")
Expand Down Expand Up @@ -120,7 +121,7 @@ export class CADSettingsController {
async updateCadSettings(
@Context("sessionUserId") sessionUserId: string,
@Context("cad") cad: cad,
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(CAD_SETTINGS_SCHEMA) body: unknown,
): Promise<APITypes.PutCADSettingsData> {
const data = validateSchema(CAD_SETTINGS_SCHEMA, body);

Expand Down Expand Up @@ -171,7 +172,7 @@ export class CADSettingsController {
async updateCadFeatures(
@Context("cad") cad: cad & { features?: Record<Feature, boolean> },
@Context("sessionUserId") sessionUserId: string,
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(DISABLED_FEATURES_SCHEMA) body: unknown,
): Promise<APITypes.PutCADFeaturesData> {
const data = validateSchema(DISABLED_FEATURES_SCHEMA, body);

Expand Down Expand Up @@ -219,7 +220,7 @@ export class CADSettingsController {
async updateMiscSettings(
@Context("sessionUserId") sessionUserId: string,
@Context("cad") cad: cad & { miscCadSettings: MiscCadSettings },
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(CAD_MISC_SETTINGS_SCHEMA) body: unknown,
): Promise<APITypes.PutCADMiscSettingsData> {
const data = validateSchema(CAD_MISC_SETTINGS_SCHEMA, body);

Expand Down Expand Up @@ -289,7 +290,7 @@ export class CADSettingsController {
})
async updateDefaultPermissions(
@Context("cad") cad: cad,
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(UPDATE_DEFAULT_PERMISSIONS_SCHEMA) body: unknown,
@Context("sessionUserId") sessionUserId: string,
): Promise<APITypes.PutCADDefaultPermissionsData> {
const data = validateSchema(UPDATE_DEFAULT_PERMISSIONS_SCHEMA, body);
Expand Down Expand Up @@ -339,7 +340,7 @@ export class CADSettingsController {
})
async updateApiToken(
@Context("cad") cad: cad,
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(API_TOKEN_SCHEMA) body: unknown,
@Context("sessionUserId") sessionUserId: string,
): Promise<APITypes.PutCADApiTokenData> {
const data = validateSchema(API_TOKEN_SCHEMA, body);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { UsePermissions } from "middlewares/use-permissions";
import { performDiscordRequest } from "lib/discord/performDiscordRequest";
import { AuditLogActionType, createAuditLogEntry } from "@snailycad/audit-logger/server";
import { parseDiscordGuildIds } from "lib/discord/utils";
import { ZodSchema } from "~/lib/zod-schema";

const guildId = process.env.DISCORD_SERVER_ID;

Expand Down Expand Up @@ -97,7 +98,7 @@ export class DiscordSettingsController {
})
async setRoleTypes(
@Context("cad") cad: cad,
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(DISCORD_SETTINGS_SCHEMA) body: unknown,
@Context("sessionUserId") sessionUserId: string,
): Promise<APITypes.PostCADDiscordRolesData> {
if (!guildId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { performDiscordRequest } from "lib/discord/performDiscordRequest";
import { AuditLogActionType } from "@snailycad/audit-logger";
import { createAuditLogEntry } from "@snailycad/audit-logger/server";
import { parseDiscordGuildIds } from "lib/discord/utils";
import { ZodSchema } from "~/lib/zod-schema";

const guildId = process.env.DISCORD_SERVER_ID;

Expand Down Expand Up @@ -98,7 +99,7 @@ export class DiscordWebhooksController {
async setWebhookTypes(
@Context("cad")
cad: cad & { miscCadSettings: (MiscCadSettings & { webhooks?: DiscordWebhook[] }) | null },
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(DISCORD_WEBHOOKS_SCHEMA) body: unknown,
@Context("sessionUserId") sessionUserId: string,
): Promise<APITypes.PostCADDiscordWebhooksData> {
const name = cad.name || "SnailyCAD";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import sharp from "sharp";
import { getLastOfArray, manyToManyHelper } from "lib/data/many-to-many";
import { allowedFileExtensions } from "@snailycad/config";
import { ExtendedBadRequest } from "~/exceptions/extended-bad-request";
import { ZodSchema } from "~/lib/zod-schema";

@Controller("/admin/manage/cad-settings/live-map")
@ContentType("application/json")
Expand All @@ -27,7 +28,7 @@ export class CADSettingsLiveMapController {
async updateMiscSettings(
@Context("sessionUserId") sessionUserId: string,
@Context("cad") cad: cad & { miscCadSettings: MiscCadSettings },
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(LIVE_MAP_SETTINGS) body: unknown,
): Promise<APITypes.PutCADMiscSettingsData> {
const data = validateSchema(LIVE_MAP_SETTINGS, body);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { prisma } from "lib/data/prisma";
import { validateSchema } from "lib/data/validate-schema";
import { IsAuth } from "middlewares/auth/is-auth";
import { Permissions, UsePermissions } from "middlewares/use-permissions";
import { ZodSchema } from "~/lib/zod-schema";

@Controller("/admin/manage/cad-settings/webhooks")
@ContentType("application/json")
Expand All @@ -29,7 +30,7 @@ export class WebhooksController {
permissions: [Permissions.ManageCADSettings],
})
async saveWebhooks(
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(RAW_WEBHOOKS_SCHEMA) body: unknown,
@Context("cad") cad: cad,
@Context("sessionUserId") sessionUserId: string,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { UPDATE_EMPLOYEE_SCHEMA } from "@snailycad/schemas";
import { EmployeeAsEnum } from "@snailycad/types";
import { ExtendedBadRequest } from "src/exceptions/extended-bad-request";
import { AuditLogActionType, createAuditLogEntry } from "@snailycad/audit-logger/server";
import { ZodSchema } from "~/lib/zod-schema";

const businessInclude = {
user: {
Expand Down Expand Up @@ -109,7 +110,7 @@ export class AdminManageBusinessesController {
async updateBusinessEmployee(
@PathParams("id") employeeId: string,
@Context("sessionUserId") sessionUserId: string,
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(UPDATE_EMPLOYEE_SCHEMA) body: unknown,
) {
const data = validateSchema(UPDATE_EMPLOYEE_SCHEMA, body);
const employee = await prisma.employee.findFirst({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import generateBlurPlaceholder from "lib/images/generate-image-blur-data";
import { AuditLogActionType, createAuditLogEntry } from "@snailycad/audit-logger/server";
import { isFeatureEnabled } from "lib/upsert-cad";
import { leoProperties, unitProperties } from "utils/leo/includes";
import { ZodSchema } from "~/lib/zod-schema";

@UseBeforeEach(IsAuth)
@Controller("/admin/manage/citizens")
Expand Down Expand Up @@ -119,7 +120,7 @@ export class AdminManageCitizensController {
})
async updateCitizen(
@PathParams("id") id: string,
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(CREATE_CITIZEN_SCHEMA.partial()) body: unknown,
@Context("sessionUserId") sessionUserId: string,
@Context("cad") cad: { features: Record<Feature, boolean> },
): Promise<APITypes.PutManageCitizenByIdData> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { validateSchema } from "lib/data/validate-schema";
import { IsAuth } from "middlewares/auth/is-auth";
import { UsePermissions, Permissions } from "middlewares/use-permissions";
import type * as APITypes from "@snailycad/types/api";
import { ZodSchema } from "~/lib/zod-schema";

@Controller("/admin/manage/custom-fields")
@UseBeforeEach(IsAuth)
Expand Down Expand Up @@ -46,7 +47,7 @@ export class AdminManageCustomFieldsController {
permissions: [Permissions.ManageCustomFields],
})
async createCustomField(
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(CUSTOM_FIELDS_SCHEMA) body: unknown,
@Context("sessionUserId") sessionUserId: string,
): Promise<APITypes.PostManageCustomFieldsData> {
const data = validateSchema(CUSTOM_FIELDS_SCHEMA, body);
Expand Down Expand Up @@ -74,7 +75,7 @@ export class AdminManageCustomFieldsController {
permissions: [Permissions.ManageCustomFields],
})
async updateCustomField(
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(CUSTOM_FIELDS_SCHEMA) body: unknown,
@PathParams("id") id: string,
@Context("sessionUserId") sessionUserId: string,
): Promise<APITypes.PutManageCustomFieldsData> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import process from "node:process";
import type * as APITypes from "@snailycad/types/api";
import { AuditLogActionType, createAuditLogEntry } from "@snailycad/audit-logger/server";
import { defaultPermissions } from "@snailycad/permissions";
import { ZodSchema } from "~/lib/zod-schema";

@Controller("/admin/manage/custom-roles")
@UseBeforeEach(IsAuth)
Expand Down Expand Up @@ -66,7 +67,7 @@ export class AdminManageCustomRolesController {
})
@Description("Create a new custom role.")
async createCustomRole(
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(CUSTOM_ROLE_SCHEMA) body: unknown,
@Context("sessionUserId") sessionUserId: string,
): Promise<APITypes.PostCustomRolesData> {
const data = validateSchema(CUSTOM_ROLE_SCHEMA, body);
Expand Down Expand Up @@ -104,7 +105,7 @@ export class AdminManageCustomRolesController {
})
@Description("Update a custom role by its ID.")
async updateCustomRole(
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(CUSTOM_ROLE_SCHEMA) body: unknown,
@PathParams("id") id: string,
@Context("sessionUserId") sessionUserId: string,
): Promise<APITypes.PutCustomRoleByIdData> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { manyToManyHelper } from "lib/data/many-to-many";
import type * as APITypes from "@snailycad/types/api";
import { AuditLogActionType, createAuditLogEntry } from "@snailycad/audit-logger/server";
import { isDiscordIdInUse } from "lib/discord/utils";
import { ZodSchema } from "~/lib/zod-schema";

const manageUsersSelect = (selectCitizens: boolean) =>
({
Expand Down Expand Up @@ -205,7 +206,7 @@ export class ManageUsersController {
async updateUserPermissionsById(
@Context("sessionUserId") sessionUserId: string,
@PathParams("id") userId: string,
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(PERMISSIONS_SCHEMA) body: unknown,
): Promise<APITypes.PutManageUserPermissionsByIdData> {
const data = validateSchema(PERMISSIONS_SCHEMA, body);
const user = await prisma.user.findUnique({
Expand Down Expand Up @@ -247,7 +248,7 @@ export class ManageUsersController {
async updateUserRolesById(
@Context("sessionUserId") sessionUserId: string,
@PathParams("id") userId: string,
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(ROLES_SCHEMA) body: unknown,
): Promise<APITypes.PutManageUserByIdRolesData> {
const data = validateSchema(ROLES_SCHEMA, body);
const user = await prisma.user.findUnique({
Expand Down Expand Up @@ -296,7 +297,7 @@ export class ManageUsersController {
async updateUserById(
@Context("sessionUserId") sessionUserId: string,
@PathParams("id") userId: string,
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(UPDATE_USER_SCHEMA) body: unknown,
): Promise<APITypes.PutManageUserByIdData> {
const data = validateSchema(UPDATE_USER_SCHEMA, body);
const user = await prisma.user.findUnique({
Expand Down Expand Up @@ -391,7 +392,7 @@ export class ManageUsersController {
@Context("user") authUser: User,
@PathParams("id") userId: string,
@PathParams("type") banType: "ban" | "unban",
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(BAN_SCHEMA) body: unknown,
): Promise<APITypes.PostManageUserBanUnbanData> {
if (!["ban", "unban"].includes(banType)) {
throw new NotFound("notFound");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
combinedUnitProperties,
combinedEmsFdUnitProperties,
} from "utils/leo/includes";
import { ZodSchema } from "~/lib/zod-schema";

const ACTIONS = ["SET_DEPARTMENT_DEFAULT", "SET_DEPARTMENT_NULL", "DELETE_UNIT"] as const;
type Action = (typeof ACTIONS)[number];
Expand Down Expand Up @@ -364,7 +365,7 @@ export class AdminManageUnitsController {
async updateCallsignUnit(
@Context("sessionUserId") sessionUserId: string,
@PathParams("unitId") unitId: string,
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(UPDATE_UNIT_CALLSIGN_SCHEMA.partial()) body: unknown,
@Context("cad") cad: cad & { features?: Record<Feature, boolean> },
): Promise<APITypes.PutManageUnitCallsignData> {
const data = validateSchema(UPDATE_UNIT_CALLSIGN_SCHEMA.partial(), body);
Expand Down Expand Up @@ -435,7 +436,7 @@ export class AdminManageUnitsController {
async updateUnit(
@Context("sessionUserId") sessionUserId: string,
@PathParams("id") id: string,
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(UPDATE_UNIT_SCHEMA.partial()) body: unknown,
@Context("cad")
cad: cad & { miscCadSettings: MiscCadSettings; features?: Record<Feature, boolean> },
): Promise<APITypes.PutManageUnitData> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { CREATE_PENAL_CODE_GROUP_SCHEMA } from "@snailycad/schemas";
import { validateSchema } from "lib/data/validate-schema";
import { UsePermissions, Permissions } from "middlewares/use-permissions";
import type * as APITypes from "@snailycad/types/api";
import { ZodSchema } from "~/lib/zod-schema";

@Controller("/admin/penal-code-group")
@UseBeforeEach(IsAuth)
Expand Down Expand Up @@ -39,7 +40,7 @@ export class PenalCodeGroupController {
permissions: [Permissions.ManageValuePenalCode],
})
async createPenalCodeGroup(
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(CREATE_PENAL_CODE_GROUP_SCHEMA) body: unknown,
): Promise<APITypes.PostPenalCodeGroupsData> {
const data = validateSchema(CREATE_PENAL_CODE_GROUP_SCHEMA, body);

Expand All @@ -59,7 +60,7 @@ export class PenalCodeGroupController {
})
async editPenalCodeGroup(
@PathParams("id") id: string,
@BodyParams() body: unknown,
@BodyParams() @ZodSchema(CREATE_PENAL_CODE_GROUP_SCHEMA) body: unknown,
): Promise<APITypes.PutPenalCodeGroupsData> {
const data = validateSchema(CREATE_PENAL_CODE_GROUP_SCHEMA, body);

Expand Down
Loading

0 comments on commit cb518a9

Please sign in to comment.