blueprint details page

This commit is contained in:
Fred KISSIE
2025-10-28 00:14:27 +01:00
parent a05ee2483b
commit 7ce6fadb3d
19 changed files with 482 additions and 66 deletions

View File

@@ -119,6 +119,7 @@ export enum ActionsEnum {
// blueprints
listBlueprints = "listBlueprints",
getBlueprint = "getBlueprint",
applyBlueprint = "applyBlueprint"
}

View File

@@ -5,6 +5,7 @@ export const registry = new OpenAPIRegistry();
export enum OpenAPITags {
Site = "Site",
Org = "Organization",
Blueprint = "Blueprint",
Resource = "Resource",
Role = "Role",
User = "User",

View File

@@ -44,7 +44,7 @@ registry.registerPath({
path: "/org/{orgId}/blueprint",
description:
"Create and Apply a base64 encoded blueprint to an organization",
tags: [OpenAPITags.Org],
tags: [OpenAPITags.Org, OpenAPITags.Blueprint],
request: {
params: applyBlueprintParamsSchema,
body: {

View File

@@ -0,0 +1,110 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { db } from "@server/db";
import { blueprints, orgs } from "@server/db";
import { eq, and } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import stoi from "@server/lib/stoi";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { BlueprintData } from "./types";
const getBlueprintSchema = z
.object({
blueprintId: z
.string()
.transform(stoi)
.pipe(z.number().int().positive()),
orgId: z.string()
})
.strict();
async function query(blueprintId: number, orgId: string) {
// Get the client
const [blueprint] = await db
.select({
blueprintId: blueprints.blueprintId,
name: blueprints.name,
source: blueprints.source,
succeeded: blueprints.succeeded,
orgId: blueprints.orgId,
createdAt: blueprints.createdAt,
message: blueprints.message,
contents: blueprints.contents
})
.from(blueprints)
.leftJoin(orgs, eq(blueprints.orgId, orgs.orgId))
.where(
and(
eq(blueprints.blueprintId, blueprintId),
eq(blueprints.orgId, orgId)
)
)
.limit(1);
if (!blueprint) {
return null;
}
return blueprint;
}
export type GetBlueprintResponse = BlueprintData;
registry.registerPath({
method: "get",
path: "/org/{orgId}/blueprint/{blueprintId}",
description: "Get a blueprint by its blueprint ID.",
tags: [OpenAPITags.Org, OpenAPITags.Blueprint],
request: {
params: getBlueprintSchema
},
responses: {}
});
export async function getBlueprint(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedParams = getBlueprintSchema.safeParse(req.params);
if (!parsedParams.success) {
logger.error(
`Error parsing params: ${fromError(parsedParams.error).toString()}`
);
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { orgId, blueprintId } = parsedParams.data;
const blueprint = await query(blueprintId, orgId);
if (!blueprint) {
return next(
createHttpError(HttpCode.NOT_FOUND, "Client not found")
);
}
return response<GetBlueprintResponse>(res, {
data: blueprint as BlueprintData,
success: true,
error: false,
message: "Client retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -1,2 +1,3 @@
export * from "./listBlueprints";
export * from "./createAndApplyBlueprint";
export * from "./getBlueprint";

View File

@@ -46,6 +46,7 @@ async function queryBlueprints(orgId: string, limit: number, offset: number) {
})
.from(blueprints)
.leftJoin(orgs, eq(blueprints.orgId, orgs.orgId))
.where(eq(blueprints.orgId, orgId))
.limit(limit)
.offset(offset);
return res;
@@ -70,7 +71,7 @@ registry.registerPath({
method: "get",
path: "/org/{orgId}/blueprints",
description: "List all blueprints for a organization.",
tags: [OpenAPITags.Org],
tags: [OpenAPITags.Org, OpenAPITags.Blueprint],
request: {
params: z.object({
orgId: z.string()
@@ -121,10 +122,8 @@ export async function listBlueprints(
return response<ListBlueprintsResponse>(res, {
data: {
blueprints: blueprintsList.map((b) => ({
...b,
createdAt: new Date(b.createdAt * 1000)
})) as BlueprintData[],
blueprints:
blueprintsList as ListBlueprintsResponse["blueprints"],
pagination: {
total: count,
limit,

View File

@@ -2,7 +2,6 @@ import type { Blueprint } from "@server/db";
export type BlueprintSource = "API" | "UI" | "NEWT";
export type BlueprintData = Omit<Blueprint, "source" | "createdAt"> & {
export type BlueprintData = Omit<Blueprint, "source"> & {
source: BlueprintSource;
createdAt: Date;
};

View File

@@ -826,6 +826,13 @@ authenticated.put(
blueprints.createAndApplyBlueprint
);
authenticated.get(
"/org/:orgId/blueprint/:blueprintId",
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.getBlueprint),
blueprints.getBlueprint
);
// Auth routes
export const authRouter = Router();
unauthenticated.use("/auth", authRouter);