This commit is contained in:
Milo Schwartz
2024-10-06 18:09:12 -04:00
57 changed files with 2604 additions and 2104 deletions

View File

@@ -6,28 +6,28 @@ import createHttpError from 'http-errors';
import HttpCode from '@server/types/HttpCode';
export async function getUserOrgs(req: Request, res: Response, next: NextFunction) {
const userId = req.user?.id; // Assuming you have user information in the request
const userId = req.user?.id; // Assuming you have user information in the request
if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
}
if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
}
try {
const userOrganizations = await db.select({
orgId: userOrgs.orgId,
role: userOrgs.role,
})
.from(userOrgs)
.where(eq(userOrgs.userId, userId));
try {
const userOrganizations = await db.select({
orgId: userOrgs.orgId,
roleId: userOrgs.roleId,
})
.from(userOrgs)
.where(eq(userOrgs.userId, userId));
req.userOrgs = userOrganizations.map(org => org.orgId);
// req.userOrgRoles = userOrganizations.reduce((acc, org) => {
// acc[org.orgId] = org.role;
// return acc;
// }, {} as Record<number, string>);
req.userOrgIds = userOrganizations.map(org => org.orgId);
// req.userOrgRoleIds = userOrganizations.reduce((acc, org) => {
// acc[org.orgId] = org.role;
// return acc;
// }, {} as Record<number, string>);
next();
} catch (error) {
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error retrieving user organizations'));
}
next();
} catch (error) {
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error retrieving user organizations'));
}
}

View File

@@ -7,30 +7,31 @@ import HttpCode from '@server/types/HttpCode';
import { AuthenticatedRequest } from '@server/types/Auth';
export function verifyOrgAccess(req: Request, res: Response, next: NextFunction) {
const userId = req.user!.id; // Assuming you have user information in the request
const orgId = parseInt(req.params.orgId);
const userId = req.user!.id; // Assuming you have user information in the request
const orgId = parseInt(req.params.orgId);
if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
}
if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
}
if (isNaN(orgId)) {
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID'));
}
if (isNaN(orgId)) {
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID'));
}
db.select()
.from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId)))
.then((result) => {
if (result.length === 0) {
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
} else {
// User has access, attach the user's role to the request for potential future use
req.userOrgRole = result[0].role;
next();
}
})
.catch((error) => {
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access'));
});
db.select()
.from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId)))
.then((result) => {
if (result.length === 0) {
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
} else {
// User has access, attach the user's role to the request for potential future use
req.userOrgRoleId = result[0].roleId;
req.userOrgId = orgId;
next();
}
})
.catch((error) => {
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access'));
});
}

View File

@@ -1,54 +1,78 @@
import { Request, Response, NextFunction } from 'express';
import { db } from '@server/db';
import { resources, userOrgs } from '@server/db/schema';
import { resources, userOrgs, userResources, roleResources } from '@server/db/schema';
import { and, eq } from 'drizzle-orm';
import createHttpError from 'http-errors';
import HttpCode from '@server/types/HttpCode';
export async function verifyResourceAccess(req: Request, res: Response, next: NextFunction) {
const userId = req.user!.id; // Assuming you have user information in the request
const resourceId = req.params.resourceId;
const userId = req.user!.id; // Assuming you have user information in the request
const resourceId = req.params.resourceId;
if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
}
if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
}
const resource = await db.select()
.from(resources)
.where(eq(resources.resourceId, resourceId))
.limit(1);
try {
// Get the resource
const resource = await db.select()
.from(resources)
.where(eq(resources.resourceId, resourceId))
.limit(1);
if (resource.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`resource with ID ${resourceId} not found`
)
);
}
if (resource.length === 0) {
return next(createHttpError(HttpCode.NOT_FOUND, `Resource with ID ${resourceId} not found`));
}
if (!resource[0].orgId) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
`resource with ID ${resourceId} does not have an organization ID`
)
);
}
if (!resource[0].orgId) {
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Resource with ID ${resourceId} does not have an organization ID`));
}
db.select()
.from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, resource[0].orgId)))
.then((result) => {
if (result.length === 0) {
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
} else {
// User has access, attach the user's role to the request for potential future use
req.userOrgRole = result[0].role;
next();
}
})
.catch((error) => {
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access'));
});
// Get user's role ID in the organization
const userOrgRole = await db.select()
.from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, resource[0].orgId)))
.limit(1);
if (userOrgRole.length === 0) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
}
const userOrgRoleId = userOrgRole[0].roleId;
req.userOrgRoleId = userOrgRoleId;
req.userOrgId = resource[0].orgId;
// Check role-based resource access first
const roleResourceAccess = await db.select()
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
eq(roleResources.roleId, userOrgRoleId)
)
)
.limit(1);
if (roleResourceAccess.length > 0) {
// User's role has access to the resource
return next();
}
// If role doesn't have access, check user-specific resource access
const userResourceAccess = await db.select()
.from(userResources)
.where(and(eq(userResources.userId, userId), eq(userResources.resourceId, resourceId)))
.limit(1);
if (userResourceAccess.length > 0) {
// User has direct access to the resource
return next();
}
// If we reach here, the user doesn't have access to the resource
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this resource'));
} catch (error) {
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying resource access'));
}
}

View File

@@ -1,58 +1,79 @@
import { Request, Response, NextFunction } from 'express';
import { db } from '@server/db';
import { sites, userOrgs } from '@server/db/schema';
import { and, eq } from 'drizzle-orm';
import { sites, userOrgs, userSites, roleSites, roles } from '@server/db/schema';
import { and, eq, or } from 'drizzle-orm';
import createHttpError from 'http-errors';
import HttpCode from '@server/types/HttpCode';
export async function verifySiteAccess(req: Request, res: Response, next: NextFunction) {
const userId = req.user!.id; // Assuming you have user information in the request
const siteId = parseInt(req.params.siteId);
const userId = req.user!.id; // Assuming you have user information in the request
const siteId = parseInt(req.params.siteId);
if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
}
if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
}
if (isNaN(siteId)) {
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID'));
}
if (isNaN(siteId)) {
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid site ID'));
}
const site = await db.select()
.from(sites)
.where(eq(sites.siteId, siteId))
.limit(1);
try {
// Get the site
const site = await db.select().from(sites).where(eq(sites.siteId, siteId)).limit(1);
if (site.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Site with ID ${siteId} not found`
)
);
}
if (site.length === 0) {
return next(createHttpError(HttpCode.NOT_FOUND, `Site with ID ${siteId} not found`));
}
if (!site[0].orgId) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
`Site with ID ${siteId} does not have an organization ID`
)
);
}
if (!site[0].orgId) {
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Site with ID ${siteId} does not have an organization ID`));
}
db.select()
.from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, site[0].orgId)))
.then((result) => {
if (result.length === 0) {
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
} else {
// User has access, attach the user's role to the request for potential future use
req.userOrgRole = result[0].role;
next();
}
})
.catch((error) => {
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access'));
});
// Get user's role ID in the organization
const userOrgRole = await db.select()
.from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, site[0].orgId)))
.limit(1);
if (userOrgRole.length === 0) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
}
const userOrgRoleId = userOrgRole[0].roleId;
req.userOrgRoleId = userOrgRoleId;
req.userOrgId = site[0].orgId;
// Check role-based site access first
const roleSiteAccess = await db.select()
.from(roleSites)
.where(
and(
eq(roleSites.siteId, siteId),
eq(roleSites.roleId, userOrgRoleId)
)
)
.limit(1);
if (roleSiteAccess.length > 0) {
// User's role has access to the site
return next();
}
// If role doesn't have access, check user-specific site access
const userSiteAccess = await db.select()
.from(userSites)
.where(and(eq(userSites.userId, userId), eq(userSites.siteId, siteId)))
.limit(1);
if (userSiteAccess.length > 0) {
// User has direct access to the site
return next();
}
// If we reach here, the user doesn't have access to the site
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this site'));
} catch (error) {
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying site access'));
}
}

View File

@@ -6,78 +6,79 @@ import createHttpError from 'http-errors';
import HttpCode from '@server/types/HttpCode';
export async function verifyTargetAccess(req: Request, res: Response, next: NextFunction) {
const userId = req.user!.id; // Assuming you have user information in the request
const targetId = parseInt(req.params.targetId);
const userId = req.user!.id; // Assuming you have user information in the request
const targetId = parseInt(req.params.targetId);
if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
}
if (!userId) {
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
}
if (isNaN(targetId)) {
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID'));
}
if (isNaN(targetId)) {
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID'));
}
const target = await db.select()
.from(targets)
.where(eq(targets.targetId, targetId))
.limit(1);
const target = await db.select()
.from(targets)
.where(eq(targets.targetId, targetId))
.limit(1);
if (target.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`target with ID ${targetId} not found`
)
);
}
if (target.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`target with ID ${targetId} not found`
)
);
}
const resourceId = target[0].resourceId;
const resourceId = target[0].resourceId;
if (resourceId) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
`target with ID ${targetId} does not have a resource ID`
)
);
}
if (resourceId) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
`target with ID ${targetId} does not have a resource ID`
)
);
}
const resource = await db.select()
.from(resources)
.where(eq(resources.resourceId, resourceId!))
.limit(1);
const resource = await db.select()
.from(resources)
.where(eq(resources.resourceId, resourceId!))
.limit(1);
if (resource.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`resource with ID ${resourceId} not found`
)
);
}
if (resource.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`resource with ID ${resourceId} not found`
)
);
}
if (!resource[0].orgId) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
`resource with ID ${resourceId} does not have an organization ID`
)
);
}
if (!resource[0].orgId) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
`resource with ID ${resourceId} does not have an organization ID`
)
);
}
db.select()
.from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, resource[0].orgId)))
.then((result) => {
if (result.length === 0) {
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
} else {
// User has access, attach the user's role to the request for potential future use
req.userOrgRole = result[0].role;
next();
}
})
.catch((error) => {
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access'));
});
db.select()
.from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, resource[0].orgId)))
.then((result) => {
if (result.length === 0) {
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
} else {
// User has access, attach the user's role to the request for potential future use
req.userOrgRoleId = result[0].roleId;
req.userOrgId = resource[0].orgId!;
next();
}
})
.catch((error) => {
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access'));
});
}

View File

@@ -3,6 +3,8 @@ import { DrizzleError, eq } from 'drizzle-orm';
import { sites, resources, targets, exitNodes } from '@server/db/schema';
import db from '@server/db';
import logger from '@server/logger';
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
export const getConfig = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
@@ -55,11 +57,7 @@ export const getConfig = async (req: Request, res: Response, next: NextFunction)
res.json(config);
} catch (error) {
logger.error('Error querying database:', error);
if (error instanceof DrizzleError) {
res.status(500).json({ error: 'Database query error', message: error.message });
} else {
next(error);
}
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
};

View File

@@ -3,55 +3,64 @@ import { DrizzleError, eq } from 'drizzle-orm';
import { sites, resources, targets, exitNodes } from '@server/db/schema';
import db from '@server/db';
import logger from '@server/logger';
import createHttpError from 'http-errors';
import HttpCode from '@server/types/HttpCode';
import response from "@server/utils/response";
interface PeerBandwidth {
publicKey: string;
bytesIn: number;
bytesOut: number;
publicKey: string;
bytesIn: number;
bytesOut: number;
}
export const receiveBandwidth = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
try {
const bandwidthData: PeerBandwidth[] = req.body;
export const receiveBandwidth = async (req: Request, res: Response, next: NextFunction): Promise<any> => {
try {
const bandwidthData: PeerBandwidth[] = req.body;
if (!Array.isArray(bandwidthData)) {
throw new Error('Invalid bandwidth data');
if (!Array.isArray(bandwidthData)) {
throw new Error('Invalid bandwidth data');
}
for (const peer of bandwidthData) {
const { publicKey, bytesIn, bytesOut } = peer;
// Find the site by public key
const site = await db.query.sites.findFirst({
where: eq(sites.pubKey, publicKey),
});
if (!site) {
logger.warn(`Site not found for public key: ${publicKey}`);
continue;
}
// Update the site's bandwidth usage
await db.update(sites)
.set({
megabytesIn: (site.megabytesIn || 0) + bytesIn,
megabytesOut: (site.megabytesOut || 0) + bytesOut,
})
.where(eq(sites.siteId, site.siteId));
logger.debug(`Updated bandwidth for site: ${site.siteId}: megabytesIn: ${(site.megabytesIn || 0) + bytesIn}, megabytesOut: ${(site.megabytesOut || 0) + bytesOut}`);
}
return response(res, {
data: {},
success: true,
error: false,
message: "Organization retrieved successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error('Error updating bandwidth data:', error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
for (const peer of bandwidthData) {
const { publicKey, bytesIn, bytesOut } = peer;
// Find the site by public key
const site = await db.query.sites.findFirst({
where: eq(sites.pubKey, publicKey),
});
if (!site) {
logger.warn(`Site not found for public key: ${publicKey}`);
continue;
}
// Update the site's bandwidth usage
await db.update(sites)
.set({
megabytesIn: (site.megabytesIn || 0) + bytesIn,
megabytesOut: (site.megabytesOut || 0) + bytesOut,
})
.where(eq(sites.siteId, site.siteId));
logger.debug(`Updated bandwidth for site: ${site.siteId}: megabytesIn: ${(site.megabytesIn || 0) + bytesIn}, megabytesOut: ${(site.megabytesOut || 0) + bytesOut}`);
}
res.status(200).json({ message: 'Bandwidth data updated successfully' });
} catch (error) {
logger.error('Error updating bandwidth data:', error);
res.status(500).json({ error: 'Internal server error' });
}
};
function calculateSubnet(index: number): string {
const baseIp = 10 << 24;
const subnetSize = 16;
return `${(baseIp | (index * subnetSize)).toString()}/28`;
const baseIp = 10 << 24;
const subnetSize = 16;
return `${(baseIp | (index * subnetSize)).toString()}/28`;
}

View File

@@ -5,53 +5,60 @@ import { orgs } from '@server/db/schema';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const createOrgSchema = z.object({
name: z.string().min(1).max(255),
domain: z.string().min(1).max(255),
name: z.string().min(1).max(255),
domain: z.string().min(1).max(255),
});
const MAX_ORGS = 5;
export async function createOrg(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
const parsedBody = createOrgSchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedBody.error.errors.map(e => e.message).join(', ')
)
);
try {
const parsedBody = createOrgSchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedBody.error.errors.map(e => e.message).join(', ')
)
);
}
const userOrgIds = req.userOrgIds;
if (userOrgIds && userOrgIds.length > MAX_ORGS) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
`Maximum number of organizations reached.`
)
);
}
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.createOrg, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const { name, domain } = parsedBody.data;
const newOrg = await db.insert(orgs).values({
name,
domain,
}).returning();
return response(res, {
data: newOrg[0],
success: true,
error: false,
message: "Organization created successfully",
status: HttpCode.CREATED,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const userOrgIds = req.userOrgs;
if (userOrgIds && userOrgIds.length > MAX_ORGS) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
`Maximum number of organizations reached.`
)
);
}
const { name, domain } = parsedBody.data;
const newOrg = await db.insert(orgs).values({
name,
domain,
}).returning();
return res.status(HttpCode.CREATED).send(
response(res, {
data: newOrg[0],
success: true,
error: false,
message: "Organization created successfully",
status: HttpCode.CREATED,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,48 +6,55 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const deleteOrgSchema = z.object({
orgId: z.string().transform(Number).pipe(z.number().int().positive())
orgId: z.string().transform(Number).pipe(z.number().int().positive())
});
export async function deleteOrg(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
const parsedParams = deleteOrgSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
try {
const parsedParams = deleteOrgSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { orgId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.deleteOrg, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const deletedOrg = await db.delete(orgs)
.where(eq(orgs.orgId, orgId))
.returning();
if (deletedOrg.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Organization with ID ${orgId} not found`
)
);
}
return response(res, {
data: null,
success: true,
error: false,
message: "Organization deleted successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const { orgId } = parsedParams.data;
const deletedOrg = await db.delete(orgs)
.where(eq(orgs.orgId, orgId))
.returning();
if (deletedOrg.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Organization with ID ${orgId} not found`
)
);
}
return res.status(HttpCode.OK).send(
response(res, {
data: null,
success: true,
error: false,
message: "Organization deleted successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,49 +6,56 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const getOrgSchema = z.object({
orgId: z.string().transform(Number).pipe(z.number().int().positive())
orgId: z.string().transform(Number).pipe(z.number().int().positive())
});
export async function getOrg(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
const parsedParams = getOrgSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
try {
const parsedParams = getOrgSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { orgId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.getOrg, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const org = await db.select()
.from(orgs)
.where(eq(orgs.orgId, orgId))
.limit(1);
if (org.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Organization with ID ${orgId} not found`
)
);
}
return response(res, {
data: org[0],
success: true,
error: false,
message: "Organization retrieved successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const { orgId } = parsedParams.data;
const org = await db.select()
.from(orgs)
.where(eq(orgs.orgId, orgId))
.limit(1);
if (org.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Organization with ID ${orgId} not found`
)
);
}
return res.status(HttpCode.OK).send(
response(res, {
data: org[0],
success: true,
error: false,
message: "Organization retrieved successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,82 +6,89 @@ import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { sql, inArray } from 'drizzle-orm';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const listOrgsSchema = 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)),
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)),
});
export async function listOrgs(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
const parsedQuery = listOrgsSchema.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedQuery.error.errors.map(e => e.message).join(', ')
)
);
}
try {
const parsedQuery = listOrgsSchema.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedQuery.error.errors.map(e => e.message).join(', ')
)
);
}
const { limit, offset } = parsedQuery.data;
const { limit, offset } = parsedQuery.data;
// Use the userOrgs passed from the middleware
const userOrgIds = req.userOrgs;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.listOrgs, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
if (!userOrgIds || userOrgIds.length === 0) {
return res.status(HttpCode.OK).send(
response(res, {
data: {
organizations: [],
pagination: {
total: 0,
limit,
offset,
// Use the userOrgs passed from the middleware
const userOrgIds = req.userOrgIds;
if (!userOrgIds || userOrgIds.length === 0) {
return res.status(HttpCode.OK).send(
response(res, {
data: {
organizations: [],
pagination: {
total: 0,
limit,
offset,
},
},
success: true,
error: false,
message: "No organizations found for the user",
status: HttpCode.OK,
})
);
}
const organizations = await db.select()
.from(orgs)
.where(inArray(orgs.orgId, userOrgIds))
.limit(limit)
.offset(offset);
const totalCountResult = await db.select({ count: sql<number>`cast(count(*) as integer)` })
.from(orgs)
.where(inArray(orgs.orgId, userOrgIds));
const totalCount = totalCountResult[0].count;
// // Add the user's role for each organization
// const organizationsWithRoles = organizations.map(org => ({
// ...org,
// userRole: req.userOrgRoleIds[org.orgId],
// }));
return response(res, {
data: {
organizations,
pagination: {
total: totalCount,
limit,
offset,
},
},
},
success: true,
error: false,
message: "No organizations found for the user",
status: HttpCode.OK,
})
);
success: true,
error: false,
message: "Organizations retrieved successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const organizations = await db.select()
.from(orgs)
.where(inArray(orgs.orgId, userOrgIds))
.limit(limit)
.offset(offset);
const totalCountResult = await db.select({ count: sql<number>`cast(count(*) as integer)` })
.from(orgs)
.where(inArray(orgs.orgId, userOrgIds));
const totalCount = totalCountResult[0].count;
// // Add the user's role for each organization
// const organizationsWithRoles = organizations.map(org => ({
// ...org,
// userRole: req.userOrgRoles[org.orgId],
// }));
return res.status(HttpCode.OK).send(
response(res, {
data: {
organizations,
pagination: {
total: totalCount,
limit,
offset,
},
},
success: true,
error: false,
message: "Organizations retrieved successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,67 +6,75 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const updateOrgParamsSchema = z.object({
orgId: z.string().transform(Number).pipe(z.number().int().positive())
orgId: z.string().transform(Number).pipe(z.number().int().positive())
});
const updateOrgBodySchema = z.object({
name: z.string().min(1).max(255).optional(),
domain: z.string().min(1).max(255).optional(),
name: z.string().min(1).max(255).optional(),
domain: z.string().min(1).max(255).optional(),
}).refine(data => Object.keys(data).length > 0, {
message: "At least one field must be provided for update"
message: "At least one field must be provided for update"
});
export async function updateOrg(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
const parsedParams = updateOrgParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
try {
const parsedParams = updateOrgParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const parsedBody = updateOrgBodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedBody.error.errors.map(e => e.message).join(', ')
)
);
}
const { orgId } = parsedParams.data;
const updateData = parsedBody.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.updateOrg, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const updatedOrg = await db.update(orgs)
.set(updateData)
.where(eq(orgs.orgId, orgId))
.returning();
if (updatedOrg.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Organization with ID ${orgId} not found`
)
);
}
return response(res, {
data: updatedOrg[0],
success: true,
error: false,
message: "Organization updated successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const parsedBody = updateOrgBodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedBody.error.errors.map(e => e.message).join(', ')
)
);
}
const { orgId } = parsedParams.data;
const updateData = parsedBody.data;
const updatedOrg = await db.update(orgs)
.set(updateData)
.where(eq(orgs.orgId, orgId))
.returning();
if (updatedOrg.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Organization with ID ${orgId} not found`
)
);
}
return res.status(HttpCode.OK).send(
response(res, {
data: updatedOrg[0],
success: true,
error: false,
message: "Organization updated successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -5,68 +5,75 @@ import { resources } from '@server/db/schema';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const createResourceParamsSchema = z.object({
siteId: z.number().int().positive(),
orgId: z.number().int().positive(),
siteId: z.number().int().positive(),
orgId: z.number().int().positive(),
});
// Define Zod schema for request body validation
const createResourceSchema = z.object({
name: z.string().min(1).max(255),
subdomain: z.string().min(1).max(255).optional(),
name: z.string().min(1).max(255),
subdomain: z.string().min(1).max(255).optional(),
});
export async function createResource(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
// Validate request body
const parsedBody = createResourceSchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedBody.error.errors.map(e => e.message).join(', ')
)
);
try {
// Validate request body
const parsedBody = createResourceSchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedBody.error.errors.map(e => e.message).join(', ')
)
);
}
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;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.createResource, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Generate a unique resourceId
const resourceId = "subdomain" // TODO: create the subdomain here
// Create new resource in the database
const newResource = await db.insert(resources).values({
resourceId,
siteId,
orgId,
name,
subdomain,
}).returning();
response(res, {
data: newResource[0],
success: true,
error: false,
message: "Resource created successfully",
status: HttpCode.CREATED,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
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
// Create new resource in the database
const newResource = await db.insert(resources).values({
resourceId,
siteId,
orgId,
name,
subdomain,
}).returning();
return res.status(HttpCode.CREATED).send(
response(res, {
data: newResource[0],
success: true,
error: false,
message: "Resource created successfully",
status: HttpCode.CREATED,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,51 +6,58 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
// Define Zod schema for request parameters validation
const deleteResourceSchema = z.object({
resourceId: z.string().uuid()
resourceId: z.string().uuid()
});
export async function deleteResource(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
// Validate request parameters
const parsedParams = deleteResourceSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
try {
// Validate request parameters
const parsedParams = deleteResourceSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { resourceId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.deleteResource, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Delete the resource from the database
const deletedResource = await db.delete(resources)
.where(eq(resources.resourceId, resourceId))
.returning();
if (deletedResource.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource with ID ${resourceId} not found`
)
);
}
return response(res, {
data: null,
success: true,
error: false,
message: "Resource deleted successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const { resourceId } = parsedParams.data;
// Delete the resource from the database
const deletedResource = await db.delete(resources)
.where(eq(resources.resourceId, resourceId))
.returning();
if (deletedResource.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource with ID ${resourceId} not found`
)
);
}
return res.status(HttpCode.OK).send(
response(res, {
data: null,
success: true,
error: false,
message: "Resource deleted successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,52 +6,59 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
// Define Zod schema for request parameters validation
const getResourceSchema = z.object({
resourceId: z.string().uuid()
resourceId: z.string().uuid()
});
export async function getResource(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
// Validate request parameters
const parsedParams = getResourceSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
try {
// Validate request parameters
const parsedParams = getResourceSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { resourceId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.getResource, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Fetch the resource from the database
const resource = await db.select()
.from(resources)
.where(eq(resources.resourceId, resourceId))
.limit(1);
if (resource.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource with ID ${resourceId} not found`
)
);
}
return response(res, {
data: resource[0],
success: true,
error: false,
message: "Resource retrieved successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const { resourceId } = parsedParams.data;
// Fetch the resource from the database
const resource = await db.select()
.from(resources)
.where(eq(resources.resourceId, resourceId))
.limit(1);
if (resource.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource with ID ${resourceId} not found`
)
);
}
return res.status(HttpCode.OK).send(
response(res, {
data: resource[0],
success: true,
error: false,
message: "Resource retrieved successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -1,11 +1,13 @@
import { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import { db } from '@server/db';
import { resources, sites } from '@server/db/schema';
import { resources, sites, userResources, roleResources } from '@server/db/schema';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { sql, eq } from 'drizzle-orm';
import { sql, eq, and, or, inArray } from 'drizzle-orm';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const listResourcesParamsSchema = z.object({
siteId: z.coerce.number().int().positive().optional(),
@@ -15,75 +17,98 @@ const listResourcesParamsSchema = z.object({
});
const listResourcesSchema = z.object({
limit: z.coerce.number().int().positive().default(10),
offset: z.coerce.number().int().nonnegative().default(0),
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> {
try {
const parsedQuery = listResourcesSchema.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedQuery.error.errors.map(e => e.message).join(', ')
)
);
interface RequestWithOrgAndRole extends Request {
userOrgRoleId?: number;
orgId?: number;
}
export async function listResources(req: RequestWithOrgAndRole, res: Response, next: NextFunction): Promise<any> {
try {
const parsedQuery = listResourcesSchema.safeParse(req.query);
if (!parsedQuery.success) {
return next(createHttpError(HttpCode.BAD_REQUEST, parsedQuery.error.errors.map(e => e.message).join(', ')));
}
const { limit, offset } = 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;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.listResources, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
if (orgId && orgId !== req.orgId) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
}
// Get the list of resources the user has access to
const accessibleResources = await db
.select({ resourceId: sql<string>`COALESCE(${userResources.resourceId}, ${roleResources.resourceId})` })
.from(userResources)
.fullJoin(roleResources, eq(userResources.resourceId, roleResources.resourceId))
.where(
or(
eq(userResources.userId, req.user!.id),
eq(roleResources.roleId, req.userOrgRoleId!)
)
);
const accessibleResourceIds = accessibleResources.map(resource => resource.resourceId);
let baseQuery: any = db
.select({
resourceId: resources.resourceId,
name: resources.name,
subdomain: resources.subdomain,
siteName: sites.name,
})
.from(resources)
.leftJoin(sites, eq(resources.siteId, sites.siteId))
.where(inArray(resources.resourceId, accessibleResourceIds));
let countQuery: any = db
.select({ count: sql<number>`cast(count(*) as integer)` })
.from(resources)
.where(inArray(resources.resourceId, accessibleResourceIds));
if (siteId) {
baseQuery = baseQuery.where(eq(resources.siteId, siteId));
countQuery = countQuery.where(eq(resources.siteId, siteId));
} else {
// If orgId is provided, it's already checked to match req.orgId
baseQuery = baseQuery.where(eq(resources.orgId, req.orgId!));
countQuery = countQuery.where(eq(resources.orgId, req.orgId!));
}
const resourcesList = await baseQuery.limit(limit).offset(offset);
const totalCountResult = await countQuery;
const totalCount = totalCountResult[0].count;
return response(res, {
data: {
resources: resourcesList,
pagination: {
total: totalCount,
limit,
offset,
},
},
success: true,
error: false,
message: "Resources retrieved successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const { limit, offset } = 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: any = db
.select({
resourceId: resources.resourceId,
name: resources.name,
subdomain: resources.subdomain,
siteName: sites.name,
})
.from(resources)
.leftJoin(sites, eq(resources.siteId, sites.siteId));
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);
const totalCountResult = await countQuery;
const totalCount = totalCountResult[0].count;
return res.status(HttpCode.OK).send(
response(res, {
data: {
resources: resourcesList,
pagination: {
total: totalCount,
limit,
offset,
},
},
success: true,
error: false,
message: "Resources retrieved successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,72 +6,79 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
// Define Zod schema for request parameters validation
const updateResourceParamsSchema = z.object({
resourceId: z.string().uuid()
resourceId: z.string().uuid()
});
// Define Zod schema for request body validation
const updateResourceBodySchema = z.object({
name: z.string().min(1).max(255).optional(),
subdomain: z.string().min(1).max(255).optional(),
name: z.string().min(1).max(255).optional(),
subdomain: z.string().min(1).max(255).optional(),
}).refine(data => Object.keys(data).length > 0, {
message: "At least one field must be provided for update"
message: "At least one field must be provided for update"
});
export async function updateResource(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
// Validate request parameters
const parsedParams = updateResourceParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
try {
// Validate request parameters
const parsedParams = updateResourceParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
// Validate request body
const parsedBody = updateResourceBodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedBody.error.errors.map(e => e.message).join(', ')
)
);
}
const { resourceId } = parsedParams.data;
const updateData = parsedBody.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.updateResource, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Update the resource in the database
const updatedResource = await db.update(resources)
.set(updateData)
.where(eq(resources.resourceId, resourceId))
.returning();
if (updatedResource.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource with ID ${resourceId} not found`
)
);
}
return response(res, {
data: updatedResource[0],
success: true,
error: false,
message: "Resource updated successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
// Validate request body
const parsedBody = updateResourceBodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedBody.error.errors.map(e => e.message).join(', ')
)
);
}
const { resourceId } = parsedParams.data;
const updateData = parsedBody.data;
// Update the resource in the database
const updatedResource = await db.update(resources)
.set(updateData)
.where(eq(resources.resourceId, resourceId))
.returning();
if (updatedResource.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource with ID ${resourceId} not found`
)
);
}
return res.status(HttpCode.OK).send(
response(res, {
data: updatedResource[0],
success: true,
error: false,
message: "Resource updated successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,93 +6,99 @@ import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import fetch from 'node-fetch';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const API_BASE_URL = "http://localhost:3000";
const createSiteParamsSchema = z.object({
const createSiteParamsSchema = z.object({
orgId: z.number().int().positive(),
});
// 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(),
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(', ')
)
);
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;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.createSite, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Create new site in the database
const newSite = await db.insert(sites).values({
orgId,
name,
subdomain,
pubKey,
subnet,
}).returning();
return response(res, {
data: newSite[0],
success: true,
error: false,
message: "Site created successfully",
status: HttpCode.CREATED,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
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: "Site created successfully",
status: HttpCode.CREATED,
})
);
} catch (error) {
next(error);
}
}
async function addPeer(peer: string) {
try {
const response = await fetch(`${API_BASE_URL}/peer`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(peer),
});
try {
const response = await fetch(`${API_BASE_URL}/peer`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(peer),
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data: any = await response.json();
logger.info('Peer added successfully:', data.status);
return data;
} catch (error: any) {
throw error;
}
const data: any = await response.json();
console.log('Peer added successfully:', data.status);
return data;
} catch (error: any) {
console.error('Error adding peer:', error.message);
throw error;
}
}

View File

@@ -6,74 +6,81 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const API_BASE_URL = "http://localhost:3000";
// Define Zod schema for request parameters validation
const deleteSiteSchema = z.object({
siteId: z.string().transform(Number).pipe(z.number().int().positive())
siteId: z.string().transform(Number).pipe(z.number().int().positive())
});
export async function deleteSite(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
// Validate request parameters
const parsedParams = deleteSiteSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
try {
// Validate request parameters
const parsedParams = deleteSiteSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { siteId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.deleteSite, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Delete the site from the database
const deletedSite = await db.delete(sites)
.where(eq(sites.siteId, siteId))
.returning();
if (deletedSite.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Site with ID ${siteId} not found`
)
);
}
return response(res, {
data: null,
success: true,
error: false,
message: "Site deleted successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const { siteId } = parsedParams.data;
// Delete the site from the database
const deletedSite = await db.delete(sites)
.where(eq(sites.siteId, siteId))
.returning();
if (deletedSite.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Site with ID ${siteId} not found`
)
);
}
return res.status(HttpCode.OK).send(
response(res, {
data: null,
success: true,
error: false,
message: "Site deleted successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}
async function removePeer(publicKey: string) {
try {
const response = await fetch(`${API_BASE_URL}/peer?public_key=${encodeURIComponent(publicKey)}`, {
method: 'DELETE',
});
try {
const response = await fetch(`${API_BASE_URL}/peer?public_key=${encodeURIComponent(publicKey)}`, {
method: 'DELETE',
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
console.log('Peer removed successfully:', data.status);
return data;
} catch (error: any) {
console.error('Error removing peer:', error.message);
throw error;
}
const data = await response.json();
console.log('Peer removed successfully:', data.status);
return data;
} catch (error: any) {
console.error('Error removing peer:', error.message);
throw error;
}
}

View File

@@ -6,52 +6,59 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
// Define Zod schema for request parameters validation
const getSiteSchema = z.object({
siteId: z.string().transform(Number).pipe(z.number().int().positive())
siteId: z.string().transform(Number).pipe(z.number().int().positive())
});
export async function getSite(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
// Validate request parameters
const parsedParams = getSiteSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
try {
// Validate request parameters
const parsedParams = getSiteSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { siteId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.updateSite, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Fetch the site from the database
const site = await db.select()
.from(sites)
.where(eq(sites.siteId, siteId))
.limit(1);
if (site.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Site with ID ${siteId} not found`
)
);
}
return response(res, {
data: site[0],
success: true,
error: false,
message: "Site retrieved successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const { siteId } = parsedParams.data;
// Fetch the site from the database
const site = await db.select()
.from(sites)
.where(eq(sites.siteId, siteId))
.limit(1);
if (site.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Site with ID ${siteId} not found`
)
);
}
return res.status(HttpCode.OK).send(
response(res, {
data: site[0],
success: true,
error: false,
message: "Site retrieved successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -1,91 +1,107 @@
import { Request, Response, NextFunction } from 'express';
import { z } from 'zod';
import { db } from '@server/db';
import { sites, orgs, exitNodes } from '@server/db/schema';
import { sites, orgs, exitNodes, userSites, roleSites } from '@server/db/schema';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { sql, eq } from 'drizzle-orm';
import { sql, eq, and, or, inArray } from 'drizzle-orm';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
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)),
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)),
});
export async function listSites(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
const parsedQuery = listSitesSchema.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedQuery.error.errors.map(e => e.message).join(', ')
)
);
try {
const parsedQuery = listSitesSchema.safeParse(req.query);
if (!parsedQuery.success) {
return next(createHttpError(HttpCode.BAD_REQUEST, parsedQuery.error.errors.map(e => e.message).join(', ')));
}
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;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.listSites, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
if (orgId && orgId !== req.userOrgId) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
}
const accessibleSites = await db
.select({ siteId: sql<number>`COALESCE(${userSites.siteId}, ${roleSites.siteId})` })
.from(userSites)
.fullJoin(roleSites, eq(userSites.siteId, roleSites.siteId))
.where(
or(
eq(userSites.userId, req.user!.id),
eq(roleSites.roleId, req.userOrgRoleId!)
)
);
const accessibleSiteIds = accessibleSites.map(site => site.siteId);
let baseQuery: any = db
.select({
siteId: sites.siteId,
name: sites.name,
subdomain: sites.subdomain,
pubKey: sites.pubKey,
subnet: sites.subnet,
megabytesIn: sites.megabytesIn,
megabytesOut: sites.megabytesOut,
orgName: orgs.name,
exitNodeName: exitNodes.name,
})
.from(sites)
.leftJoin(orgs, eq(sites.orgId, orgs.orgId))
.where(inArray(sites.siteId, accessibleSiteIds));
let countQuery: any = db
.select({ count: sql<number>`cast(count(*) as integer)` })
.from(sites)
.where(inArray(sites.siteId, accessibleSiteIds));
if (orgId) {
baseQuery = baseQuery.where(eq(sites.orgId, orgId));
countQuery = countQuery.where(eq(sites.orgId, orgId));
}
const sitesList = await baseQuery.limit(limit).offset(offset);
const totalCountResult = await countQuery;
const totalCount = totalCountResult[0].count;
return response(res, {
data: {
sites: sitesList,
pagination: {
total: totalCount,
limit,
offset,
},
},
success: true,
error: false,
message: "Sites retrieved successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
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({
siteId: sites.siteId,
name: sites.name,
subdomain: sites.subdomain,
pubKey: sites.pubKey,
subnet: sites.subnet,
megabytesIn: sites.megabytesIn,
megabytesOut: sites.megabytesOut,
orgName: orgs.name,
exitNodeName: exitNodes.name,
})
.from(sites)
.leftJoin(orgs, eq(sites.orgId, orgs.orgId))
.leftJoin(exitNodes, eq(sites.exitNode, exitNodes.exitNodeId));
let countQuery: any = db.select({ count: sql<number>`cast(count(*) as integer)` }).from(sites);
if (orgId) {
baseQuery = baseQuery.where(eq(sites.orgId, orgId));
countQuery = countQuery.where(eq(sites.orgId, orgId));
}
const sitesList = await baseQuery.limit(limit).offset(offset);
const totalCountResult = await countQuery;
const totalCount = totalCountResult[0].count;
return res.status(HttpCode.OK).send(
response(res, {
data: {
sites: sitesList,
pagination: {
total: totalCount,
limit,
offset,
},
},
success: true,
error: false,
message: "Sites retrieved successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,77 +6,84 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
// Define Zod schema for request parameters validation
const updateSiteParamsSchema = z.object({
siteId: z.string().transform(Number).pipe(z.number().int().positive())
siteId: z.string().transform(Number).pipe(z.number().int().positive())
});
// Define Zod schema for request body validation
const updateSiteBodySchema = z.object({
name: z.string().min(1).max(255).optional(),
subdomain: z.string().min(1).max(255).optional(),
pubKey: z.string().optional(),
subnet: z.string().optional(),
exitNode: z.number().int().positive().optional(),
megabytesIn: z.number().int().nonnegative().optional(),
megabytesOut: z.number().int().nonnegative().optional(),
name: z.string().min(1).max(255).optional(),
subdomain: z.string().min(1).max(255).optional(),
pubKey: z.string().optional(),
subnet: z.string().optional(),
exitNode: z.number().int().positive().optional(),
megabytesIn: z.number().int().nonnegative().optional(),
megabytesOut: z.number().int().nonnegative().optional(),
}).refine(data => Object.keys(data).length > 0, {
message: "At least one field must be provided for update"
message: "At least one field must be provided for update"
});
export async function updateSite(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
// Validate request parameters
const parsedParams = updateSiteParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
try {
// Validate request parameters
const parsedParams = updateSiteParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
// Validate request body
const parsedBody = updateSiteBodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedBody.error.errors.map(e => e.message).join(', ')
)
);
}
const { siteId } = parsedParams.data;
const updateData = parsedBody.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.updateSite, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
// Update the site in the database
const updatedSite = await db.update(sites)
.set(updateData)
.where(eq(sites.siteId, siteId))
.returning();
if (updatedSite.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Site with ID ${siteId} not found`
)
);
}
return response(res, {
data: updatedSite[0],
success: true,
error: false,
message: "Site updated successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
// Validate request body
const parsedBody = updateSiteBodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedBody.error.errors.map(e => e.message).join(', ')
)
);
}
const { siteId } = parsedParams.data;
const updateData = parsedBody.data;
// Update the site in the database
const updatedSite = await db.update(sites)
.set(updateData)
.where(eq(sites.siteId, siteId))
.returning();
if (updatedSite.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Site with ID ${siteId} not found`
)
);
}
return res.status(HttpCode.OK).send(
response(res, {
data: updatedSite[0],
success: true,
error: false,
message: "Site updated successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -5,60 +5,67 @@ import { targets } from '@server/db/schema';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const createTargetParamsSchema = z.object({
resourceId: z.string().uuid(),
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),
protocol: z.string().optional(),
enabled: z.boolean().default(true),
ip: z.string().ip(),
method: z.string().min(1).max(10),
port: z.number().int().min(1).max(65535),
protocol: z.string().optional(),
enabled: z.boolean().default(true),
});
export async function createTarget(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
const parsedBody = createTargetSchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedBody.error.errors.map(e => e.message).join(', ')
)
);
try {
const parsedBody = createTargetSchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedBody.error.errors.map(e => e.message).join(', ')
)
);
}
const targetData = parsedBody.data;
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;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.createTarget, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const newTarget = await db.insert(targets).values({
resourceId,
...targetData
}).returning();
return response(res, {
data: newTarget[0],
success: true,
error: false,
message: "Target created successfully",
status: HttpCode.CREATED,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const targetData = parsedBody.data;
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, {
data: newTarget[0],
success: true,
error: false,
message: "Target created successfully",
status: HttpCode.CREATED,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,48 +6,55 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const deleteTargetSchema = z.object({
targetId: z.string().transform(Number).pipe(z.number().int().positive())
targetId: z.string().transform(Number).pipe(z.number().int().positive())
});
export async function deleteTarget(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
const parsedParams = deleteTargetSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
try {
const parsedParams = deleteTargetSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { targetId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.deleteTarget, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const deletedTarget = await db.delete(targets)
.where(eq(targets.targetId, targetId))
.returning();
if (deletedTarget.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Target with ID ${targetId} not found`
)
);
}
return response(res, {
data: null,
success: true,
error: false,
message: "Target deleted successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const { targetId } = parsedParams.data;
const deletedTarget = await db.delete(targets)
.where(eq(targets.targetId, targetId))
.returning();
if (deletedTarget.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Target with ID ${targetId} not found`
)
);
}
return res.status(HttpCode.OK).send(
response(res, {
data: null,
success: true,
error: false,
message: "Target deleted successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,49 +6,56 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const getTargetSchema = z.object({
targetId: z.string().transform(Number).pipe(z.number().int().positive())
targetId: z.string().transform(Number).pipe(z.number().int().positive())
});
export async function getTarget(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
const parsedParams = getTargetSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
try {
const parsedParams = getTargetSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { targetId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.getTarget, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const target = await db.select()
.from(targets)
.where(eq(targets.targetId, targetId))
.limit(1);
if (target.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Target with ID ${targetId} not found`
)
);
}
return response(res, {
data: target[0],
success: true,
error: false,
message: "Target retrieved successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const { targetId } = parsedParams.data;
const target = await db.select()
.from(targets)
.where(eq(targets.targetId, targetId))
.limit(1);
if (target.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Target with ID ${targetId} not found`
)
);
}
return res.status(HttpCode.OK).send(
response(res, {
data: target[0],
success: true,
error: false,
message: "Target retrieved successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,83 +6,90 @@ import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { sql, eq } from 'drizzle-orm';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
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)),
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)),
});
export async function listTargets(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
const parsedQuery = listTargetsSchema.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedQuery.error.errors.map(e => e.message).join(', ')
)
);
try {
const parsedQuery = listTargetsSchema.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedQuery.error.errors.map(e => e.message).join(', ')
)
);
}
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;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.listTargets, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
let baseQuery: any = db
.select({
targetId: targets.targetId,
ip: targets.ip,
method: targets.method,
port: targets.port,
protocol: targets.protocol,
enabled: targets.enabled,
resourceName: resources.name,
})
.from(targets)
.leftJoin(resources, eq(targets.resourceId, resources.resourceId));
let countQuery: any = db.select({ count: sql<number>`cast(count(*) as integer)` }).from(targets);
if (resourceId) {
baseQuery = baseQuery.where(eq(targets.resourceId, resourceId));
countQuery = countQuery.where(eq(targets.resourceId, resourceId));
}
const targetsList = await baseQuery.limit(limit).offset(offset);
const totalCountResult = await countQuery;
const totalCount = totalCountResult[0].count;
return response(res, {
data: {
targets: targetsList,
pagination: {
total: totalCount,
limit,
offset,
},
},
success: true,
error: false,
message: "Targets retrieved successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
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({
targetId: targets.targetId,
ip: targets.ip,
method: targets.method,
port: targets.port,
protocol: targets.protocol,
enabled: targets.enabled,
resourceName: resources.name,
})
.from(targets)
.leftJoin(resources, eq(targets.resourceId, resources.resourceId));
let countQuery: any = db.select({ count: sql<number>`cast(count(*) as integer)` }).from(targets);
if (resourceId) {
baseQuery = baseQuery.where(eq(targets.resourceId, resourceId));
countQuery = countQuery.where(eq(targets.resourceId, resourceId));
}
const targetsList = await baseQuery.limit(limit).offset(offset);
const totalCountResult = await countQuery;
const totalCount = totalCountResult[0].count;
return res.status(HttpCode.OK).send(
response(res, {
data: {
targets: targetsList,
pagination: {
total: totalCount,
limit,
offset,
},
},
success: true,
error: false,
message: "Targets retrieved successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,70 +6,77 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const updateTargetParamsSchema = z.object({
targetId: z.string().transform(Number).pipe(z.number().int().positive())
targetId: z.string().transform(Number).pipe(z.number().int().positive())
});
const updateTargetBodySchema = z.object({
ip: z.string().ip().optional(),
method: z.string().min(1).max(10).optional(),
port: z.number().int().min(1).max(65535).optional(),
protocol: z.string().optional(),
enabled: z.boolean().optional(),
ip: z.string().ip().optional(),
method: z.string().min(1).max(10).optional(),
port: z.number().int().min(1).max(65535).optional(),
protocol: z.string().optional(),
enabled: z.boolean().optional(),
}).refine(data => Object.keys(data).length > 0, {
message: "At least one field must be provided for update"
message: "At least one field must be provided for update"
});
export async function updateTarget(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
const parsedParams = updateTargetParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
try {
const parsedParams = updateTargetParamsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const parsedBody = updateTargetBodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedBody.error.errors.map(e => e.message).join(', ')
)
);
}
const { targetId } = parsedParams.data;
const updateData = parsedBody.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.updateTarget, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const updatedTarget = await db.update(targets)
.set(updateData)
.where(eq(targets.targetId, targetId))
.returning();
if (updatedTarget.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Target with ID ${targetId} not found`
)
);
}
return response(res, {
data: updatedTarget[0],
success: true,
error: false,
message: "Target updated successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const parsedBody = updateTargetBodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedBody.error.errors.map(e => e.message).join(', ')
)
);
}
const { targetId } = parsedParams.data;
const updateData = parsedBody.data;
const updatedTarget = await db.update(targets)
.set(updateData)
.where(eq(targets.targetId, targetId))
.returning();
if (updatedTarget.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Target with ID ${targetId} not found`
)
);
}
return res.status(HttpCode.OK).send(
response(res, {
data: updatedTarget[0],
success: true,
error: false,
message: "Target updated successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,48 +6,55 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const deleteUserSchema = z.object({
userId: z.string().uuid()
userId: z.string().uuid()
});
export async function deleteUser(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
const parsedParams = deleteUserSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
try {
const parsedParams = deleteUserSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { userId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.deleteUser, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const deletedUser = await db.delete(users)
.where(eq(users.id, userId))
.returning();
if (deletedUser.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`User with ID ${userId} not found`
)
);
}
return response(res, {
data: null,
success: true,
error: false,
message: "User deleted successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const { userId } = parsedParams.data;
const deletedUser = await db.delete(users)
.where(eq(users.id, userId))
.returning();
if (deletedUser.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`User with ID ${userId} not found`
)
);
}
return res.status(HttpCode.OK).send(
response(res, {
data: null,
success: true,
error: false,
message: "User deleted successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,52 +6,59 @@ import { eq } from 'drizzle-orm';
import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const getUserSchema = z.object({
userId: z.string().uuid()
userId: z.string().uuid()
});
export async function getUser(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
const parsedParams = getUserSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
try {
const parsedParams = getUserSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedParams.error.errors.map(e => e.message).join(', ')
)
);
}
const { userId } = parsedParams.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.getUser, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const user = await db.select()
.from(users)
.where(eq(users.id, userId))
.limit(1);
if (user.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`User with ID ${userId} not found`
)
);
}
// Remove passwordHash from the response
const { passwordHash: _, ...userWithoutPassword } = user[0];
return response(res, {
data: userWithoutPassword,
success: true,
error: false,
message: "User retrieved successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const { userId } = parsedParams.data;
const user = await db.select()
.from(users)
.where(eq(users.id, userId))
.limit(1);
if (user.length === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`User with ID ${userId} not found`
)
);
}
// Remove passwordHash from the response
const { passwordHash: _, ...userWithoutPassword } = user[0];
return res.status(HttpCode.OK).send(
response(res, {
data: userWithoutPassword,
success: true,
error: false,
message: "User retrieved successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}

View File

@@ -6,56 +6,63 @@ import response from "@server/utils/response";
import HttpCode from '@server/types/HttpCode';
import createHttpError from 'http-errors';
import { sql } from 'drizzle-orm';
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
import logger from '@server/logger';
const listUsersSchema = 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)),
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)),
});
export async function listUsers(req: Request, res: Response, next: NextFunction): Promise<any> {
try {
const parsedQuery = listUsersSchema.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedQuery.error.errors.map(e => e.message).join(', ')
)
);
try {
const parsedQuery = listUsersSchema.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
parsedQuery.error.errors.map(e => e.message).join(', ')
)
);
}
const { limit, offset } = parsedQuery.data;
// Check if the user has permission to list sites
const hasPermission = await checkUserActionPermission(ActionsEnum.listUsers, req);
if (!hasPermission) {
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
}
const usersList = await db.select()
.from(users)
.limit(limit)
.offset(offset);
const totalCountResult = await db
.select({ count: sql<number>`cast(count(*) as integer)` })
.from(users);
const totalCount = totalCountResult[0].count;
// Remove passwordHash from each user object
const usersWithoutPassword = usersList.map(({ passwordHash, ...userWithoutPassword }) => userWithoutPassword);
return response(res, {
data: {
users: usersWithoutPassword,
pagination: {
total: totalCount,
limit,
offset,
},
},
success: true,
error: false,
message: "Users retrieved successfully",
status: HttpCode.OK,
});
} catch (error) {
logger.error(error);
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
}
const { limit, offset } = parsedQuery.data;
const usersList = await db.select()
.from(users)
.limit(limit)
.offset(offset);
const totalCountResult = await db
.select({ count: sql<number>`cast(count(*) as integer)` })
.from(users);
const totalCount = totalCountResult[0].count;
// Remove passwordHash from each user object
const usersWithoutPassword = usersList.map(({ passwordHash, ...userWithoutPassword }) => userWithoutPassword);
return res.status(HttpCode.OK).send(
response(res, {
data: {
users: usersWithoutPassword,
pagination: {
total: totalCount,
limit,
offset,
},
},
success: true,
error: false,
message: "Users retrieved successfully",
status: HttpCode.OK,
})
);
} catch (error) {
next(error);
}
}