Format files and fix http response

This commit is contained in:
Owen Schwartz 2024-10-06 18:05:20 -04:00
parent 797f72e1d0
commit 8213036729
No known key found for this signature in database
GPG key ID: 8271FDFFD9E0CCBD
49 changed files with 2428 additions and 2404 deletions

View file

@ -1,3 +1,6 @@
{ {
"extends": ["next/core-web-vitals", "next/typescript"] "extends": [
"next/core-web-vitals",
"next/typescript"
]
} }

View file

@ -1,9 +1,9 @@
{ {
"version": "1", "version": "1",
"name": "Pangolin", "name": "Pangolin",
"type": "collection", "type": "collection",
"ignore": [ "ignore": [
"node_modules", "node_modules",
".git" ".git"
] ]
} }

View file

@ -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');
}
} }

View file

@ -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');
}
} }

View file

@ -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"
] ]

View file

@ -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")

View file

@ -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'));
} }
} }

View file

@ -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'));
}); });
} }

View file

@ -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'));
}
} }

View file

@ -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'));
}
} }

View file

@ -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'));
}); });
} }

View file

@ -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);
}
} }
}; };

View file

@ -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`;
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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;
}
} }

View file

@ -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;
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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"));
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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);
}
} }

View file

@ -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",
); );
}); });
} }

View file

@ -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;
} }

View file

@ -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"

View file

@ -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"

View file

@ -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"

View file

@ -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,
} }

View file

@ -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"

View file

@ -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

View file

@ -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))
} }

View file

@ -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")],
}; };

View file

@ -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"
]
} }