mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-16 23:41:11 +02:00
Merge branch 'main' of https://github.com/fosrl/pangolin
This commit is contained in:
commit
7dbf4307e7
9 changed files with 198 additions and 60 deletions
|
@ -30,26 +30,20 @@ async function insertDummyData() {
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
// Insert dummy users
|
// Insert dummy users
|
||||||
await db.insert(users).values([
|
// await db.insert(users).values([
|
||||||
{
|
// {
|
||||||
orgId: org1.orgId,
|
// email: "john@fossorial.com",
|
||||||
name: "John Doe",
|
// groups: "admin,developer",
|
||||||
email: "john@fossorial.com",
|
// },
|
||||||
groups: "admin,developer",
|
// {
|
||||||
},
|
// email: "jane@fossorial.com",
|
||||||
{
|
// groups: "developer",
|
||||||
orgId: org1.orgId,
|
// },
|
||||||
name: "Jane Smith",
|
// {
|
||||||
email: "jane@fossorial.com",
|
// email: "bob@fosrl.io",
|
||||||
groups: "developer",
|
// groups: "admin",
|
||||||
},
|
// },
|
||||||
{
|
// ]);
|
||||||
orgId: org2.orgId,
|
|
||||||
name: "Bob Johnson",
|
|
||||||
email: "bob@fosrl.io",
|
|
||||||
groups: "admin",
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Insert dummy exit nodes
|
// Insert dummy exit nodes
|
||||||
const exitNode1 = db
|
const exitNode1 = db
|
||||||
|
@ -107,6 +101,7 @@ async function insertDummyData() {
|
||||||
.values({
|
.values({
|
||||||
resourceId: `web.${site1.subdomain}.${org1.domain}`,
|
resourceId: `web.${site1.subdomain}.${org1.domain}`,
|
||||||
siteId: site1.siteId,
|
siteId: site1.siteId,
|
||||||
|
orgId: site1.orgId,
|
||||||
name: "Web Server",
|
name: "Web Server",
|
||||||
subdomain: "web",
|
subdomain: "web",
|
||||||
})
|
})
|
||||||
|
@ -118,6 +113,7 @@ async function insertDummyData() {
|
||||||
.values({
|
.values({
|
||||||
resourceId: `web2.${site1.subdomain}.${org1.domain}`,
|
resourceId: `web2.${site1.subdomain}.${org1.domain}`,
|
||||||
siteId: site1.siteId,
|
siteId: site1.siteId,
|
||||||
|
orgId: site1.orgId,
|
||||||
name: "Web Server 2",
|
name: "Web Server 2",
|
||||||
subdomain: "web2",
|
subdomain: "web2",
|
||||||
})
|
})
|
||||||
|
@ -129,6 +125,7 @@ async function insertDummyData() {
|
||||||
.values({
|
.values({
|
||||||
resourceId: `db.${site2.subdomain}.${org2.domain}`,
|
resourceId: `db.${site2.subdomain}.${org2.domain}`,
|
||||||
siteId: site2.siteId,
|
siteId: site2.siteId,
|
||||||
|
orgId: site2.orgId,
|
||||||
name: "Database",
|
name: "Database",
|
||||||
subdomain: "db",
|
subdomain: "db",
|
||||||
})
|
})
|
||||||
|
|
|
@ -31,6 +31,9 @@ export const resources = sqliteTable("resources", {
|
||||||
siteId: integer("siteId").references(() => sites.siteId, {
|
siteId: integer("siteId").references(() => sites.siteId, {
|
||||||
onDelete: "cascade",
|
onDelete: "cascade",
|
||||||
}),
|
}),
|
||||||
|
orgId: integer("orgId").references(() => orgs.orgId, {
|
||||||
|
onDelete: "cascade",
|
||||||
|
}),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
subdomain: text("subdomain"),
|
subdomain: text("subdomain"),
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,32 +17,34 @@ unauthenticated.get("/", (_, res) => {
|
||||||
// Authenticated Root routes
|
// Authenticated Root routes
|
||||||
export const authenticated = Router();
|
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.put("/org", org.createOrg);
|
||||||
authenticated.get("/org/:orgId", org.getOrg);
|
|
||||||
authenticated.get("/orgs", org.listOrgs);
|
authenticated.get("/orgs", org.listOrgs);
|
||||||
|
authenticated.get("/org/:orgId", org.getOrg);
|
||||||
authenticated.post("/org/:orgId", org.updateOrg);
|
authenticated.post("/org/:orgId", org.updateOrg);
|
||||||
authenticated.delete("/org/:orgId", org.deleteOrg);
|
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("/resource/:resourceId", resource.getResource);
|
||||||
authenticated.get("/resources", resource.listResources);
|
|
||||||
authenticated.post("/resource/:resourceId", resource.updateResource);
|
authenticated.post("/resource/:resourceId", resource.updateResource);
|
||||||
authenticated.delete("/resource/:resourceId", resource.deleteResource);
|
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("/target/:targetId", target.getTarget);
|
||||||
authenticated.get("/targets", target.listTargets);
|
|
||||||
authenticated.post("/target/:targetId", target.updateTarget);
|
authenticated.post("/target/:targetId", target.updateTarget);
|
||||||
authenticated.delete("/target/:targetId", target.deleteTarget);
|
authenticated.delete("/target/:targetId", target.deleteTarget);
|
||||||
|
|
||||||
authenticated.get("/user/:userId", user.getUser);
|
|
||||||
authenticated.get("/users", user.listUsers);
|
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);
|
authenticated.delete("/user/:userId", user.deleteUser);
|
||||||
|
|
||||||
// Auth routes
|
// Auth routes
|
||||||
|
|
|
@ -6,9 +6,13 @@ import response from "@server/utils/response";
|
||||||
import HttpCode from '@server/types/HttpCode';
|
import HttpCode from '@server/types/HttpCode';
|
||||||
import createHttpError from 'http-errors';
|
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
|
// Define Zod schema for request body validation
|
||||||
const createResourceSchema = z.object({
|
const createResourceSchema = z.object({
|
||||||
siteId: z.number().int().positive(),
|
|
||||||
name: z.string().min(1).max(255),
|
name: z.string().min(1).max(255),
|
||||||
subdomain: z.string().min(1).max(255).optional(),
|
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
|
// Generate a unique resourceId
|
||||||
const resourceId = "subdomain" // TODO: create the subdomain here
|
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({
|
const newResource = await db.insert(resources).values({
|
||||||
resourceId,
|
resourceId,
|
||||||
siteId,
|
siteId,
|
||||||
|
orgId,
|
||||||
name,
|
name,
|
||||||
subdomain,
|
subdomain,
|
||||||
}).returning();
|
}).returning();
|
||||||
|
|
|
@ -7,10 +7,16 @@ import HttpCode from '@server/types/HttpCode';
|
||||||
import createHttpError from 'http-errors';
|
import createHttpError from 'http-errors';
|
||||||
import { sql, eq } from 'drizzle-orm';
|
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({
|
const listResourcesSchema = z.object({
|
||||||
limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)),
|
limit: z.coerce.number().int().positive().default(10),
|
||||||
offset: z.string().optional().transform(Number).pipe(z.number().int().nonnegative().default(0)),
|
offset: z.coerce.number().int().nonnegative().default(0),
|
||||||
siteId: z.string().optional().transform(Number).pipe(z.number().int().positive()),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function listResources(req: Request, res: Response, next: NextFunction): Promise<any> {
|
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({
|
.select({
|
||||||
resourceId: resources.resourceId,
|
resourceId: resources.resourceId,
|
||||||
name: resources.name,
|
name: resources.name,
|
||||||
|
@ -37,11 +53,14 @@ export async function listResources(req: Request, res: Response, next: NextFunct
|
||||||
.from(resources)
|
.from(resources)
|
||||||
.leftJoin(sites, eq(resources.siteId, sites.siteId));
|
.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) {
|
if (siteId) {
|
||||||
baseQuery = baseQuery.where(eq(resources.siteId, siteId));
|
baseQuery = baseQuery.where(eq(resources.siteId, siteId));
|
||||||
countQuery = countQuery.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);
|
const resourcesList = await baseQuery.limit(limit).offset(offset);
|
||||||
|
|
|
@ -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 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(
|
// Define Zod schema for request body validation
|
||||||
req: Request,
|
const createSiteSchema = z.object({
|
||||||
res: Response,
|
name: z.string().min(1).max(255),
|
||||||
next: NextFunction,
|
subdomain: z.string().min(1).max(255).optional(),
|
||||||
): Promise<any> {
|
pubKey: z.string().optional(),
|
||||||
return response<null>(res, {
|
subnet: z.string().optional(),
|
||||||
data: null,
|
});
|
||||||
|
|
||||||
|
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,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Logged in successfully",
|
message: "Site created successfully",
|
||||||
status: HttpCode.OK,
|
status: HttpCode.CREATED,
|
||||||
});
|
})
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -7,10 +7,13 @@ import HttpCode from '@server/types/HttpCode';
|
||||||
import createHttpError from 'http-errors';
|
import createHttpError from 'http-errors';
|
||||||
import { sql, eq } from 'drizzle-orm';
|
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({
|
const listSitesSchema = z.object({
|
||||||
limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)),
|
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)),
|
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> {
|
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
|
let baseQuery: any = db
|
||||||
.select({
|
.select({
|
||||||
|
|
|
@ -6,8 +6,11 @@ import response from "@server/utils/response";
|
||||||
import HttpCode from '@server/types/HttpCode';
|
import HttpCode from '@server/types/HttpCode';
|
||||||
import createHttpError from 'http-errors';
|
import createHttpError from 'http-errors';
|
||||||
|
|
||||||
const createTargetSchema = z.object({
|
const createTargetParamsSchema = z.object({
|
||||||
resourceId: z.string().uuid(),
|
resourceId: z.string().uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const createTargetSchema = z.object({
|
||||||
ip: z.string().ip(),
|
ip: z.string().ip(),
|
||||||
method: z.string().min(1).max(10),
|
method: z.string().min(1).max(10),
|
||||||
port: z.number().int().min(1).max(65535),
|
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 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(
|
return res.status(HttpCode.CREATED).send(
|
||||||
response(res, {
|
response(res, {
|
||||||
|
|
|
@ -7,10 +7,13 @@ import HttpCode from '@server/types/HttpCode';
|
||||||
import createHttpError from 'http-errors';
|
import createHttpError from 'http-errors';
|
||||||
import { sql, eq } from 'drizzle-orm';
|
import { sql, eq } from 'drizzle-orm';
|
||||||
|
|
||||||
|
const listTargetsParamsSchema = z.object({
|
||||||
|
resourceId: z.string().optional()
|
||||||
|
});
|
||||||
|
|
||||||
const listTargetsSchema = z.object({
|
const listTargetsSchema = z.object({
|
||||||
limit: z.string().optional().transform(Number).pipe(z.number().int().positive().default(10)),
|
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)),
|
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> {
|
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
|
let baseQuery: any = db
|
||||||
.select({
|
.select({
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue