mirror of
https://github.com/fosrl/pangolin.git
synced 2025-06-26 15:18:45 +02:00
Format files and fix http response
This commit is contained in:
parent
797f72e1d0
commit
8213036729
49 changed files with 2428 additions and 2404 deletions
|
@ -1,3 +1,6 @@
|
||||||
{
|
{
|
||||||
"extends": ["next/core-web-vitals", "next/typescript"]
|
"extends": [
|
||||||
|
"next/core-web-vitals",
|
||||||
|
"next/typescript"
|
||||||
|
]
|
||||||
}
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
{
|
{
|
||||||
"version": "1",
|
"version": "1",
|
||||||
"name": "Pangolin",
|
"name": "Pangolin",
|
||||||
"type": "collection",
|
"type": "collection",
|
||||||
"ignore": [
|
"ignore": [
|
||||||
"node_modules",
|
"node_modules",
|
||||||
".git"
|
".git"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -5,98 +5,92 @@ import { and, eq } from 'drizzle-orm';
|
||||||
import createHttpError from 'http-errors';
|
import createHttpError from 'http-errors';
|
||||||
import HttpCode from '@server/types/HttpCode';
|
import HttpCode from '@server/types/HttpCode';
|
||||||
|
|
||||||
export const ActionsEnum = {
|
export enum ActionsEnum {
|
||||||
|
createOrg = "createOrg",
|
||||||
createOrg: 1,
|
deleteOrg = "deleteOrg",
|
||||||
deleteOrg: 2,
|
getOrg = "getOrg",
|
||||||
getOrg: 3,
|
listOrgs = "listOrgs",
|
||||||
listOrgs: 4,
|
updateOrg = "updateOrg",
|
||||||
updateOrg: 5,
|
createSite = "createSite",
|
||||||
|
deleteSite = "deleteSite",
|
||||||
createSite: 6,
|
getSite = "getSite",
|
||||||
deleteSite: 7,
|
listSites = "listSites",
|
||||||
getSite: 8,
|
updateSite = "updateSite",
|
||||||
listSites: 9,
|
createResource = "createResource",
|
||||||
updateSite: 10,
|
deleteResource = "deleteResource",
|
||||||
|
getResource = "getResource",
|
||||||
createResource: 11,
|
listResources = "listResources",
|
||||||
deleteResource: 12,
|
updateResource = "updateResource",
|
||||||
getResource: 13,
|
createTarget = "createTarget",
|
||||||
listResources: 14,
|
deleteTarget = "deleteTarget",
|
||||||
updateResource: 15,
|
getTarget = "getTarget",
|
||||||
|
listTargets = "listTargets",
|
||||||
createTarget: 16,
|
updateTarget = "updateTarget",
|
||||||
deleteTarget: 17,
|
getUser = "getUser",
|
||||||
getTarget: 18,
|
deleteUser = "deleteUser",
|
||||||
listTargets: 19,
|
listUsers = "listUsers"
|
||||||
updateTarget: 20,
|
|
||||||
|
|
||||||
getUser: 21,
|
|
||||||
deleteUser: 22,
|
|
||||||
listUsers: 23
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkUserActionPermission(actionId: number, req: Request): Promise<boolean> {
|
export async function checkUserActionPermission(actionId: string, req: Request): Promise<boolean> {
|
||||||
const userId = req.user?.id;
|
const userId = req.user?.id;
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
throw createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated');
|
throw createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated');
|
||||||
}
|
|
||||||
|
|
||||||
if (!req.userOrgId) {
|
|
||||||
throw createHttpError(HttpCode.BAD_REQUEST, 'Organization ID is required');
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let userOrgRoleId = req.userOrgRoleId;
|
|
||||||
|
|
||||||
// If userOrgRoleId is not available on the request, fetch it
|
|
||||||
if (userOrgRoleId === undefined) {
|
|
||||||
const userOrgRole = await db.select()
|
|
||||||
.from(userOrgs)
|
|
||||||
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, req.userOrgId)))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (userOrgRole.length === 0) {
|
|
||||||
throw createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization');
|
|
||||||
}
|
|
||||||
|
|
||||||
userOrgRoleId = userOrgRole[0].roleId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user has direct permission for the action in the current org
|
if (!req.userOrgId) {
|
||||||
const userActionPermission = await db.select()
|
throw createHttpError(HttpCode.BAD_REQUEST, 'Organization ID is required');
|
||||||
.from(userActions)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(userActions.userId, userId),
|
|
||||||
eq(userActions.actionId, actionId),
|
|
||||||
eq(userActions.orgId, req.userOrgId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (userActionPermission.length > 0) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no direct permission, check role-based permission
|
try {
|
||||||
const roleActionPermission = await db.select()
|
let userOrgRoleId = req.userOrgRoleId;
|
||||||
.from(roleActions)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(roleActions.actionId, actionId),
|
|
||||||
eq(roleActions.roleId, userOrgRoleId),
|
|
||||||
eq(roleActions.orgId, req.userOrgId)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
return roleActionPermission.length > 0;
|
// If userOrgRoleId is not available on the request, fetch it
|
||||||
|
if (userOrgRoleId === undefined) {
|
||||||
|
const userOrgRole = await db.select()
|
||||||
|
.from(userOrgs)
|
||||||
|
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, req.userOrgId)))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
} catch (error) {
|
if (userOrgRole.length === 0) {
|
||||||
console.error('Error checking user action permission:', error);
|
throw createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization');
|
||||||
throw createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error checking action permission');
|
}
|
||||||
}
|
|
||||||
|
userOrgRoleId = userOrgRole[0].roleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the user has direct permission for the action in the current org
|
||||||
|
const userActionPermission = await db.select()
|
||||||
|
.from(userActions)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(userActions.userId, userId),
|
||||||
|
eq(userActions.actionId, actionId),
|
||||||
|
eq(userActions.orgId, req.userOrgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (userActionPermission.length > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no direct permission, check role-based permission
|
||||||
|
const roleActionPermission = await db.select()
|
||||||
|
.from(roleActions)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(roleActions.actionId, actionId),
|
||||||
|
eq(roleActions.roleId, userOrgRoleId),
|
||||||
|
eq(roleActions.orgId, req.userOrgId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
return roleActionPermission.length > 0;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking user action permission:', error);
|
||||||
|
throw createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error checking action permission');
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -5,36 +5,36 @@ import createHttpError from 'http-errors';
|
||||||
import HttpCode from '@server/types/HttpCode';
|
import HttpCode from '@server/types/HttpCode';
|
||||||
|
|
||||||
interface CheckLimitOptions {
|
interface CheckLimitOptions {
|
||||||
orgId: number;
|
orgId: number;
|
||||||
limitName: string;
|
limitName: string;
|
||||||
currentValue: number;
|
currentValue: number;
|
||||||
increment?: number;
|
increment?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkOrgLimit({ orgId, limitName, currentValue, increment = 0 }: CheckLimitOptions): Promise<boolean> {
|
export async function checkOrgLimit({ orgId, limitName, currentValue, increment = 0 }: CheckLimitOptions): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
const limit = await db.select()
|
const limit = await db.select()
|
||||||
.from(limitsTable)
|
.from(limitsTable)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(limitsTable.orgId, orgId),
|
eq(limitsTable.orgId, orgId),
|
||||||
eq(limitsTable.name, limitName)
|
eq(limitsTable.name, limitName)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (limit.length === 0) {
|
if (limit.length === 0) {
|
||||||
throw createHttpError(HttpCode.NOT_FOUND, `Limit "${limitName}" not found for organization`);
|
throw createHttpError(HttpCode.NOT_FOUND, `Limit "${limitName}" not found for organization`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const limitValue = limit[0].value;
|
||||||
|
|
||||||
|
// Check if the current value plus the increment is within the limit
|
||||||
|
return (currentValue + increment) <= limitValue;
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof Error) {
|
||||||
|
throw createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Error checking limit: ${error.message}`);
|
||||||
|
}
|
||||||
|
throw createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Unknown error occurred while checking limit');
|
||||||
}
|
}
|
||||||
|
|
||||||
const limitValue = limit[0].value;
|
|
||||||
|
|
||||||
// Check if the current value plus the increment is within the limit
|
|
||||||
return (currentValue + increment) <= limitValue;
|
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
throw createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Error checking limit: ${error.message}`);
|
|
||||||
}
|
|
||||||
throw createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Unknown error occurred while checking limit');
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,360 +1,360 @@
|
||||||
[
|
[
|
||||||
"Cape Fox",
|
"Cape Fox",
|
||||||
"Short-Beaked Echidna",
|
"Short-Beaked Echidna",
|
||||||
"Platypus",
|
"Platypus",
|
||||||
"Arctic Ground Squirrel",
|
"Arctic Ground Squirrel",
|
||||||
"Black-Tailed Prairie Dog",
|
"Black-Tailed Prairie Dog",
|
||||||
"Franklin's Ground Squirrel",
|
"Franklin's Ground Squirrel",
|
||||||
"Golden-Mantled Ground Squirrel",
|
"Golden-Mantled Ground Squirrel",
|
||||||
"Groundhog",
|
"Groundhog",
|
||||||
"Yellow-Bellied Marmot",
|
"Yellow-Bellied Marmot",
|
||||||
"Eastern Mole",
|
"Eastern Mole",
|
||||||
"Pink Fairy Armadillo",
|
"Pink Fairy Armadillo",
|
||||||
"Star-Nosed Mole",
|
"Star-Nosed Mole",
|
||||||
"Smooth-Coated Otter",
|
"Smooth-Coated Otter",
|
||||||
"Degu",
|
"Degu",
|
||||||
"Meadow Vole",
|
"Meadow Vole",
|
||||||
"Campbell's Dwarf Hamster",
|
"Campbell's Dwarf Hamster",
|
||||||
"Fat Sand Rat",
|
"Fat Sand Rat",
|
||||||
"Striped Ground Squirrel",
|
"Striped Ground Squirrel",
|
||||||
"Syrian Hamster",
|
"Syrian Hamster",
|
||||||
"Common Wombat",
|
"Common Wombat",
|
||||||
"Greater Bilby",
|
"Greater Bilby",
|
||||||
"Marsupial Mole",
|
"Marsupial Mole",
|
||||||
"Numbat",
|
"Numbat",
|
||||||
"Southern Hairy-Nosed Wombat",
|
"Southern Hairy-Nosed Wombat",
|
||||||
"American Badger",
|
"American Badger",
|
||||||
"Little Blue Penguin",
|
"Little Blue Penguin",
|
||||||
"Giant Armadillo",
|
"Giant Armadillo",
|
||||||
"Eastern Long-Beaked Echidna",
|
"Eastern Long-Beaked Echidna",
|
||||||
"Screaming Hairy Armadillo",
|
"Screaming Hairy Armadillo",
|
||||||
"Chinese Hamster",
|
"Chinese Hamster",
|
||||||
"Roborovski Hamster",
|
"Roborovski Hamster",
|
||||||
"Djungarian Hamster",
|
"Djungarian Hamster",
|
||||||
"Indian Desert Jird",
|
"Indian Desert Jird",
|
||||||
"Great Gerbil",
|
"Great Gerbil",
|
||||||
"Plains Rat",
|
"Plains Rat",
|
||||||
"Big-Headed Mole-Rat",
|
"Big-Headed Mole-Rat",
|
||||||
"Cape Ground Squirrel",
|
"Cape Ground Squirrel",
|
||||||
"Colorado Chipmunk",
|
"Colorado Chipmunk",
|
||||||
"Alpine Chipmunk",
|
"Alpine Chipmunk",
|
||||||
"Cliff Chipmunk",
|
"Cliff Chipmunk",
|
||||||
"Hoary Marmot",
|
"Hoary Marmot",
|
||||||
"Himalayan Marmot",
|
"Himalayan Marmot",
|
||||||
"Olympic Marmot",
|
"Olympic Marmot",
|
||||||
"San Joaquin Antelope Squirrel",
|
"San Joaquin Antelope Squirrel",
|
||||||
"Gunnison's Prairie Dog",
|
"Gunnison's Prairie Dog",
|
||||||
"California Ground Squirrel",
|
"California Ground Squirrel",
|
||||||
"White-Tailed Prairie Dog",
|
"White-Tailed Prairie Dog",
|
||||||
"Spotted Ground Squirrel",
|
"Spotted Ground Squirrel",
|
||||||
"Uinta Ground Squirrel",
|
"Uinta Ground Squirrel",
|
||||||
"Columbian Ground Squirrel",
|
"Columbian Ground Squirrel",
|
||||||
"Richardson's Ground Squirrel",
|
"Richardson's Ground Squirrel",
|
||||||
"European Ground Squirrel",
|
"European Ground Squirrel",
|
||||||
"Speckled Ground Squirrel",
|
"Speckled Ground Squirrel",
|
||||||
"Broad-Footed Mole",
|
"Broad-Footed Mole",
|
||||||
"European Mole",
|
"European Mole",
|
||||||
"Sunda Pangolin",
|
"Sunda Pangolin",
|
||||||
"Desert Rosy Boa",
|
"Desert Rosy Boa",
|
||||||
"Desert Tortoise",
|
"Desert Tortoise",
|
||||||
"Brahminy Blind Snake",
|
"Brahminy Blind Snake",
|
||||||
"Eastern Hognose Snake",
|
"Eastern Hognose Snake",
|
||||||
"Saharan Horned Viper",
|
"Saharan Horned Viper",
|
||||||
"Gopher Snake",
|
"Gopher Snake",
|
||||||
"Scarlet Kingsnake",
|
"Scarlet Kingsnake",
|
||||||
"Eastern Pine Snake",
|
"Eastern Pine Snake",
|
||||||
"Eastern Coral Snake",
|
"Eastern Coral Snake",
|
||||||
"Naked Mole-Rat",
|
"Naked Mole-Rat",
|
||||||
"Mud Snake",
|
"Mud Snake",
|
||||||
"Barbados Threadsnake",
|
"Barbados Threadsnake",
|
||||||
"Arabian Sand Boa",
|
"Arabian Sand Boa",
|
||||||
"Japanese Badger",
|
"Japanese Badger",
|
||||||
"Rainbow Snake",
|
"Rainbow Snake",
|
||||||
"Red-Eyed Crocodile Skink",
|
"Red-Eyed Crocodile Skink",
|
||||||
"Texas Coral Snake",
|
"Texas Coral Snake",
|
||||||
"Glossy Snake",
|
"Glossy Snake",
|
||||||
"Oriental Wolf Snake",
|
"Oriental Wolf Snake",
|
||||||
"Hog Badger",
|
"Hog Badger",
|
||||||
"Mongolian Gerbil",
|
"Mongolian Gerbil",
|
||||||
"Damaraland Mole-Rat",
|
"Damaraland Mole-Rat",
|
||||||
"Steppe Polecat",
|
"Steppe Polecat",
|
||||||
"Woma Python",
|
"Woma Python",
|
||||||
"Southern Hognose Snake",
|
"Southern Hognose Snake",
|
||||||
"Asian Badger",
|
"Asian Badger",
|
||||||
"Giant Girdled Lizard",
|
"Giant Girdled Lizard",
|
||||||
"Common Vole",
|
"Common Vole",
|
||||||
"Bank Vole",
|
"Bank Vole",
|
||||||
"Chinese Ferret-Badger",
|
"Chinese Ferret-Badger",
|
||||||
"Desert Grassland Whiptail Lizard",
|
"Desert Grassland Whiptail Lizard",
|
||||||
"Rough Earth Snake",
|
"Rough Earth Snake",
|
||||||
"Thirteen-Lined Ground Squirrel",
|
"Thirteen-Lined Ground Squirrel",
|
||||||
"Southern Three-Banded Armadillo",
|
"Southern Three-Banded Armadillo",
|
||||||
"Slowworm",
|
"Slowworm",
|
||||||
"Siberian Chipmunk",
|
"Siberian Chipmunk",
|
||||||
"Round-Tailed Ground Squirrel",
|
"Round-Tailed Ground Squirrel",
|
||||||
"Pygmy Rabbit",
|
"Pygmy Rabbit",
|
||||||
"Pied Kingfisher",
|
"Pied Kingfisher",
|
||||||
"Northern Short-Tailed Shrew ",
|
"Northern Short-Tailed Shrew ",
|
||||||
"Northern Pika",
|
"Northern Pika",
|
||||||
"Nine-Banded Armadillo",
|
"Nine-Banded Armadillo",
|
||||||
"Nile Monitor",
|
"Nile Monitor",
|
||||||
"Lowland Streaked Tenrec",
|
"Lowland Streaked Tenrec",
|
||||||
"Lowland Paca",
|
"Lowland Paca",
|
||||||
"Long-Nosed Bandicoot",
|
"Long-Nosed Bandicoot",
|
||||||
"Long-Eared Jerboa",
|
"Long-Eared Jerboa",
|
||||||
"Idaho Ground Squirrel",
|
"Idaho Ground Squirrel",
|
||||||
"Ground Pangolin",
|
"Ground Pangolin",
|
||||||
"Great Plains Rat Snake",
|
"Great Plains Rat Snake",
|
||||||
"Gopher Tortoise",
|
"Gopher Tortoise",
|
||||||
"Giant Pangolin",
|
"Giant Pangolin",
|
||||||
"European Hedgehog",
|
"European Hedgehog",
|
||||||
"European Hamster",
|
"European Hamster",
|
||||||
"Common Box Turtle",
|
"Common Box Turtle",
|
||||||
"Brown Rat",
|
"Brown Rat",
|
||||||
"Bog Turtle",
|
"Bog Turtle",
|
||||||
"Bengal Fox",
|
"Bengal Fox",
|
||||||
"American Alligator",
|
"American Alligator",
|
||||||
"Aardvark",
|
"Aardvark",
|
||||||
"Olm",
|
"Olm",
|
||||||
"Tiger salamander",
|
"Tiger salamander",
|
||||||
"Chinese giant salamander",
|
"Chinese giant salamander",
|
||||||
"Spotted salamander",
|
"Spotted salamander",
|
||||||
"Blue-spotted salamander",
|
"Blue-spotted salamander",
|
||||||
"Eastern worm snake",
|
"Eastern worm snake",
|
||||||
"Deinagkistrodon",
|
"Deinagkistrodon",
|
||||||
"Northern crested newt",
|
"Northern crested newt",
|
||||||
"Barred tiger salamander",
|
"Barred tiger salamander",
|
||||||
"Rainbow bee-eater",
|
"Rainbow bee-eater",
|
||||||
"Sunbeam Snake",
|
"Sunbeam Snake",
|
||||||
"Sandfish Skink",
|
"Sandfish Skink",
|
||||||
"Mexican Mole Lizard",
|
"Mexican Mole Lizard",
|
||||||
"Tarbagan marmot",
|
"Tarbagan marmot",
|
||||||
"Black-Headed Python",
|
"Black-Headed Python",
|
||||||
"Vancouver Island Marmot",
|
"Vancouver Island Marmot",
|
||||||
"Bothrochilus",
|
"Bothrochilus",
|
||||||
"Western Box Turtle",
|
"Western Box Turtle",
|
||||||
"Long-toed salamander",
|
"Long-toed salamander",
|
||||||
"Fat-Tailed Gerbil",
|
"Fat-Tailed Gerbil",
|
||||||
"Mexican Prairie Dog",
|
"Mexican Prairie Dog",
|
||||||
"Marbled salamander",
|
"Marbled salamander",
|
||||||
"Bandy-Bandy",
|
"Bandy-Bandy",
|
||||||
"Smooth Earth Snake",
|
"Smooth Earth Snake",
|
||||||
"Boodie",
|
"Boodie",
|
||||||
"Zebra-Tailed Lizard",
|
"Zebra-Tailed Lizard",
|
||||||
"White-headed langur",
|
"White-headed langur",
|
||||||
"Javan Ferret-Badger",
|
"Javan Ferret-Badger",
|
||||||
"Southwestern Blackhead Snake",
|
"Southwestern Blackhead Snake",
|
||||||
"Malagasy Giant Rat",
|
"Malagasy Giant Rat",
|
||||||
"Big Hairy Armadillo",
|
"Big Hairy Armadillo",
|
||||||
"Camas pocket gopher",
|
"Camas pocket gopher",
|
||||||
"Woodland vole",
|
"Woodland vole",
|
||||||
"Lesser Egyptian jerboa",
|
"Lesser Egyptian jerboa",
|
||||||
"Little Brown Skink",
|
"Little Brown Skink",
|
||||||
"Plains pocket gopher",
|
"Plains pocket gopher",
|
||||||
"Alaska marmot",
|
"Alaska marmot",
|
||||||
"Gray marmot",
|
"Gray marmot",
|
||||||
"Louisiana waterthrush",
|
"Louisiana waterthrush",
|
||||||
"Ord's kangaroo rat",
|
"Ord's kangaroo rat",
|
||||||
"North American least shrew",
|
"North American least shrew",
|
||||||
"Western rosella",
|
"Western rosella",
|
||||||
"Northwestern salamander",
|
"Northwestern salamander",
|
||||||
"Acrochordus granulatus",
|
"Acrochordus granulatus",
|
||||||
"Kowari",
|
"Kowari",
|
||||||
"Anilius",
|
"Anilius",
|
||||||
"Gastrophryne carolinensis",
|
"Gastrophryne carolinensis",
|
||||||
"Yellow mud turtle",
|
"Yellow mud turtle",
|
||||||
"Plateau pika",
|
"Plateau pika",
|
||||||
"Steppe lemming",
|
"Steppe lemming",
|
||||||
"American shrew mole",
|
"American shrew mole",
|
||||||
"Calabar python",
|
"Calabar python",
|
||||||
"Dermophis mexicanus",
|
"Dermophis mexicanus",
|
||||||
"Rufous rat-kangaroo",
|
"Rufous rat-kangaroo",
|
||||||
"Hairy-tailed mole",
|
"Hairy-tailed mole",
|
||||||
"Mexican burrowing toad",
|
"Mexican burrowing toad",
|
||||||
"Seven-banded armadillo",
|
"Seven-banded armadillo",
|
||||||
"Scaphiopus holbrookii",
|
"Scaphiopus holbrookii",
|
||||||
"Asiatic brush-tailed porcupine",
|
"Asiatic brush-tailed porcupine",
|
||||||
"Bolson tortoise",
|
"Bolson tortoise",
|
||||||
"Common midwife toad",
|
"Common midwife toad",
|
||||||
"Ambystoma talpoideum",
|
"Ambystoma talpoideum",
|
||||||
"Crucifix toad",
|
"Crucifix toad",
|
||||||
"Red Hills salamander",
|
"Red Hills salamander",
|
||||||
"Uperodon taprobanicus",
|
"Uperodon taprobanicus",
|
||||||
"Plains spadefoot toad",
|
"Plains spadefoot toad",
|
||||||
"Spea hammondii",
|
"Spea hammondii",
|
||||||
"Puerto Rican crested toad",
|
"Puerto Rican crested toad",
|
||||||
"Physalaemus nattereri",
|
"Physalaemus nattereri",
|
||||||
"Yosemite toad",
|
"Yosemite toad",
|
||||||
"Frosted flatwoods salamander",
|
"Frosted flatwoods salamander",
|
||||||
"Striped newt",
|
"Striped newt",
|
||||||
"Streamside salamander",
|
"Streamside salamander",
|
||||||
"Southern red-backed salamander",
|
"Southern red-backed salamander",
|
||||||
"Spencer's burrowing frog",
|
"Spencer's burrowing frog",
|
||||||
"Ringed salamander",
|
"Ringed salamander",
|
||||||
"Kaloula baleata",
|
"Kaloula baleata",
|
||||||
"Uperodon systoma",
|
"Uperodon systoma",
|
||||||
"Ichthyophis beddomei",
|
"Ichthyophis beddomei",
|
||||||
"Uperodon globulosus",
|
"Uperodon globulosus",
|
||||||
"Herpele squalostoma",
|
"Herpele squalostoma",
|
||||||
"Ichthyophis mindanaoensis",
|
"Ichthyophis mindanaoensis",
|
||||||
"Sandhill frog",
|
"Sandhill frog",
|
||||||
"Strecker's chorus frog",
|
"Strecker's chorus frog",
|
||||||
"Uraeotyphlus oxyurus",
|
"Uraeotyphlus oxyurus",
|
||||||
"Caecilia nigricans",
|
"Caecilia nigricans",
|
||||||
"Uraeotyphlus menoni",
|
"Uraeotyphlus menoni",
|
||||||
"Savannah forest tree frog",
|
"Savannah forest tree frog",
|
||||||
"Uraeotyphlus interruptus",
|
"Uraeotyphlus interruptus",
|
||||||
"Rose's rain frog",
|
"Rose's rain frog",
|
||||||
"Dermophis parviceps",
|
"Dermophis parviceps",
|
||||||
"Leptopelis gramineus",
|
"Leptopelis gramineus",
|
||||||
"Rhombophryne coudreaui",
|
"Rhombophryne coudreaui",
|
||||||
"Elachistocleis pearsei",
|
"Elachistocleis pearsei",
|
||||||
"Hylodes heyeri",
|
"Hylodes heyeri",
|
||||||
"Carphophis vermis",
|
"Carphophis vermis",
|
||||||
"Anniella pulchra",
|
"Anniella pulchra",
|
||||||
"Lampropeltis calligaster rhombomaculata",
|
"Lampropeltis calligaster rhombomaculata",
|
||||||
"Xerotyphlops vermicularis",
|
"Xerotyphlops vermicularis",
|
||||||
"Iberian worm lizard",
|
"Iberian worm lizard",
|
||||||
"Lytorhynchus diadema",
|
"Lytorhynchus diadema",
|
||||||
"Micrurus frontalis",
|
"Micrurus frontalis",
|
||||||
"Euprepiophis conspicillata",
|
"Euprepiophis conspicillata",
|
||||||
"Amphisbaena fuliginosa",
|
"Amphisbaena fuliginosa",
|
||||||
"Greater earless lizard",
|
"Greater earless lizard",
|
||||||
"Afrotyphlops schlegelii",
|
"Afrotyphlops schlegelii",
|
||||||
"Texas lined snake",
|
"Texas lined snake",
|
||||||
"Atractaspis branchi",
|
"Atractaspis branchi",
|
||||||
"Calamaria gervaisii",
|
"Calamaria gervaisii",
|
||||||
"Brachyurophis fasciolatus",
|
"Brachyurophis fasciolatus",
|
||||||
"Brongersma's worm snake",
|
"Brongersma's worm snake",
|
||||||
"Letheobia simonii",
|
"Letheobia simonii",
|
||||||
"Grypotyphlops acutus",
|
"Grypotyphlops acutus",
|
||||||
"Acontias breviceps",
|
"Acontias breviceps",
|
||||||
"Reticulate worm snake",
|
"Reticulate worm snake",
|
||||||
"Trinidad worm snake",
|
"Trinidad worm snake",
|
||||||
"Amphisbaena microcephala",
|
"Amphisbaena microcephala",
|
||||||
"Lerista labialis",
|
"Lerista labialis",
|
||||||
"Flathead worm snake",
|
"Flathead worm snake",
|
||||||
"Mertens's worm lizard",
|
"Mertens's worm lizard",
|
||||||
"Elegant worm snake",
|
"Elegant worm snake",
|
||||||
"Iranian worm snake",
|
"Iranian worm snake",
|
||||||
"Pernambuco worm snake",
|
"Pernambuco worm snake",
|
||||||
"Crest-tailed mulgara",
|
"Crest-tailed mulgara",
|
||||||
"Southern long-nosed armadillo",
|
"Southern long-nosed armadillo",
|
||||||
"Greater fairy armadillo",
|
"Greater fairy armadillo",
|
||||||
"Steppe pika",
|
"Steppe pika",
|
||||||
"Black-capped marmot",
|
"Black-capped marmot",
|
||||||
"Armored rat",
|
"Armored rat",
|
||||||
"Giant mole-rat",
|
"Giant mole-rat",
|
||||||
"Montane vole",
|
"Montane vole",
|
||||||
"Oldfield mouse",
|
"Oldfield mouse",
|
||||||
"Southeastern pocket gopher",
|
"Southeastern pocket gopher",
|
||||||
"Long-tailed vole",
|
"Long-tailed vole",
|
||||||
"Greater naked-tailed armadillo",
|
"Greater naked-tailed armadillo",
|
||||||
"Common mole-rat",
|
"Common mole-rat",
|
||||||
"Philippine porcupine",
|
"Philippine porcupine",
|
||||||
"Milne-Edwards's sifaka",
|
"Milne-Edwards's sifaka",
|
||||||
"Townsend's mole",
|
"Townsend's mole",
|
||||||
"Giant golden mole",
|
"Giant golden mole",
|
||||||
"Daurian pika",
|
"Daurian pika",
|
||||||
"Cape golden mole",
|
"Cape golden mole",
|
||||||
"Yellow-faced pocket gopher",
|
"Yellow-faced pocket gopher",
|
||||||
"Indian gerbil",
|
"Indian gerbil",
|
||||||
"Plains viscacha rat",
|
"Plains viscacha rat",
|
||||||
"Red tree vole",
|
"Red tree vole",
|
||||||
"Middle East blind mole-rat",
|
"Middle East blind mole-rat",
|
||||||
"Mountain paca",
|
"Mountain paca",
|
||||||
"Pallas's pika",
|
"Pallas's pika",
|
||||||
"Bicolored shrew",
|
"Bicolored shrew",
|
||||||
"Cape mole-rat",
|
"Cape mole-rat",
|
||||||
"Cascade golden-mantled ground squirrel",
|
"Cascade golden-mantled ground squirrel",
|
||||||
"Unstriped ground squirrel",
|
"Unstriped ground squirrel",
|
||||||
"Townsend's vole",
|
"Townsend's vole",
|
||||||
"Yellow ground squirrel",
|
"Yellow ground squirrel",
|
||||||
"Desert pocket gopher",
|
"Desert pocket gopher",
|
||||||
"Bunny rat",
|
"Bunny rat",
|
||||||
"Washington ground squirrel",
|
"Washington ground squirrel",
|
||||||
"Mole-like rice tenrec",
|
"Mole-like rice tenrec",
|
||||||
"Greater mole-rat",
|
"Greater mole-rat",
|
||||||
"Hottentot golden mole",
|
"Hottentot golden mole",
|
||||||
"Plains pocket mouse",
|
"Plains pocket mouse",
|
||||||
"Cheesman's gerbil",
|
"Cheesman's gerbil",
|
||||||
"Judean Mountains blind mole-rat",
|
"Judean Mountains blind mole-rat",
|
||||||
"Chisel-toothed kangaroo rat",
|
"Chisel-toothed kangaroo rat",
|
||||||
"Rough-haired golden mole",
|
"Rough-haired golden mole",
|
||||||
"Southeastern shrew",
|
"Southeastern shrew",
|
||||||
"California pocket mouse",
|
"California pocket mouse",
|
||||||
"Coruro",
|
"Coruro",
|
||||||
"Merriam's shrew",
|
"Merriam's shrew",
|
||||||
"Long-tailed mole",
|
"Long-tailed mole",
|
||||||
"Orange leaf-nosed bat",
|
"Orange leaf-nosed bat",
|
||||||
"South African pouched mouse",
|
"South African pouched mouse",
|
||||||
"Selous's mongoose",
|
"Selous's mongoose",
|
||||||
"Ash-grey mouse",
|
"Ash-grey mouse",
|
||||||
"Russet ground squirrel",
|
"Russet ground squirrel",
|
||||||
"Gulf Coast kangaroo rat",
|
"Gulf Coast kangaroo rat",
|
||||||
"Olive-backed pocket mouse",
|
"Olive-backed pocket mouse",
|
||||||
"Northeast African mole-rat",
|
"Northeast African mole-rat",
|
||||||
"San Diego pocket mouse",
|
"San Diego pocket mouse",
|
||||||
"Nelson's pocket mouse",
|
"Nelson's pocket mouse",
|
||||||
"Geoffroy's horseshoe bat",
|
"Geoffroy's horseshoe bat",
|
||||||
"Narrow-faced kangaroo rat",
|
"Narrow-faced kangaroo rat",
|
||||||
"Chilean rock rat",
|
"Chilean rock rat",
|
||||||
"R\u00fcppell's horseshoe bat",
|
"R\u00fcppell's horseshoe bat",
|
||||||
"Long-tailed pocket mouse",
|
"Long-tailed pocket mouse",
|
||||||
"Aztec mouse",
|
"Aztec mouse",
|
||||||
"Western mouse",
|
"Western mouse",
|
||||||
"Felten's myotis",
|
"Felten's myotis",
|
||||||
"Akodon azarae",
|
"Akodon azarae",
|
||||||
"Talas tuco-tuco",
|
"Talas tuco-tuco",
|
||||||
"Upper Galilee Mountains blind mole-rat",
|
"Upper Galilee Mountains blind mole-rat",
|
||||||
"Pearson's tuco-tuco",
|
"Pearson's tuco-tuco",
|
||||||
"Mount Carmel blind mole-rat",
|
"Mount Carmel blind mole-rat",
|
||||||
"Plethobasus cyphyus",
|
"Plethobasus cyphyus",
|
||||||
"Long-Nosed Snake",
|
"Long-Nosed Snake",
|
||||||
"Russian Desman",
|
"Russian Desman",
|
||||||
"Texas Blind Snake",
|
"Texas Blind Snake",
|
||||||
"Florida Box Turtle",
|
"Florida Box Turtle",
|
||||||
"Lesser Bandicoot Rat",
|
"Lesser Bandicoot Rat",
|
||||||
"Bush Rat",
|
"Bush Rat",
|
||||||
"Six-Lined Racerunner",
|
"Six-Lined Racerunner",
|
||||||
"Eastern Bearded Dragon",
|
"Eastern Bearded Dragon",
|
||||||
"Lesser Antillean Iguana",
|
"Lesser Antillean Iguana",
|
||||||
"Eastern Mud Turtle",
|
"Eastern Mud Turtle",
|
||||||
"Slender Glass Lizard",
|
"Slender Glass Lizard",
|
||||||
"Scarlet Snake",
|
"Scarlet Snake",
|
||||||
"Natal Multimammate Mouse",
|
"Natal Multimammate Mouse",
|
||||||
"Mountain Beaver",
|
"Mountain Beaver",
|
||||||
"Bobak Marmot",
|
"Bobak Marmot",
|
||||||
"Kirtland's Snake",
|
"Kirtland's Snake",
|
||||||
"Pine Woods Snake",
|
"Pine Woods Snake",
|
||||||
"Western Whiptail",
|
"Western Whiptail",
|
||||||
"Boxelder bug",
|
"Boxelder bug",
|
||||||
"Porcellio scaber",
|
"Porcellio scaber",
|
||||||
"German cockroach",
|
"German cockroach",
|
||||||
"Forficula auricularia",
|
"Forficula auricularia",
|
||||||
"Anisolabis maritima",
|
"Anisolabis maritima",
|
||||||
"Trigoniulus corallinus",
|
"Trigoniulus corallinus",
|
||||||
"Sinea diadema",
|
"Sinea diadema",
|
||||||
"Black imported fire ant",
|
"Black imported fire ant",
|
||||||
"Scutigera coleoptrata",
|
"Scutigera coleoptrata",
|
||||||
"Mastigoproctus giganteus",
|
"Mastigoproctus giganteus",
|
||||||
"Dermacentor andersoni",
|
"Dermacentor andersoni",
|
||||||
"Deathstalker",
|
"Deathstalker",
|
||||||
"Larinioides cornutus",
|
"Larinioides cornutus",
|
||||||
"Cheiracanthium inclusum",
|
"Cheiracanthium inclusum",
|
||||||
"Latrodectus hesperus",
|
"Latrodectus hesperus",
|
||||||
"Scytodes thoracica",
|
"Scytodes thoracica",
|
||||||
"Atypus affinis",
|
"Atypus affinis",
|
||||||
"Illacme plenipes",
|
"Illacme plenipes",
|
||||||
"Ommatoiulus moreleti",
|
"Ommatoiulus moreleti",
|
||||||
"Narceus americanus",
|
"Narceus americanus",
|
||||||
"Madagascar hissing cockroach",
|
"Madagascar hissing cockroach",
|
||||||
"Labidura riparia",
|
"Labidura riparia",
|
||||||
"Forficula smyrnensis",
|
"Forficula smyrnensis",
|
||||||
"Argentine ant",
|
"Argentine ant",
|
||||||
"Texas leafcutter ant",
|
"Texas leafcutter ant",
|
||||||
"Brachypelma klaasi",
|
"Brachypelma klaasi",
|
||||||
"Western Blind Snake",
|
"Western Blind Snake",
|
||||||
"Desert Box Turtle",
|
"Desert Box Turtle",
|
||||||
"African Striped Weasel"
|
"African Striped Weasel"
|
||||||
]
|
]
|
|
@ -130,7 +130,7 @@ export const passwordResetTokens = sqliteTable("passwordResetTokens", {
|
||||||
});
|
});
|
||||||
|
|
||||||
export const actions = sqliteTable("actions", {
|
export const actions = sqliteTable("actions", {
|
||||||
actionId: integer("actionId").primaryKey({ autoIncrement: true }),
|
actionId: text("actionId").primaryKey(),
|
||||||
name: text("name").notNull(),
|
name: text("name").notNull(),
|
||||||
description: text("description"),
|
description: text("description"),
|
||||||
});
|
});
|
||||||
|
@ -146,7 +146,7 @@ export const roleActions = sqliteTable("roleActions", {
|
||||||
roleId: integer("roleId")
|
roleId: integer("roleId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => roles.roleId, { onDelete: "cascade" }),
|
.references(() => roles.roleId, { onDelete: "cascade" }),
|
||||||
actionId: integer("actionId")
|
actionId: text("actionId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => actions.actionId, { onDelete: "cascade" }),
|
.references(() => actions.actionId, { onDelete: "cascade" }),
|
||||||
orgId: integer("orgId")
|
orgId: integer("orgId")
|
||||||
|
@ -158,7 +158,7 @@ export const userActions = sqliteTable("userActions", {
|
||||||
userId: text("userId")
|
userId: text("userId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => users.id, { onDelete: "cascade" }),
|
.references(() => users.id, { onDelete: "cascade" }),
|
||||||
actionId: integer("actionId")
|
actionId: text("actionId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => actions.actionId, { onDelete: "cascade" }),
|
.references(() => actions.actionId, { onDelete: "cascade" }),
|
||||||
orgId: integer("orgId")
|
orgId: integer("orgId")
|
||||||
|
|
|
@ -6,28 +6,28 @@ import createHttpError from 'http-errors';
|
||||||
import HttpCode from '@server/types/HttpCode';
|
import HttpCode from '@server/types/HttpCode';
|
||||||
|
|
||||||
export async function getUserOrgs(req: Request, res: Response, next: NextFunction) {
|
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) {
|
if (!userId) {
|
||||||
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
|
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const userOrganizations = await db.select({
|
const userOrganizations = await db.select({
|
||||||
orgId: userOrgs.orgId,
|
orgId: userOrgs.orgId,
|
||||||
role: userOrgs.role,
|
roleId: userOrgs.roleId,
|
||||||
})
|
})
|
||||||
.from(userOrgs)
|
.from(userOrgs)
|
||||||
.where(eq(userOrgs.userId, userId));
|
.where(eq(userOrgs.userId, userId));
|
||||||
|
|
||||||
req.userOrgs = userOrganizations.map(org => org.orgId);
|
req.userOrgIds = userOrganizations.map(org => org.orgId);
|
||||||
// req.userOrgRoleIds = userOrganizations.reduce((acc, org) => {
|
// req.userOrgRoleIds = userOrganizations.reduce((acc, org) => {
|
||||||
// acc[org.orgId] = org.role;
|
// acc[org.orgId] = org.role;
|
||||||
// return acc;
|
// return acc;
|
||||||
// }, {} as Record<number, string>);
|
// }, {} as Record<number, string>);
|
||||||
|
|
||||||
next();
|
next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error retrieving user organizations'));
|
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error retrieving user organizations'));
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,31 +7,31 @@ import HttpCode from '@server/types/HttpCode';
|
||||||
import { AuthenticatedRequest } from '@server/types/Auth';
|
import { AuthenticatedRequest } from '@server/types/Auth';
|
||||||
|
|
||||||
export function verifyOrgAccess(req: Request, res: Response, next: NextFunction) {
|
export function verifyOrgAccess(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
|
||||||
const orgId = parseInt(req.params.orgId);
|
const orgId = parseInt(req.params.orgId);
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
|
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNaN(orgId)) {
|
if (isNaN(orgId)) {
|
||||||
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID'));
|
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID'));
|
||||||
}
|
}
|
||||||
|
|
||||||
db.select()
|
db.select()
|
||||||
.from(userOrgs)
|
.from(userOrgs)
|
||||||
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId)))
|
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId)))
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.length === 0) {
|
if (result.length === 0) {
|
||||||
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
|
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
|
||||||
} else {
|
} else {
|
||||||
// User has access, attach the user's role to the request for potential future use
|
// User has access, attach the user's role to the request for potential future use
|
||||||
req.userOrgRoleId = result[0].roleId;
|
req.userOrgRoleId = result[0].roleId;
|
||||||
req.userOrgId = orgId;
|
req.userOrgId = orgId;
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access'));
|
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,73 +6,73 @@ import createHttpError from 'http-errors';
|
||||||
import HttpCode from '@server/types/HttpCode';
|
import HttpCode from '@server/types/HttpCode';
|
||||||
|
|
||||||
export async function verifyResourceAccess(req: Request, res: Response, next: NextFunction) {
|
export async function verifyResourceAccess(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
|
||||||
const resourceId = req.params.resourceId;
|
const resourceId = req.params.resourceId;
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
|
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
|
||||||
}
|
|
||||||
|
|
||||||
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[0].orgId) {
|
try {
|
||||||
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Resource with ID ${resourceId} does not have an organization ID`));
|
// 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[0].orgId) {
|
||||||
|
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Resource with ID ${resourceId} does not have an organization ID`));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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'));
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -6,74 +6,74 @@ import createHttpError from 'http-errors';
|
||||||
import HttpCode from '@server/types/HttpCode';
|
import HttpCode from '@server/types/HttpCode';
|
||||||
|
|
||||||
export async function verifySiteAccess(req: Request, res: Response, next: NextFunction) {
|
export async function verifySiteAccess(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
|
||||||
const siteId = parseInt(req.params.siteId);
|
const siteId = parseInt(req.params.siteId);
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
|
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
|
||||||
}
|
|
||||||
|
|
||||||
if (isNaN(siteId)) {
|
|
||||||
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid site ID'));
|
|
||||||
}
|
|
||||||
|
|
||||||
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[0].orgId) {
|
if (isNaN(siteId)) {
|
||||||
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, `Site with ID ${siteId} does not have an organization ID`));
|
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid site ID'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user's role ID in the organization
|
try {
|
||||||
const userOrgRole = await db.select()
|
// Get the site
|
||||||
.from(userOrgs)
|
const site = await db.select().from(sites).where(eq(sites.siteId, siteId)).limit(1);
|
||||||
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, site[0].orgId)))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (userOrgRole.length === 0) {
|
if (site.length === 0) {
|
||||||
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
|
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`));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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'));
|
||||||
}
|
}
|
||||||
|
|
||||||
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'));
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -6,79 +6,79 @@ import createHttpError from 'http-errors';
|
||||||
import HttpCode from '@server/types/HttpCode';
|
import HttpCode from '@server/types/HttpCode';
|
||||||
|
|
||||||
export async function verifyTargetAccess(req: Request, res: Response, next: NextFunction) {
|
export async function verifyTargetAccess(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
|
||||||
const targetId = parseInt(req.params.targetId);
|
const targetId = parseInt(req.params.targetId);
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
|
return next(createHttpError(HttpCode.UNAUTHORIZED, 'User not authenticated'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNaN(targetId)) {
|
if (isNaN(targetId)) {
|
||||||
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID'));
|
return next(createHttpError(HttpCode.BAD_REQUEST, 'Invalid organization ID'));
|
||||||
}
|
}
|
||||||
|
|
||||||
const target = await db.select()
|
const target = await db.select()
|
||||||
.from(targets)
|
.from(targets)
|
||||||
.where(eq(targets.targetId, targetId))
|
.where(eq(targets.targetId, targetId))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (target.length === 0) {
|
if (target.length === 0) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.NOT_FOUND,
|
HttpCode.NOT_FOUND,
|
||||||
`target with ID ${targetId} not found`
|
`target with ID ${targetId} not found`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resourceId = target[0].resourceId;
|
const resourceId = target[0].resourceId;
|
||||||
|
|
||||||
if (resourceId) {
|
if (resourceId) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
`target with ID ${targetId} does not have a resource ID`
|
`target with ID ${targetId} does not have a resource ID`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resource = await db.select()
|
const resource = await db.select()
|
||||||
.from(resources)
|
.from(resources)
|
||||||
.where(eq(resources.resourceId, resourceId!))
|
.where(eq(resources.resourceId, resourceId!))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (resource.length === 0) {
|
if (resource.length === 0) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.NOT_FOUND,
|
HttpCode.NOT_FOUND,
|
||||||
`resource with ID ${resourceId} not found`
|
`resource with ID ${resourceId} not found`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!resource[0].orgId) {
|
if (!resource[0].orgId) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
`resource with ID ${resourceId} does not have an organization ID`
|
`resource with ID ${resourceId} does not have an organization ID`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
db.select()
|
db.select()
|
||||||
.from(userOrgs)
|
.from(userOrgs)
|
||||||
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, resource[0].orgId)))
|
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, resource[0].orgId)))
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result.length === 0) {
|
if (result.length === 0) {
|
||||||
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
|
next(createHttpError(HttpCode.FORBIDDEN, 'User does not have access to this organization'));
|
||||||
} else {
|
} else {
|
||||||
// User has access, attach the user's role to the request for potential future use
|
// User has access, attach the user's role to the request for potential future use
|
||||||
req.userOrgRoleId = result[0].roleId;
|
req.userOrgRoleId = result[0].roleId;
|
||||||
req.userOrgId = resource[0].orgId!;
|
req.userOrgId = resource[0].orgId!;
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access'));
|
next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, 'Error verifying organization access'));
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -3,6 +3,8 @@ import { DrizzleError, eq } from 'drizzle-orm';
|
||||||
import { sites, resources, targets, exitNodes } from '@server/db/schema';
|
import { sites, resources, targets, exitNodes } from '@server/db/schema';
|
||||||
import db from '@server/db';
|
import db from '@server/db';
|
||||||
import logger from '@server/logger';
|
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> => {
|
export const getConfig = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
|
@ -55,11 +57,7 @@ export const getConfig = async (req: Request, res: Response, next: NextFunction)
|
||||||
res.json(config);
|
res.json(config);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Error querying database:', error);
|
logger.error('Error querying database:', error);
|
||||||
if (error instanceof DrizzleError) {
|
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred..."));
|
||||||
res.status(500).json({ error: 'Database query error', message: error.message });
|
|
||||||
} else {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,55 +3,64 @@ import { DrizzleError, eq } from 'drizzle-orm';
|
||||||
import { sites, resources, targets, exitNodes } from '@server/db/schema';
|
import { sites, resources, targets, exitNodes } from '@server/db/schema';
|
||||||
import db from '@server/db';
|
import db from '@server/db';
|
||||||
import logger from '@server/logger';
|
import logger from '@server/logger';
|
||||||
|
import createHttpError from 'http-errors';
|
||||||
|
import HttpCode from '@server/types/HttpCode';
|
||||||
|
import response from "@server/utils/response";
|
||||||
|
|
||||||
interface PeerBandwidth {
|
interface PeerBandwidth {
|
||||||
publicKey: string;
|
publicKey: string;
|
||||||
bytesIn: number;
|
bytesIn: number;
|
||||||
bytesOut: number;
|
bytesOut: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const receiveBandwidth = async (req: Request, res: Response, next: NextFunction): Promise<void> => {
|
export const receiveBandwidth = async (req: Request, res: Response, next: NextFunction): Promise<any> => {
|
||||||
try {
|
try {
|
||||||
const bandwidthData: PeerBandwidth[] = req.body;
|
const bandwidthData: PeerBandwidth[] = req.body;
|
||||||
|
|
||||||
if (!Array.isArray(bandwidthData)) {
|
if (!Array.isArray(bandwidthData)) {
|
||||||
throw new Error('Invalid bandwidth data');
|
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 {
|
function calculateSubnet(index: number): string {
|
||||||
const baseIp = 10 << 24;
|
const baseIp = 10 << 24;
|
||||||
const subnetSize = 16;
|
const subnetSize = 16;
|
||||||
return `${(baseIp | (index * subnetSize)).toString()}/28`;
|
return `${(baseIp | (index * subnetSize)).toString()}/28`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,59 +6,59 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const createOrgSchema = z.object({
|
const createOrgSchema = z.object({
|
||||||
name: z.string().min(1).max(255),
|
name: z.string().min(1).max(255),
|
||||||
domain: z.string().min(1).max(255),
|
domain: z.string().min(1).max(255),
|
||||||
});
|
});
|
||||||
|
|
||||||
const MAX_ORGS = 5;
|
const MAX_ORGS = 5;
|
||||||
|
|
||||||
export async function createOrg(req: Request, res: Response, next: NextFunction): Promise<any> {
|
export async function createOrg(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedBody = createOrgSchema.safeParse(req.body);
|
const parsedBody = createOrgSchema.safeParse(req.body);
|
||||||
if (!parsedBody.success) {
|
if (!parsedBody.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedBody.error.errors.map(e => e.message).join(', ')
|
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.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 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,54 +7,54 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const deleteOrgSchema = z.object({
|
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> {
|
export async function deleteOrg(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedParams = deleteOrgSchema.safeParse(req.params);
|
const parsedParams = deleteOrgSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 res.status(HttpCode.OK).send(
|
|
||||||
response(res, {
|
|
||||||
data: null,
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "Organization deleted successfully",
|
|
||||||
status: HttpCode.OK,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,55 +7,55 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const getOrgSchema = z.object({
|
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> {
|
export async function getOrg(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedParams = getOrgSchema.safeParse(req.params);
|
const parsedParams = getOrgSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,88 +7,88 @@ import HttpCode from '@server/types/HttpCode';
|
||||||
import createHttpError from 'http-errors';
|
import createHttpError from 'http-errors';
|
||||||
import { sql, inArray } from 'drizzle-orm';
|
import { sql, inArray } from 'drizzle-orm';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const listOrgsSchema = z.object({
|
const listOrgsSchema = 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)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function listOrgs(req: Request, res: Response, next: NextFunction): Promise<any> {
|
export async function listOrgs(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedQuery = listOrgsSchema.safeParse(req.query);
|
const parsedQuery = listOrgsSchema.safeParse(req.query);
|
||||||
if (!parsedQuery.success) {
|
if (!parsedQuery.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedQuery.error.errors.map(e => e.message).join(', ')
|
parsedQuery.error.errors.map(e => e.message).join(', ')
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { limit, offset } = parsedQuery.data;
|
const { limit, offset } = parsedQuery.data;
|
||||||
|
|
||||||
// Check if the user has permission to list sites
|
// Check if the user has permission to list sites
|
||||||
const hasPermission = await checkUserActionPermission(ActionsEnum.listOrgs, req);
|
const hasPermission = await checkUserActionPermission(ActionsEnum.listOrgs, req);
|
||||||
if (!hasPermission) {
|
if (!hasPermission) {
|
||||||
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
|
return next(createHttpError(HttpCode.FORBIDDEN, 'User does not have permission to list sites'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the userOrgs passed from the middleware
|
// Use the userOrgs passed from the middleware
|
||||||
const userOrgIds = req.userOrgIds;
|
const userOrgIds = req.userOrgIds;
|
||||||
|
|
||||||
if (!userOrgIds || userOrgIds.length === 0) {
|
if (!userOrgIds || userOrgIds.length === 0) {
|
||||||
return res.status(HttpCode.OK).send(
|
return res.status(HttpCode.OK).send(
|
||||||
response(res, {
|
response(res, {
|
||||||
data: {
|
data: {
|
||||||
organizations: [],
|
organizations: [],
|
||||||
pagination: {
|
pagination: {
|
||||||
total: 0,
|
total: 0,
|
||||||
limit,
|
limit,
|
||||||
offset,
|
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,
|
||||||
success: true,
|
error: false,
|
||||||
error: false,
|
message: "Organizations retrieved successfully",
|
||||||
message: "No organizations found for the user",
|
status: HttpCode.OK,
|
||||||
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.userOrgRoleIds[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);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -7,74 +7,74 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const updateOrgParamsSchema = z.object({
|
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({
|
const updateOrgBodySchema = z.object({
|
||||||
name: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255).optional(),
|
||||||
domain: z.string().min(1).max(255).optional(),
|
domain: z.string().min(1).max(255).optional(),
|
||||||
}).refine(data => Object.keys(data).length > 0, {
|
}).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> {
|
export async function updateOrg(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedParams = updateOrgParamsSchema.safeParse(req.params);
|
const parsedParams = updateOrgParamsSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
|
|
||||||
// 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 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,74 +6,74 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const createResourceParamsSchema = z.object({
|
const createResourceParamsSchema = z.object({
|
||||||
siteId: z.number().int().positive(),
|
siteId: z.number().int().positive(),
|
||||||
orgId: 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({
|
||||||
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(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function createResource(req: Request, res: Response, next: NextFunction): Promise<any> {
|
export async function createResource(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
// Validate request body
|
// Validate request body
|
||||||
const parsedBody = createResourceSchema.safeParse(req.body);
|
const parsedBody = createResourceSchema.safeParse(req.body);
|
||||||
if (!parsedBody.success) {
|
if (!parsedBody.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedBody.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,57 +7,57 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
// Define Zod schema for request parameters validation
|
// Define Zod schema for request parameters validation
|
||||||
const deleteResourceSchema = z.object({
|
const deleteResourceSchema = z.object({
|
||||||
resourceId: z.string().uuid()
|
resourceId: z.string().uuid()
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function deleteResource(req: Request, res: Response, next: NextFunction): Promise<any> {
|
export async function deleteResource(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
// Validate request parameters
|
// Validate request parameters
|
||||||
const parsedParams = deleteResourceSchema.safeParse(req.params);
|
const parsedParams = deleteResourceSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 res.status(HttpCode.OK).send(
|
|
||||||
response(res, {
|
|
||||||
data: null,
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "Resource deleted successfully",
|
|
||||||
status: HttpCode.OK,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,58 +7,58 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
// Define Zod schema for request parameters validation
|
// Define Zod schema for request parameters validation
|
||||||
const getResourceSchema = z.object({
|
const getResourceSchema = z.object({
|
||||||
resourceId: z.string().uuid()
|
resourceId: z.string().uuid()
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function getResource(req: Request, res: Response, next: NextFunction): Promise<any> {
|
export async function getResource(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
// Validate request parameters
|
// Validate request parameters
|
||||||
const parsedParams = getResourceSchema.safeParse(req.params);
|
const parsedParams = getResourceSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import HttpCode from '@server/types/HttpCode';
|
||||||
import createHttpError from 'http-errors';
|
import createHttpError from 'http-errors';
|
||||||
import { sql, eq, and, or, inArray } from 'drizzle-orm';
|
import { sql, eq, and, or, inArray } from 'drizzle-orm';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const listResourcesParamsSchema = z.object({
|
const listResourcesParamsSchema = z.object({
|
||||||
siteId: z.coerce.number().int().positive().optional(),
|
siteId: z.coerce.number().int().positive().optional(),
|
||||||
|
@ -16,99 +17,98 @@ const listResourcesParamsSchema = z.object({
|
||||||
});
|
});
|
||||||
|
|
||||||
const listResourcesSchema = z.object({
|
const listResourcesSchema = z.object({
|
||||||
limit: z.coerce.number().int().positive().default(10),
|
limit: z.coerce.number().int().positive().default(10),
|
||||||
offset: z.coerce.number().int().nonnegative().default(0),
|
offset: z.coerce.number().int().nonnegative().default(0),
|
||||||
});
|
});
|
||||||
|
|
||||||
interface RequestWithOrgAndRole extends Request {
|
interface RequestWithOrgAndRole extends Request {
|
||||||
userOrgRoleId?: number;
|
userOrgRoleId?: number;
|
||||||
orgId?: number;
|
orgId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function listResources(req: RequestWithOrgAndRole, res: Response, next: NextFunction): Promise<any> {
|
export async function listResources(req: RequestWithOrgAndRole, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedQuery = listResourcesSchema.safeParse(req.query);
|
const parsedQuery = listResourcesSchema.safeParse(req.query);
|
||||||
if (!parsedQuery.success) {
|
if (!parsedQuery.success) {
|
||||||
return next(createHttpError(HttpCode.BAD_REQUEST, parsedQuery.error.errors.map(e => e.message).join(', ')));
|
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;
|
|
||||||
|
|
||||||
// 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 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -7,78 +7,78 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
// Define Zod schema for request parameters validation
|
// Define Zod schema for request parameters validation
|
||||||
const updateResourceParamsSchema = z.object({
|
const updateResourceParamsSchema = z.object({
|
||||||
resourceId: z.string().uuid()
|
resourceId: z.string().uuid()
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define Zod schema for request body validation
|
// Define Zod schema for request body validation
|
||||||
const updateResourceBodySchema = z.object({
|
const updateResourceBodySchema = z.object({
|
||||||
name: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255).optional(),
|
||||||
subdomain: z.string().min(1).max(255).optional(),
|
subdomain: z.string().min(1).max(255).optional(),
|
||||||
}).refine(data => Object.keys(data).length > 0, {
|
}).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> {
|
export async function updateResource(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
// Validate request parameters
|
// Validate request parameters
|
||||||
const parsedParams = updateResourceParamsSchema.safeParse(req.params);
|
const parsedParams = updateResourceParamsSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import HttpCode from '@server/types/HttpCode';
|
||||||
import createHttpError from 'http-errors';
|
import createHttpError from 'http-errors';
|
||||||
import fetch from 'node-fetch';
|
import fetch from 'node-fetch';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const API_BASE_URL = "http://localhost:3000";
|
const API_BASE_URL = "http://localhost:3000";
|
||||||
|
|
||||||
|
@ -16,90 +17,88 @@ const createSiteParamsSchema = z.object({
|
||||||
|
|
||||||
// Define Zod schema for request body validation
|
// Define Zod schema for request body validation
|
||||||
const createSiteSchema = z.object({
|
const createSiteSchema = z.object({
|
||||||
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(),
|
||||||
pubKey: z.string().optional(),
|
pubKey: z.string().optional(),
|
||||||
subnet: z.string().optional(),
|
subnet: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function createSite(req: Request, res: Response, next: NextFunction): Promise<any> {
|
export async function createSite(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
// Validate request body
|
// Validate request body
|
||||||
const parsedBody = createSiteSchema.safeParse(req.body);
|
const parsedBody = createSiteSchema.safeParse(req.body);
|
||||||
if (!parsedBody.success) {
|
if (!parsedBody.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedBody.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 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) {
|
async function addPeer(peer: string) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/peer`, {
|
const response = await fetch(`${API_BASE_URL}/peer`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
body: JSON.stringify(peer),
|
body: JSON.stringify(peer),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,80 +7,80 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
|
|
||||||
const API_BASE_URL = "http://localhost:3000";
|
const API_BASE_URL = "http://localhost:3000";
|
||||||
|
|
||||||
// Define Zod schema for request parameters validation
|
// Define Zod schema for request parameters validation
|
||||||
const deleteSiteSchema = z.object({
|
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> {
|
export async function deleteSite(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
// Validate request parameters
|
// Validate request parameters
|
||||||
const parsedParams = deleteSiteSchema.safeParse(req.params);
|
const parsedParams = deleteSiteSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 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) {
|
async function removePeer(publicKey: string) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${API_BASE_URL}/peer?public_key=${encodeURIComponent(publicKey)}`, {
|
const response = await fetch(`${API_BASE_URL}/peer?public_key=${encodeURIComponent(publicKey)}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,58 +7,58 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
// Define Zod schema for request parameters validation
|
// Define Zod schema for request parameters validation
|
||||||
const getSiteSchema = z.object({
|
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> {
|
export async function getSite(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
// Validate request parameters
|
// Validate request parameters
|
||||||
const parsedParams = getSiteSchema.safeParse(req.params);
|
const parsedParams = getSiteSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,101 +7,101 @@ import HttpCode from '@server/types/HttpCode';
|
||||||
import createHttpError from 'http-errors';
|
import createHttpError from 'http-errors';
|
||||||
import { sql, eq, and, or, inArray } from 'drizzle-orm';
|
import { sql, eq, and, or, inArray } from 'drizzle-orm';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const listSitesParamsSchema = z.object({
|
const listSitesParamsSchema = z.object({
|
||||||
orgId: z.string().optional().transform(Number).pipe(z.number().int().positive()),
|
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)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function listSites(req: Request, res: Response, next: NextFunction): Promise<any> {
|
export async function listSites(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedQuery = listSitesSchema.safeParse(req.query);
|
const parsedQuery = listSitesSchema.safeParse(req.query);
|
||||||
if (!parsedQuery.success) {
|
if (!parsedQuery.success) {
|
||||||
return next(createHttpError(HttpCode.BAD_REQUEST, parsedQuery.error.errors.map(e => e.message).join(', ')));
|
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;
|
|
||||||
|
|
||||||
// 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 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -7,83 +7,83 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
// Define Zod schema for request parameters validation
|
// Define Zod schema for request parameters validation
|
||||||
const updateSiteParamsSchema = z.object({
|
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
|
// Define Zod schema for request body validation
|
||||||
const updateSiteBodySchema = z.object({
|
const updateSiteBodySchema = z.object({
|
||||||
name: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255).optional(),
|
||||||
subdomain: z.string().min(1).max(255).optional(),
|
subdomain: z.string().min(1).max(255).optional(),
|
||||||
pubKey: z.string().optional(),
|
pubKey: z.string().optional(),
|
||||||
subnet: z.string().optional(),
|
subnet: z.string().optional(),
|
||||||
exitNode: z.number().int().positive().optional(),
|
exitNode: z.number().int().positive().optional(),
|
||||||
megabytesIn: z.number().int().nonnegative().optional(),
|
megabytesIn: z.number().int().nonnegative().optional(),
|
||||||
megabytesOut: z.number().int().nonnegative().optional(),
|
megabytesOut: z.number().int().nonnegative().optional(),
|
||||||
}).refine(data => Object.keys(data).length > 0, {
|
}).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> {
|
export async function updateSite(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
// Validate request parameters
|
// Validate request parameters
|
||||||
const parsedParams = updateSiteParamsSchema.safeParse(req.params);
|
const parsedParams = updateSiteParamsSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,66 +6,66 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const createTargetParamsSchema = z.object({
|
const createTargetParamsSchema = z.object({
|
||||||
resourceId: z.string().uuid(),
|
resourceId: z.string().uuid(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const createTargetSchema = z.object({
|
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),
|
||||||
protocol: z.string().optional(),
|
protocol: z.string().optional(),
|
||||||
enabled: z.boolean().default(true),
|
enabled: z.boolean().default(true),
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function createTarget(req: Request, res: Response, next: NextFunction): Promise<any> {
|
export async function createTarget(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedBody = createTargetSchema.safeParse(req.body);
|
const parsedBody = createTargetSchema.safeParse(req.body);
|
||||||
if (!parsedBody.success) {
|
if (!parsedBody.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedBody.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,54 +7,54 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const deleteTargetSchema = z.object({
|
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> {
|
export async function deleteTarget(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedParams = deleteTargetSchema.safeParse(req.params);
|
const parsedParams = deleteTargetSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 res.status(HttpCode.OK).send(
|
|
||||||
response(res, {
|
|
||||||
data: null,
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "Target deleted successfully",
|
|
||||||
status: HttpCode.OK,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,55 +7,55 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const getTargetSchema = z.object({
|
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> {
|
export async function getTarget(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedParams = getTargetSchema.safeParse(req.params);
|
const parsedParams = getTargetSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,89 +7,89 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const listTargetsParamsSchema = z.object({
|
const listTargetsParamsSchema = z.object({
|
||||||
resourceId: z.string().optional()
|
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)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function listTargets(req: Request, res: Response, next: NextFunction): Promise<any> {
|
export async function listTargets(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedQuery = listTargetsSchema.safeParse(req.query);
|
const parsedQuery = listTargetsSchema.safeParse(req.query);
|
||||||
if (!parsedQuery.success) {
|
if (!parsedQuery.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedQuery.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
console.log(error);
|
|
||||||
|
|
||||||
return next(createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "sadfdf"));
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -7,76 +7,76 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const updateTargetParamsSchema = z.object({
|
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({
|
const updateTargetBodySchema = z.object({
|
||||||
ip: z.string().ip().optional(),
|
ip: z.string().ip().optional(),
|
||||||
method: z.string().min(1).max(10).optional(),
|
method: z.string().min(1).max(10).optional(),
|
||||||
port: z.number().int().min(1).max(65535).optional(),
|
port: z.number().int().min(1).max(65535).optional(),
|
||||||
protocol: z.string().optional(),
|
protocol: z.string().optional(),
|
||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
}).refine(data => Object.keys(data).length > 0, {
|
}).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> {
|
export async function updateTarget(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedParams = updateTargetParamsSchema.safeParse(req.params);
|
const parsedParams = updateTargetParamsSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,54 +7,54 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const deleteUserSchema = z.object({
|
const deleteUserSchema = z.object({
|
||||||
userId: z.string().uuid()
|
userId: z.string().uuid()
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function deleteUser(req: Request, res: Response, next: NextFunction): Promise<any> {
|
export async function deleteUser(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedParams = deleteUserSchema.safeParse(req.params);
|
const parsedParams = deleteUserSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 res.status(HttpCode.OK).send(
|
|
||||||
response(res, {
|
|
||||||
data: null,
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "User deleted successfully",
|
|
||||||
status: HttpCode.OK,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,58 +7,58 @@ 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';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const getUserSchema = z.object({
|
const getUserSchema = z.object({
|
||||||
userId: z.string().uuid()
|
userId: z.string().uuid()
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function getUser(req: Request, res: Response, next: NextFunction): Promise<any> {
|
export async function getUser(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedParams = getUserSchema.safeParse(req.params);
|
const parsedParams = getUserSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedParams.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 res.status(HttpCode.OK).send(
|
|
||||||
response(res, {
|
|
||||||
data: userWithoutPassword,
|
|
||||||
success: true,
|
|
||||||
error: false,
|
|
||||||
message: "User retrieved successfully",
|
|
||||||
status: HttpCode.OK,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
next(error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,62 +7,62 @@ import HttpCode from '@server/types/HttpCode';
|
||||||
import createHttpError from 'http-errors';
|
import createHttpError from 'http-errors';
|
||||||
import { sql } from 'drizzle-orm';
|
import { sql } from 'drizzle-orm';
|
||||||
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
import { ActionsEnum, checkUserActionPermission } from '@server/auth/actions';
|
||||||
|
import logger from '@server/logger';
|
||||||
|
|
||||||
const listUsersSchema = z.object({
|
const listUsersSchema = 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)),
|
||||||
});
|
});
|
||||||
|
|
||||||
export async function listUsers(req: Request, res: Response, next: NextFunction): Promise<any> {
|
export async function listUsers(req: Request, res: Response, next: NextFunction): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const parsedQuery = listUsersSchema.safeParse(req.query);
|
const parsedQuery = listUsersSchema.safeParse(req.query);
|
||||||
if (!parsedQuery.success) {
|
if (!parsedQuery.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
parsedQuery.error.errors.map(e => e.message).join(', ')
|
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;
|
|
||||||
|
|
||||||
// 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 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -55,7 +55,7 @@ export default function LoginForm() {
|
||||||
.catch((e) => {
|
.catch((e) => {
|
||||||
setError(
|
setError(
|
||||||
e.response?.data?.message ||
|
e.response?.data?.message ||
|
||||||
"An error occurred while logging in",
|
"An error occurred while logging in",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
@tailwind base;
|
@tailwind base;
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
:root {
|
:root {
|
||||||
--background: 37 100% 100%;
|
--background: 37 100% 100%;
|
||||||
|
@ -24,6 +25,7 @@
|
||||||
--ring: 37 8% 51%;
|
--ring: 37 8% 51%;
|
||||||
--radius: 0rem;
|
--radius: 0rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dark {
|
.dark {
|
||||||
--background: 37 50% 10%;
|
--background: 37 50% 10%;
|
||||||
--foreground: 37 5% 100%;
|
--foreground: 37 5% 100%;
|
||||||
|
@ -47,10 +49,12 @@
|
||||||
--radius: 0rem;
|
--radius: 0rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
* {
|
* {
|
||||||
@apply border-border;
|
@apply border-border;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,55 +4,55 @@ import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const alertVariants = cva(
|
const alertVariants = cva(
|
||||||
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
"relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-background text-foreground",
|
default: "bg-background text-foreground",
|
||||||
destructive:
|
destructive:
|
||||||
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const Alert = React.forwardRef<
|
const Alert = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||||
>(({ className, variant, ...props }, ref) => (
|
>(({ className, variant, ...props }, ref) => (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
role="alert"
|
role="alert"
|
||||||
className={cn(alertVariants({ variant }), className)}
|
className={cn(alertVariants({ variant }), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
Alert.displayName = "Alert"
|
Alert.displayName = "Alert"
|
||||||
|
|
||||||
const AlertTitle = React.forwardRef<
|
const AlertTitle = React.forwardRef<
|
||||||
HTMLParagraphElement,
|
HTMLParagraphElement,
|
||||||
React.HTMLAttributes<HTMLHeadingElement>
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<h5
|
<h5
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
AlertTitle.displayName = "AlertTitle"
|
AlertTitle.displayName = "AlertTitle"
|
||||||
|
|
||||||
const AlertDescription = React.forwardRef<
|
const AlertDescription = React.forwardRef<
|
||||||
HTMLParagraphElement,
|
HTMLParagraphElement,
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
AlertDescription.displayName = "AlertDescription"
|
AlertDescription.displayName = "AlertDescription"
|
||||||
|
|
||||||
|
|
|
@ -5,51 +5,51 @@ import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
destructive:
|
destructive:
|
||||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||||
outline:
|
outline:
|
||||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||||
secondary:
|
secondary:
|
||||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-10 px-4 py-2",
|
default: "h-10 px-4 py-2",
|
||||||
sm: "h-9 rounded-md px-3",
|
sm: "h-9 rounded-md px-3",
|
||||||
lg: "h-11 rounded-md px-8",
|
lg: "h-11 rounded-md px-8",
|
||||||
icon: "h-10 w-10",
|
icon: "h-10 w-10",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
variant: "default",
|
variant: "default",
|
||||||
size: "default",
|
size: "default",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
export interface ButtonProps
|
export interface ButtonProps
|
||||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
VariantProps<typeof buttonVariants> {
|
VariantProps<typeof buttonVariants> {
|
||||||
asChild?: boolean
|
asChild?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
const Comp = asChild ? Slot : "button"
|
const Comp = asChild ? Slot : "button"
|
||||||
return (
|
return (
|
||||||
<Comp
|
<Comp
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Button.displayName = "Button"
|
Button.displayName = "Button"
|
||||||
|
|
||||||
|
|
|
@ -3,76 +3,76 @@ import * as React from "react"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const Card = React.forwardRef<
|
const Card = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
"rounded-lg border bg-card text-card-foreground shadow-sm",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
Card.displayName = "Card"
|
Card.displayName = "Card"
|
||||||
|
|
||||||
const CardHeader = React.forwardRef<
|
const CardHeader = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
CardHeader.displayName = "CardHeader"
|
CardHeader.displayName = "CardHeader"
|
||||||
|
|
||||||
const CardTitle = React.forwardRef<
|
const CardTitle = React.forwardRef<
|
||||||
HTMLParagraphElement,
|
HTMLParagraphElement,
|
||||||
React.HTMLAttributes<HTMLHeadingElement>
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<h3
|
<h3
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-2xl font-semibold leading-none tracking-tight",
|
"text-2xl font-semibold leading-none tracking-tight",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
CardTitle.displayName = "CardTitle"
|
CardTitle.displayName = "CardTitle"
|
||||||
|
|
||||||
const CardDescription = React.forwardRef<
|
const CardDescription = React.forwardRef<
|
||||||
HTMLParagraphElement,
|
HTMLParagraphElement,
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<p
|
<p
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("text-sm text-muted-foreground", className)}
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
CardDescription.displayName = "CardDescription"
|
CardDescription.displayName = "CardDescription"
|
||||||
|
|
||||||
const CardContent = React.forwardRef<
|
const CardContent = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||||
))
|
))
|
||||||
CardContent.displayName = "CardContent"
|
CardContent.displayName = "CardContent"
|
||||||
|
|
||||||
const CardFooter = React.forwardRef<
|
const CardFooter = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("flex items-center p-6 pt-0", className)}
|
className={cn("flex items-center p-6 pt-0", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
CardFooter.displayName = "CardFooter"
|
CardFooter.displayName = "CardFooter"
|
||||||
|
|
||||||
|
|
|
@ -4,12 +4,12 @@ import * as React from "react"
|
||||||
import * as LabelPrimitive from "@radix-ui/react-label"
|
import * as LabelPrimitive from "@radix-ui/react-label"
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
ControllerProps,
|
ControllerProps,
|
||||||
FieldPath,
|
FieldPath,
|
||||||
FieldValues,
|
FieldValues,
|
||||||
FormProvider,
|
FormProvider,
|
||||||
useFormContext,
|
useFormContext,
|
||||||
} from "react-hook-form"
|
} from "react-hook-form"
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
@ -18,161 +18,161 @@ import { Label } from "@/components/ui/label"
|
||||||
const Form = FormProvider
|
const Form = FormProvider
|
||||||
|
|
||||||
type FormFieldContextValue<
|
type FormFieldContextValue<
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
> = {
|
> = {
|
||||||
name: TName
|
name: TName
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
||||||
{} as FormFieldContextValue
|
{} as FormFieldContextValue
|
||||||
)
|
)
|
||||||
|
|
||||||
const FormField = <
|
const FormField = <
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
TFieldValues extends FieldValues = FieldValues,
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
|
||||||
>({
|
>({
|
||||||
...props
|
...props
|
||||||
}: ControllerProps<TFieldValues, TName>) => {
|
}: ControllerProps<TFieldValues, TName>) => {
|
||||||
return (
|
return (
|
||||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
<FormFieldContext.Provider value={{ name: props.name }}>
|
||||||
<Controller {...props} />
|
<Controller {...props} />
|
||||||
</FormFieldContext.Provider>
|
</FormFieldContext.Provider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const useFormField = () => {
|
const useFormField = () => {
|
||||||
const fieldContext = React.useContext(FormFieldContext)
|
const fieldContext = React.useContext(FormFieldContext)
|
||||||
const itemContext = React.useContext(FormItemContext)
|
const itemContext = React.useContext(FormItemContext)
|
||||||
const { getFieldState, formState } = useFormContext()
|
const { getFieldState, formState } = useFormContext()
|
||||||
|
|
||||||
const fieldState = getFieldState(fieldContext.name, formState)
|
const fieldState = getFieldState(fieldContext.name, formState)
|
||||||
|
|
||||||
if (!fieldContext) {
|
if (!fieldContext) {
|
||||||
throw new Error("useFormField should be used within <FormField>")
|
throw new Error("useFormField should be used within <FormField>")
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = itemContext
|
const { id } = itemContext
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name: fieldContext.name,
|
name: fieldContext.name,
|
||||||
formItemId: `${id}-form-item`,
|
formItemId: `${id}-form-item`,
|
||||||
formDescriptionId: `${id}-form-item-description`,
|
formDescriptionId: `${id}-form-item-description`,
|
||||||
formMessageId: `${id}-form-item-message`,
|
formMessageId: `${id}-form-item-message`,
|
||||||
...fieldState,
|
...fieldState,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type FormItemContextValue = {
|
type FormItemContextValue = {
|
||||||
id: string
|
id: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
const FormItemContext = React.createContext<FormItemContextValue>(
|
||||||
{} as FormItemContextValue
|
{} as FormItemContextValue
|
||||||
)
|
)
|
||||||
|
|
||||||
const FormItem = React.forwardRef<
|
const FormItem = React.forwardRef<
|
||||||
HTMLDivElement,
|
HTMLDivElement,
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
>(({ className, ...props }, ref) => {
|
>(({ className, ...props }, ref) => {
|
||||||
const id = React.useId()
|
const id = React.useId()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormItemContext.Provider value={{ id }}>
|
<FormItemContext.Provider value={{ id }}>
|
||||||
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
<div ref={ref} className={cn("space-y-2", className)} {...props} />
|
||||||
</FormItemContext.Provider>
|
</FormItemContext.Provider>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
FormItem.displayName = "FormItem"
|
FormItem.displayName = "FormItem"
|
||||||
|
|
||||||
const FormLabel = React.forwardRef<
|
const FormLabel = React.forwardRef<
|
||||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
||||||
>(({ className, ...props }, ref) => {
|
>(({ className, ...props }, ref) => {
|
||||||
const { error, formItemId } = useFormField()
|
const { error, formItemId } = useFormField()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Label
|
<Label
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(error && "text-destructive", className)}
|
className={cn(error && "text-destructive", className)}
|
||||||
htmlFor={formItemId}
|
htmlFor={formItemId}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
FormLabel.displayName = "FormLabel"
|
FormLabel.displayName = "FormLabel"
|
||||||
|
|
||||||
const FormControl = React.forwardRef<
|
const FormControl = React.forwardRef<
|
||||||
React.ElementRef<typeof Slot>,
|
React.ElementRef<typeof Slot>,
|
||||||
React.ComponentPropsWithoutRef<typeof Slot>
|
React.ComponentPropsWithoutRef<typeof Slot>
|
||||||
>(({ ...props }, ref) => {
|
>(({ ...props }, ref) => {
|
||||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Slot
|
<Slot
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={formItemId}
|
id={formItemId}
|
||||||
aria-describedby={
|
aria-describedby={
|
||||||
!error
|
!error
|
||||||
? `${formDescriptionId}`
|
? `${formDescriptionId}`
|
||||||
: `${formDescriptionId} ${formMessageId}`
|
: `${formDescriptionId} ${formMessageId}`
|
||||||
}
|
}
|
||||||
aria-invalid={!!error}
|
aria-invalid={!!error}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
FormControl.displayName = "FormControl"
|
FormControl.displayName = "FormControl"
|
||||||
|
|
||||||
const FormDescription = React.forwardRef<
|
const FormDescription = React.forwardRef<
|
||||||
HTMLParagraphElement,
|
HTMLParagraphElement,
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
>(({ className, ...props }, ref) => {
|
>(({ className, ...props }, ref) => {
|
||||||
const { formDescriptionId } = useFormField()
|
const { formDescriptionId } = useFormField()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p
|
<p
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={formDescriptionId}
|
id={formDescriptionId}
|
||||||
className={cn("text-sm text-muted-foreground", className)}
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
FormDescription.displayName = "FormDescription"
|
FormDescription.displayName = "FormDescription"
|
||||||
|
|
||||||
const FormMessage = React.forwardRef<
|
const FormMessage = React.forwardRef<
|
||||||
HTMLParagraphElement,
|
HTMLParagraphElement,
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
>(({ className, children, ...props }, ref) => {
|
>(({ className, children, ...props }, ref) => {
|
||||||
const { error, formMessageId } = useFormField()
|
const { error, formMessageId } = useFormField()
|
||||||
const body = error ? String(error?.message) : children
|
const body = error ? String(error?.message) : children
|
||||||
|
|
||||||
if (!body) {
|
if (!body) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p
|
<p
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={formMessageId}
|
id={formMessageId}
|
||||||
className={cn("text-sm font-medium text-destructive", className)}
|
className={cn("text-sm font-medium text-destructive", className)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{body}
|
{body}
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
FormMessage.displayName = "FormMessage"
|
FormMessage.displayName = "FormMessage"
|
||||||
|
|
||||||
export {
|
export {
|
||||||
useFormField,
|
useFormField,
|
||||||
Form,
|
Form,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormDescription,
|
FormDescription,
|
||||||
FormMessage,
|
FormMessage,
|
||||||
FormField,
|
FormField,
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,19 +5,19 @@ import { cn } from "@/lib/utils"
|
||||||
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
export type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
|
||||||
|
|
||||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
({ className, type, ...props }, ref) => {
|
({ className, type, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<input
|
<input
|
||||||
type={type}
|
type={type}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Input.displayName = "Input"
|
Input.displayName = "Input"
|
||||||
|
|
||||||
|
|
|
@ -7,19 +7,19 @@ import { cva, type VariantProps } from "class-variance-authority"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const labelVariants = cva(
|
const labelVariants = cva(
|
||||||
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Label = React.forwardRef<
|
const Label = React.forwardRef<
|
||||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
React.ElementRef<typeof LabelPrimitive.Root>,
|
||||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
|
||||||
VariantProps<typeof labelVariants>
|
VariantProps<typeof labelVariants>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
<LabelPrimitive.Root
|
<LabelPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(labelVariants(), className)}
|
className={cn(labelVariants(), className)}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
))
|
))
|
||||||
Label.displayName = LabelPrimitive.Root.displayName
|
Label.displayName = LabelPrimitive.Root.displayName
|
||||||
|
|
||||||
|
|
|
@ -2,5 +2,5 @@ import { clsx, type ClassValue } from "clsx"
|
||||||
import { twMerge } from "tailwind-merge"
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs))
|
return twMerge(clsx(inputs))
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,55 +8,55 @@ const config: Config = {
|
||||||
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
|
||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
background: 'hsl(var(--background))',
|
background: 'hsl(var(--background))',
|
||||||
foreground: 'hsl(var(--foreground))',
|
foreground: 'hsl(var(--foreground))',
|
||||||
card: {
|
card: {
|
||||||
DEFAULT: 'hsl(var(--card))',
|
DEFAULT: 'hsl(var(--card))',
|
||||||
foreground: 'hsl(var(--card-foreground))'
|
foreground: 'hsl(var(--card-foreground))'
|
||||||
},
|
},
|
||||||
popover: {
|
popover: {
|
||||||
DEFAULT: 'hsl(var(--popover))',
|
DEFAULT: 'hsl(var(--popover))',
|
||||||
foreground: 'hsl(var(--popover-foreground))'
|
foreground: 'hsl(var(--popover-foreground))'
|
||||||
},
|
},
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: 'hsl(var(--primary))',
|
DEFAULT: 'hsl(var(--primary))',
|
||||||
foreground: 'hsl(var(--primary-foreground))'
|
foreground: 'hsl(var(--primary-foreground))'
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
DEFAULT: 'hsl(var(--secondary))',
|
DEFAULT: 'hsl(var(--secondary))',
|
||||||
foreground: 'hsl(var(--secondary-foreground))'
|
foreground: 'hsl(var(--secondary-foreground))'
|
||||||
},
|
},
|
||||||
muted: {
|
muted: {
|
||||||
DEFAULT: 'hsl(var(--muted))',
|
DEFAULT: 'hsl(var(--muted))',
|
||||||
foreground: 'hsl(var(--muted-foreground))'
|
foreground: 'hsl(var(--muted-foreground))'
|
||||||
},
|
},
|
||||||
accent: {
|
accent: {
|
||||||
DEFAULT: 'hsl(var(--accent))',
|
DEFAULT: 'hsl(var(--accent))',
|
||||||
foreground: 'hsl(var(--accent-foreground))'
|
foreground: 'hsl(var(--accent-foreground))'
|
||||||
},
|
},
|
||||||
destructive: {
|
destructive: {
|
||||||
DEFAULT: 'hsl(var(--destructive))',
|
DEFAULT: 'hsl(var(--destructive))',
|
||||||
foreground: 'hsl(var(--destructive-foreground))'
|
foreground: 'hsl(var(--destructive-foreground))'
|
||||||
},
|
},
|
||||||
border: 'hsl(var(--border))',
|
border: 'hsl(var(--border))',
|
||||||
input: 'hsl(var(--input))',
|
input: 'hsl(var(--input))',
|
||||||
ring: 'hsl(var(--ring))',
|
ring: 'hsl(var(--ring))',
|
||||||
chart: {
|
chart: {
|
||||||
'1': 'hsl(var(--chart-1))',
|
'1': 'hsl(var(--chart-1))',
|
||||||
'2': 'hsl(var(--chart-2))',
|
'2': 'hsl(var(--chart-2))',
|
||||||
'3': 'hsl(var(--chart-3))',
|
'3': 'hsl(var(--chart-3))',
|
||||||
'4': 'hsl(var(--chart-4))',
|
'4': 'hsl(var(--chart-4))',
|
||||||
'5': 'hsl(var(--chart-5))'
|
'5': 'hsl(var(--chart-5))'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
lg: 'var(--radius)',
|
lg: 'var(--radius)',
|
||||||
md: 'calc(var(--radius) - 2px)',
|
md: 'calc(var(--radius) - 2px)',
|
||||||
sm: 'calc(var(--radius) - 4px)'
|
sm: 'calc(var(--radius) - 4px)'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate")],
|
plugins: [require("tailwindcss-animate")],
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
@ -14,9 +18,15 @@
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"baseUrl": "src",
|
"baseUrl": "src",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@server/*": ["../server/*"],
|
"@server/*": [
|
||||||
"@app/*": ["*"],
|
"../server/*"
|
||||||
"@/*": ["./*"]
|
],
|
||||||
|
"@app/*": [
|
||||||
|
"*"
|
||||||
|
],
|
||||||
|
"@/*": [
|
||||||
|
"./*"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
@ -24,6 +34,13 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
"exclude": ["node_modules"]
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue