This commit is contained in:
Milo Schwartz 2024-10-02 23:39:14 -04:00
commit 7dbf4307e7
No known key found for this signature in database
9 changed files with 198 additions and 60 deletions

View file

@ -31,6 +31,9 @@ export const resources = sqliteTable("resources", {
siteId: integer("siteId").references(() => sites.siteId, {
onDelete: "cascade",
}),
orgId: integer("orgId").references(() => orgs.orgId, {
onDelete: "cascade",
}),
name: text("name").notNull(),
subdomain: text("subdomain"),
});

View file

@ -17,32 +17,34 @@ unauthenticated.get("/", (_, res) => {
// Authenticated Root routes
export const authenticated = Router();
authenticated.put("/site", site.createSite);
authenticated.get("/site/:siteId", site.getSite);
authenticated.get("/sites", site.listSites);
authenticated.post("/site/:siteId", site.updateSite);
authenticated.delete("/site/:siteId", site.deleteSite);
authenticated.put("/org", org.createOrg);
authenticated.get("/org/:orgId", org.getOrg);
authenticated.get("/orgs", org.listOrgs);
authenticated.get("/org/:orgId", org.getOrg);
authenticated.post("/org/:orgId", org.updateOrg);
authenticated.delete("/org/:orgId", org.deleteOrg);
authenticated.put("/resource", resource.createResource);
authenticated.put("/org/:orgId/site", site.createSite);
authenticated.get("/org/:orgId/sites", site.listSites);
authenticated.get("/site/:siteId", site.getSite);
authenticated.post("/site/:siteId", site.updateSite);
authenticated.delete("/site/:siteId", site.deleteSite);
authenticated.put("/org/:orgId/site/:siteId/resource", resource.createResource);
authenticated.get("/site/:siteId/resources", resource.listResources);
authenticated.get("/org/:orgId/resources", resource.listResources);
authenticated.get("/resource/:resourceId", resource.getResource);
authenticated.get("/resources", resource.listResources);
authenticated.post("/resource/:resourceId", resource.updateResource);
authenticated.delete("/resource/:resourceId", resource.deleteResource);
authenticated.put("/target", target.createTarget);
authenticated.put("/resource/:resourceId/target", target.createTarget);
authenticated.get("/resource/:resourceId/targets", target.listTargets);
authenticated.get("/target/:targetId", target.getTarget);
authenticated.get("/targets", target.listTargets);
authenticated.post("/target/:targetId", target.updateTarget);
authenticated.delete("/target/:targetId", target.deleteTarget);
authenticated.get("/user/:userId", user.getUser);
authenticated.get("/users", user.listUsers);
// authenticated.get("/org/:orgId/users", user.???); // TODO: Implement this
authenticated.get("/user/:userId", user.getUser);
authenticated.delete("/user/:userId", user.deleteUser);
// Auth routes

View file

@ -6,9 +6,13 @@ import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
const createResourceParamsSchema = z.object({
siteId: z.number().int().positive(),
orgId: z.number().int().positive(),
});
// Define Zod schema for request body validation
const createResourceSchema = z.object({
siteId: z.number().int().positive(),
name: z.string().min(1).max(255),
subdomain: z.string().min(1).max(255).optional(),
});
@ -26,7 +30,20 @@ export async function createResource(req: Request, res: Response, next: NextFunc
);
}
const { siteId, name, subdomain } = parsedBody.data;
const { name, subdomain } = parsedBody.data;
// Validate request params
const parsedParams = createResourceParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { siteId, orgId } = parsedParams.data;
// Generate a unique resourceId
const resourceId = "subdomain" // TODO: create the subdomain here
@ -35,6 +52,7 @@ export async function createResource(req: Request, res: Response, next: NextFunc
const newResource = await db.insert(resources).values({
resourceId,
siteId,
orgId,
name,
subdomain,
}).returning();

View file

@ -7,10 +7,16 @@ import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { sql, eq } from 'drizzle-orm';
const listResourcesParamsSchema = z.object({
siteId: z.coerce.number().int().positive().optional(),
orgId: z.coerce.number().int().positive().optional(),
}).refine(data => !!data.siteId !== !!data.orgId, {
message: "Either siteId or orgId must be provided, but not both",
});
const listResourcesSchema = z.object({
limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)),
offset: z.string().optional().transform(Number).pipe(z.number().int().nonnegative().default(0)),
siteId: z.string().optional().transform(Number).pipe(z.number().int().positive()),
limit: z.coerce.number().int().positive().default(10),
offset: z.coerce.number().int().nonnegative().default(0),
});
export async function listResources(req: Request, res: Response, next: NextFunction): Promise<any> {
@ -24,10 +30,20 @@ export async function listResources(req: Request, res: Response, next: NextFunct
)
);
}
const { limit, offset } = parsedQuery.data;
const { limit, offset, siteId } = parsedQuery.data;
const parsedParams = listResourcesParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { siteId, orgId } = parsedParams.data;
let baseQuery = db
let baseQuery: any = db
.select({
resourceId: resources.resourceId,
name: resources.name,
@ -37,11 +53,14 @@ export async function listResources(req: Request, res: Response, next: NextFunct
.from(resources)
.leftJoin(sites, eq(resources.siteId, sites.siteId));
let countQuery = db.select({ count: sql<number>`cast(count(*) as integer)` }).from(resources);
let countQuery: any = db.select({ count: sql<number>`cast(count(*) as integer)` }).from(resources);
if (siteId) {
baseQuery = baseQuery.where(eq(resources.siteId, siteId));
countQuery = countQuery.where(eq(resources.siteId, siteId));
} else if (orgId) {
baseQuery = baseQuery.where(eq(resources.orgId, orgId));
countQuery = countQuery.where(eq(resources.orgId, orgId));
}
const resourcesList = await baseQuery.limit(limit).offset(offset);

View file

@ -1,19 +1,70 @@
import { Request, Response, NextFunction } from "express";
import { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import { db } from '@server/db';
import { sites } from '@server/db/schema';
import response from "@server/utils/response";
import HttpCode from "@server/types/HttpCode";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
// define zod type here
const createSiteParamsSchema = z.object({
orgId: z.number().int().positive(),
});
export async function createSite(
req: Request,
res: Response,
next: NextFunction,
): Promise<any> {
return response<null>(res, {
data: null,
// Define Zod schema for request body validation
const createSiteSchema = z.object({
name: z.string().min(1).max(255),
subdomain: z.string().min(1).max(255).optional(),
pubKey: z.string().optional(),
subnet: z.string().optional(),
});
export async function createSite(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
// Validate request body
const parsedBody = createSiteSchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedBody.error.errors.map(e => e.message).join(', ')
)
);
}
const { name, subdomain, pubKey, subnet } = parsedBody.data;
// Validate request params
const parsedParams = createSiteParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { orgId } = parsedParams.data;
// Create new site in the database
const newSite = await db.insert(sites).values({
orgId,
name,
subdomain,
pubKey,
subnet,
}).returning();
return res.status(HttpCode.CREATED).send(
response(res, {
data: newSite[0],
success: true,
error: false,
message: "Logged in successfully",
status: HttpCode.OK,
});
}
message: "Site created successfully",
status: HttpCode.CREATED,
})
);
} catch (error) {
next(error);
}
}

View file

@ -7,10 +7,13 @@ import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { sql, eq } from 'drizzle-orm';
const listSitesParamsSchema = z.object({
orgId: z.string().optional().transform(Number).pipe(z.number().int().positive()),
});
const listSitesSchema = z.object({
limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)),
offset: z.string().optional().transform(Number).pipe(z.number().int().nonnegative().default(0)),
orgId: z.string().optional().transform(Number).pipe(z.number().int().positive()),
});
export async function listSites(req: Request, res: Response, next: NextFunction): Promise<any> {
@ -25,7 +28,19 @@ export async function listSites(req: Request, res: Response, next: NextFunction)
);
}
const { limit, offset, orgId } = parsedQuery.data;
const { limit, offset } = parsedQuery.data;
const parsedParams = listSitesParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { orgId } = parsedParams.data;
let baseQuery: any = db
.select({

View file

@ -6,8 +6,11 @@ import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
const createTargetSchema = z.object({
const createTargetParamsSchema = z.object({
resourceId: z.string().uuid(),
});
const createTargetSchema = z.object({
ip: z.string().ip(),
method: z.string().min(1).max(10),
port: z.number().int().min(1).max(65535),
@ -29,7 +32,22 @@ export async function createTarget(req: Request, res: Response, next: NextFuncti
const targetData = parsedBody.data;
const newTarget = await db.insert(targets).values(targetData).returning();
const parsedParams = createTargetParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { resourceId } = parsedParams.data;
const newTarget = await db.insert(targets).values({
resourceId,
...targetData
}).returning();
return res.status(HttpCode.CREATED).send(
response(res, {

View file

@ -7,10 +7,13 @@ import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { sql, eq } from 'drizzle-orm';
const listTargetsParamsSchema = z.object({
resourceId: z.string().optional()
});
const listTargetsSchema = z.object({
limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)),
offset: z.string().optional().transform(Number).pipe(z.number().int().nonnegative().default(0)),
resourceId: z.string().optional(),
});
export async function listTargets(req: Request, res: Response, next: NextFunction): Promise<any> {
@ -25,7 +28,19 @@ export async function listTargets(req: Request, res: Response, next: NextFunctio
);
}
const { limit, offset, resourceId } = parsedQuery.data;
const { limit, offset } = parsedQuery.data;
const parsedParams = listTargetsParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { resourceId } = parsedParams.data;
let baseQuery: any = db
.select({