refactor(lst): refactored to be back to npm from bun

This commit is contained in:
2025-02-27 08:36:05 -06:00
parent 4c73fb0317
commit 379f7b836d
75 changed files with 15693 additions and 2705 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
dist
frontend/dist
server/dist
# ---> Node # ---> Node
bun.lock bun.lock
.nx .nx

View File

@@ -6,11 +6,11 @@
{"type": "docs", "section": "📚 Documentation"}, {"type": "docs", "section": "📚 Documentation"},
{"type": "style", "hidden": true}, {"type": "style", "hidden": true},
{"type": "refactor", "section": "🛠️ Code Refactor"}, {"type": "refactor", "section": "🛠️ Code Refactor"},
{"type": "perf", "hidden": false, "section": "🚀 Code Refactor"}, {"type": "perf", "hidden": false, "section": "🚀 Performance"},
{"type": "test", "section": "📝 Testing Code"}, {"type": "test", "section": "📝 Testing Code"},
{"type": "ci", "section": "📈 Project changes"} {"type": "ci", "section": "📈 Project changes"}
], ],
"commitUrlFormat": "https://git.tuffraid.net/cowch/lstV2/commits{{hash}}", "commitUrlFormat": "https://git.tuffraid.net/cowch/lstV2/commits{{hash}}",
"compareUrlFormat": "https://git.tuffraid.net/cowch/lstV2/compare/{{previousTag}}...{{currentTag}}", "compareUrlFormat": "https://git.tuffraid.net/cowch/lstV2/compare/{{previousTag}}...{{currentTag}}",
"header": "# All CHanges to LST can be found below.\n`" "header": "# All CHanges to LST can be found below.\n"
} }

View File

@@ -5,14 +5,14 @@ meta {
} }
post { post {
url: http://localhost:3000/api/auth/login url: http://localhost:4000/api/auth/login
body: json body: json
auth: none auth: none
} }
body:json { body:json {
{ {
"username": "adm_matthes", "username": "matthes01",
"password": "Nova0511!" "password": "99Monsters200Scary!"
} }
} }

View File

@@ -5,8 +5,8 @@ meta {
} }
get { get {
url: http://localhost:3000/api/server/modules url: http://localhost:4000/api/server/modules
body: json body: none
auth: none auth: none
} }

View File

@@ -1,5 +1,9 @@
import {drizzle} from "drizzle-orm/postgres-js"; import {drizzle} from "drizzle-orm/postgres-js";
import postgres from "postgres"; import postgres from "postgres";
import dotenv from "dotenv";
dotenv.config();
const database = process.env.DATABASE_URL || ""; const database = process.env.DATABASE_URL || "";
const queryClient = postgres(database); const queryClient = postgres(database);

View File

@@ -2,6 +2,7 @@ CREATE TABLE "modules" (
"module_id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, "module_id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL, "name" text NOT NULL,
"active" boolean DEFAULT false, "active" boolean DEFAULT false,
"roles" text DEFAULT '["view", "systemAdmin"]' NOT NULL,
"add_User" text DEFAULT 'LST_System' NOT NULL, "add_User" text DEFAULT 'LST_System' NOT NULL,
"add_Date" timestamp DEFAULT now(), "add_Date" timestamp DEFAULT now(),
"upd_User" text DEFAULT 'LST_System' NOT NULL, "upd_User" text DEFAULT 'LST_System' NOT NULL,
@@ -21,7 +22,24 @@ CREATE TABLE "userRoles" (
"user_id" uuid NOT NULL, "user_id" uuid NOT NULL,
"role_id" uuid NOT NULL, "role_id" uuid NOT NULL,
"module_id" uuid NOT NULL, "module_id" uuid NOT NULL,
"roles" text NOT NULL, "role" text NOT NULL,
"add_User" text DEFAULT 'LST_System' NOT NULL,
"add_Date" timestamp DEFAULT now(),
"upd_User" text DEFAULT 'LST_System' NOT NULL,
"upd_date" timestamp DEFAULT now()
);
--> statement-breakpoint
CREATE TABLE "users" (
"user_id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"username" text NOT NULL,
"email" text NOT NULL,
"password" text NOT NULL,
"passwordToken" text,
"passwordTokenExpires" timestamp,
"active" boolean DEFAULT true NOT NULL,
"pingcode" numeric,
"role" text DEFAULT 'user' NOT NULL,
"lastLogin" timestamp DEFAULT now(),
"add_User" text DEFAULT 'LST_System' NOT NULL, "add_User" text DEFAULT 'LST_System' NOT NULL,
"add_Date" timestamp DEFAULT now(), "add_Date" timestamp DEFAULT now(),
"upd_User" text DEFAULT 'LST_System' NOT NULL, "upd_User" text DEFAULT 'LST_System' NOT NULL,
@@ -33,4 +51,5 @@ ALTER TABLE "userRoles" ADD CONSTRAINT "userRoles_role_id_roles_role_id_fk" FORE
ALTER TABLE "userRoles" ADD CONSTRAINT "userRoles_module_id_modules_module_id_fk" FOREIGN KEY ("module_id") REFERENCES "public"."modules"("module_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint ALTER TABLE "userRoles" ADD CONSTRAINT "userRoles_module_id_modules_module_id_fk" FOREIGN KEY ("module_id") REFERENCES "public"."modules"("module_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE UNIQUE INDEX "module_name" ON "modules" USING btree ("name");--> statement-breakpoint CREATE UNIQUE INDEX "module_name" ON "modules" USING btree ("name");--> statement-breakpoint
CREATE UNIQUE INDEX "role_name" ON "roles" USING btree ("name");--> statement-breakpoint CREATE UNIQUE INDEX "role_name" ON "roles" USING btree ("name");--> statement-breakpoint
CREATE UNIQUE INDEX "user_module_unique" ON "userRoles" USING btree ("user_id","module_id"); CREATE UNIQUE INDEX "user_module_unique" ON "userRoles" USING btree ("user_id","module_id");--> statement-breakpoint
CREATE UNIQUE INDEX "username" ON "users" USING btree ("username");

View File

@@ -1,6 +1,6 @@
{ {
"id": "eb681265-0d44-4a8a-acaf-840acc169228", "id": "467c98f1-3785-42b5-80ed-528eac5fcbe4",
"prevId": "313590a8-2068-45b5-96fc-cfa5d2b32b56", "prevId": "00000000-0000-0000-0000-000000000000",
"version": "7", "version": "7",
"dialect": "postgresql", "dialect": "postgresql",
"tables": { "tables": {

View File

@@ -0,0 +1,13 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1740534974452,
"tag": "0000_nebulous_bulldozer",
"breakpoints": true
}
]
}

View File

@@ -1,9 +1,13 @@
import {defineConfig} from "drizzle-kit"; import {defineConfig} from "drizzle-kit";
const database = process.env.DATABASE_URL || ""; import dotenv from "dotenv";
dotenv.config();
const database = process.env.DATABASE_URL! || "";
export default defineConfig({ export default defineConfig({
dialect: "postgresql", // 'mysql' | 'sqlite' | 'turso' dialect: "postgresql", // 'mysql' | 'sqlite' | 'turso'
schema: "./server/database/schema/", schema: "database/schema",
out: "./server/database/migrations", out: "database/migrations",
dbCredentials: { dbCredentials: {
url: database, url: database,
}, },

View File

@@ -5,7 +5,7 @@
"tsx": true, "tsx": true,
"tailwind": { "tailwind": {
"config": "", "config": "",
"css": "src/styles.css", "css": "src/style.css",
"baseColor": "neutral", "baseColor": "neutral",
"cssVariables": true, "cssVariables": true,
"prefix": "" "prefix": ""

5597
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,17 +1,17 @@
{ {
"name": "frontend", "name": "vitefix",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "bun --bun vite", "dev": "vite",
"build": "vite build", "build": "tsc -b && vite build",
"lint": "eslint .", "lint": "eslint .",
"preview": "vite preview" "preview": "vite preview",
"shad": "npx shadcn@canary add "
}, },
"dependencies": { "dependencies": {
"@antfu/ni": "^23.3.1", "@hookform/resolvers": "^4.1.2",
"@hookform/resolvers": "^4.1.0",
"@radix-ui/react-avatar": "^1.1.3", "@radix-ui/react-avatar": "^1.1.3",
"@radix-ui/react-checkbox": "^1.1.4", "@radix-ui/react-checkbox": "^1.1.4",
"@radix-ui/react-collapsible": "^1.1.3", "@radix-ui/react-collapsible": "^1.1.3",
@@ -21,42 +21,38 @@
"@radix-ui/react-separator": "^1.1.2", "@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.1.2", "@radix-ui/react-slot": "^1.1.2",
"@radix-ui/react-tooltip": "^1.1.8", "@radix-ui/react-tooltip": "^1.1.8",
"@tailwindcss/vite": "^4.0.6", "@tailwindcss/vite": "^4.0.9",
"@tanstack/react-query": "^5.66.5", "@tanstack/react-query": "^5.66.9",
"@tanstack/react-router": "^1.106.0", "@tanstack/react-router": "^1.111.11",
"@tanstack/zod-form-adapter": "^0.42.1",
"@types/react-grid-layout": "^1.3.5",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"lucide-react": "^0.475.0", "lucide-react": "^0.476.0",
"next-themes": "^0.4.4", "next-themes": "^0.4.4",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"react-grid-layout": "^1.5.0", "react-grid-layout": "^1.5.0",
"react-hook-form": "^7.54.2", "react-hook-form": "^7.54.2",
"shadcn": "^2.4.0-canary.6",
"sonner": "^2.0.1", "sonner": "^2.0.1",
"tailwind-merge": "^3.0.1", "tailwind-merge": "^3.0.2",
"tailwindcss": "^4.0.6", "tailwindcss": "^4.0.9",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"zod": "^3.24.2", "zod": "^3.24.2",
"zustand": "^5.0.3" "zustand": "^5.0.3"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.19.0", "@eslint/js": "^9.21.0",
"@tanstack/router-devtools": "^1.106.0", "@tanstack/router-devtools": "^1.106.0",
"@tanstack/router-plugin": "^1.106.0", "@tanstack/router-plugin": "^1.106.0",
"@types/node": "^22.13.4", "@types/react": "^19.0.10",
"@types/react": "^19.0.8", "@types/react-dom": "^19.0.4",
"@types/react-dom": "^19.0.3", "@vitejs/plugin-react-swc": "^3.8.0",
"@vitejs/plugin-react-swc": "^3.5.0", "eslint": "^9.21.0",
"eslint": "^9.19.0",
"eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.18", "eslint-plugin-react-refresh": "^0.4.19",
"globals": "^15.14.0", "globals": "^15.15.0",
"typescript": "~5.7.2", "typescript": "~5.7.2",
"typescript-eslint": "^8.22.0", "typescript-eslint": "^8.24.1",
"vite": "^6.1.0" "vite": "^6.2.0"
} }
} }

View File

@@ -1,36 +1,51 @@
import * as React from "react"; import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"; import * as AvatarPrimitive from "@radix-ui/react-avatar"
import {cn} from "../../lib/utils"; import { cn } from "@/lib/utils"
function Avatar({className, ...props}: React.ComponentProps<typeof AvatarPrimitive.Root>) { function Avatar({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
return ( return (
<AvatarPrimitive.Root <AvatarPrimitive.Root
data-slot="avatar" data-slot="avatar"
className={cn("relative flex size-8 shrink-0 overflow-hidden rounded-full", className)} className={cn(
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
className
)}
{...props} {...props}
/> />
); )
} }
function AvatarImage({className, ...props}: React.ComponentProps<typeof AvatarPrimitive.Image>) { function AvatarImage({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
return ( return (
<AvatarPrimitive.Image <AvatarPrimitive.Image
data-slot="avatar-image" data-slot="avatar-image"
className={cn("aspect-square size-full", className)} className={cn("aspect-square size-full", className)}
{...props} {...props}
/> />
); )
} }
function AvatarFallback({className, ...props}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) { function AvatarFallback({
className,
...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
return ( return (
<AvatarPrimitive.Fallback <AvatarPrimitive.Fallback
data-slot="avatar-fallback" data-slot="avatar-fallback"
className={cn("bg-muted flex size-full items-center justify-center rounded-full", className)} className={cn(
"bg-muted flex size-full items-center justify-center rounded-full",
className
)}
{...props} {...props}
/> />
); )
} }
export {Avatar, AvatarImage, AvatarFallback}; export { Avatar, AvatarImage, AvatarFallback }

View File

@@ -1,24 +1,28 @@
import * as React from "react"; import * as React from "react"
import {Slot} from "@radix-ui/react-slot"; import { Slot } from "@radix-ui/react-slot"
import {cva, type VariantProps} from "class-variance-authority"; 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 gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 focus-visible:ring-4 focus-visible:outline-1 aria-invalid:focus-visible:ring-0", "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{ {
variants: { variants: {
variant: { variant: {
default: "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90", default:
destructive: "bg-destructive text-destructive-foreground shadow-xs hover:bg-destructive/90", "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
outline: "border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground", destructive:
secondary: "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40",
outline:
"border border-input bg-background shadow-xs hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-xs 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-9 px-4 py-2 has-[>svg]:px-3", default: "h-9 px-4 py-2 has-[>svg]:px-3",
sm: "h-8 rounded-md px-3 has-[>svg]:px-2.5", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4", lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9", icon: "size-9",
}, },
@@ -28,7 +32,7 @@ const buttonVariants = cva(
size: "default", size: "default",
}, },
} }
); )
function Button({ function Button({
className, className,
@@ -38,11 +42,17 @@ function Button({
...props ...props
}: React.ComponentProps<"button"> & }: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & { VariantProps<typeof buttonVariants> & {
asChild?: boolean; asChild?: boolean
}) { }) {
const Comp = asChild ? Slot : "button"; const Comp = asChild ? Slot : "button"
return <Comp data-slot="button" className={cn(buttonVariants({variant, size, className}))} {...props} />; return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
} }
export {Button, buttonVariants}; export { Button, buttonVariants }

View File

@@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<"div">) {
<div <div
data-slot="card" data-slot="card"
className={cn( className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border shadow-sm", "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className className
)} )}
{...props} {...props}
@@ -19,7 +19,7 @@ function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-header" data-slot="card-header"
className={cn("flex flex-col gap-1.5 px-6 pt-6", className)} className={cn("flex flex-col gap-1.5 px-6", className)}
{...props} {...props}
/> />
) )
@@ -59,7 +59,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-footer" data-slot="card-footer"
className={cn("flex items-center px-6 pb-6", className)} className={cn("flex items-center px-6", className)}
{...props} {...props}
/> />
) )

View File

@@ -12,14 +12,14 @@ function Checkbox({
<CheckboxPrimitive.Root <CheckboxPrimitive.Root
data-slot="checkbox" data-slot="checkbox"
className={cn( className={cn(
"peer border-input data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50", "peer border-input data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className className
)} )}
{...props} {...props}
> >
<CheckboxPrimitive.Indicator <CheckboxPrimitive.Indicator
data-slot="checkbox-indicator" data-slot="checkbox-indicator"
className="flex items-center justify-center text-current" className="flex items-center justify-center text-current transition-none"
> >
<CheckIcon className="size-3.5" /> <CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator> </CheckboxPrimitive.Indicator>

View File

@@ -1,3 +1,5 @@
"use client"
import * as React from "react" import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu" import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react" import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
@@ -72,7 +74,7 @@ function DropdownMenuItem({
data-inset={inset} data-inset={inset}
data-variant={variant} data-variant={variant}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive-foreground data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/40 data-[variant=destructive]:focus:text-destructive-foreground data-[variant=destructive]:*:[svg]:!text-destructive-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className className
)} )}
{...props} {...props}
@@ -153,7 +155,7 @@ function DropdownMenuLabel({
data-slot="dropdown-menu-label" data-slot="dropdown-menu-label"
data-inset={inset} data-inset={inset}
className={cn( className={cn(
"px-2 py-1.5 text-sm font-semibold data-[inset]:pl-8", "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
className className
)} )}
{...props} {...props}

View File

@@ -11,7 +11,7 @@ function Label({
<LabelPrimitive.Root <LabelPrimitive.Root
data-slot="label" data-slot="label"
className={cn( className={cn(
"text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50", "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
className className
)} )}
{...props} {...props}

View File

@@ -1,9 +1,9 @@
"use client"; "use client"
import * as React from "react"; import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"; import * as SeparatorPrimitive from "@radix-ui/react-separator"
import {cn} from "../../lib/utils"; import { cn } from "@/lib/utils"
function Separator({ function Separator({
className, className,
@@ -22,7 +22,7 @@ function Separator({
)} )}
{...props} {...props}
/> />
); )
} }
export {Separator}; export { Separator }

View File

@@ -1,26 +1,35 @@
import * as React from "react"; import * as React from "react"
import * as SheetPrimitive from "@radix-ui/react-dialog"; import * as SheetPrimitive from "@radix-ui/react-dialog"
import {XIcon} from "lucide-react"; import { XIcon } from "lucide-react"
import {cn} from "../../lib/utils"; import { cn } from "@/lib/utils"
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) { function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
return <SheetPrimitive.Root data-slot="sheet" {...props} />; return <SheetPrimitive.Root data-slot="sheet" {...props} />
} }
function SheetTrigger({...props}: React.ComponentProps<typeof SheetPrimitive.Trigger>) { function SheetTrigger({
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />; ...props
}: React.ComponentProps<typeof SheetPrimitive.Trigger>) {
return <SheetPrimitive.Trigger data-slot="sheet-trigger" {...props} />
} }
function SheetClose({...props}: React.ComponentProps<typeof SheetPrimitive.Close>) { function SheetClose({
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />; ...props
}: React.ComponentProps<typeof SheetPrimitive.Close>) {
return <SheetPrimitive.Close data-slot="sheet-close" {...props} />
} }
function SheetPortal({...props}: React.ComponentProps<typeof SheetPrimitive.Portal>) { function SheetPortal({
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />; ...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} />
} }
function SheetOverlay({className, ...props}: React.ComponentProps<typeof SheetPrimitive.Overlay>) { function SheetOverlay({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Overlay>) {
return ( return (
<SheetPrimitive.Overlay <SheetPrimitive.Overlay
data-slot="sheet-overlay" data-slot="sheet-overlay"
@@ -30,7 +39,7 @@ function SheetOverlay({className, ...props}: React.ComponentProps<typeof SheetPr
)} )}
{...props} {...props}
/> />
); )
} }
function SheetContent({ function SheetContent({
@@ -39,7 +48,7 @@ function SheetContent({
side = "right", side = "right",
...props ...props
}: React.ComponentProps<typeof SheetPrimitive.Content> & { }: React.ComponentProps<typeof SheetPrimitive.Content> & {
side?: "top" | "right" | "bottom" | "left"; side?: "top" | "right" | "bottom" | "left"
}) { }) {
return ( return (
<SheetPortal> <SheetPortal>
@@ -67,35 +76,62 @@ function SheetContent({
</SheetPrimitive.Close> </SheetPrimitive.Close>
</SheetPrimitive.Content> </SheetPrimitive.Content>
</SheetPortal> </SheetPortal>
); )
} }
function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { function SheetHeader({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="sheet-header" className={cn("flex flex-col gap-1.5 p-4", className)} {...props} />; return (
<div
data-slot="sheet-header"
className={cn("flex flex-col gap-1.5 p-4", className)}
{...props}
/>
)
} }
function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { function SheetFooter({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="sheet-footer" className={cn("mt-auto flex flex-col gap-2 p-4", className)} {...props} />; return (
<div
data-slot="sheet-footer"
className={cn("mt-auto flex flex-col gap-2 p-4", className)}
{...props}
/>
)
} }
function SheetTitle({className, ...props}: React.ComponentProps<typeof SheetPrimitive.Title>) { function SheetTitle({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Title>) {
return ( return (
<SheetPrimitive.Title <SheetPrimitive.Title
data-slot="sheet-title" data-slot="sheet-title"
className={cn("text-foreground font-semibold tracking-tight", className)} className={cn("text-foreground font-semibold", className)}
{...props} {...props}
/> />
); )
} }
function SheetDescription({className, ...props}: React.ComponentProps<typeof SheetPrimitive.Description>) { function SheetDescription({
className,
...props
}: React.ComponentProps<typeof SheetPrimitive.Description>) {
return ( return (
<SheetPrimitive.Description <SheetPrimitive.Description
data-slot="sheet-description" data-slot="sheet-description"
className={cn("text-muted-foreground text-sm", className)} className={cn("text-muted-foreground text-sm", className)}
{...props} {...props}
/> />
); )
} }
export {Sheet, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription}; export {
Sheet,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
}

View File

@@ -1,96 +1,115 @@
import * as React from "react"; import * as React from "react"
import {Slot} from "@radix-ui/react-slot"; import { Slot } from "@radix-ui/react-slot"
import {VariantProps, cva} from "class-variance-authority"; import { VariantProps, cva } from "class-variance-authority"
import {PanelLeftIcon} from "lucide-react"; import { PanelLeftIcon } from "lucide-react"
import {useIsMobile} from "../../hooks/use-mobile"; import { useIsMobile } from "@/hooks/use-mobile"
import {cn} from "../../lib/utils"; import { cn } from "@/lib/utils"
import {Button} from "../../components/ui/button"; import { Button } from "@/components/ui/button"
import {Input} from "../../components/ui/input"; import { Input } from "@/components/ui/input"
import {Separator} from "../../components/ui/separator"; import { Separator } from "@/components/ui/separator"
import {Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle} from "../../components/ui/sheet"; import {
import {Skeleton} from "../../components/ui/skeleton"; Sheet,
import {Tooltip, TooltipContent, TooltipProvider, TooltipTrigger} from "../../components/ui/tooltip"; SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from "@/components/ui/sheet"
import { Skeleton } from "@/components/ui/skeleton"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"
const SIDEBAR_COOKIE_NAME = "sidebar_state"; const SIDEBAR_COOKIE_NAME = "sidebar_state"
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7; const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
const SIDEBAR_WIDTH = "16rem"; const SIDEBAR_WIDTH = "16rem"
const SIDEBAR_WIDTH_MOBILE = "18rem"; const SIDEBAR_WIDTH_MOBILE = "18rem"
const SIDEBAR_WIDTH_ICON = "3rem"; const SIDEBAR_WIDTH_ICON = "3rem"
const SIDEBAR_KEYBOARD_SHORTCUT = "b"; const SIDEBAR_KEYBOARD_SHORTCUT = "b"
type SidebarContext = { type SidebarContext = {
state: "expanded" | "collapsed"; state: "expanded" | "collapsed"
open: boolean; open: boolean
setOpen: (open: boolean) => void; setOpen: (open: boolean) => void
openMobile: boolean; openMobile: boolean
setOpenMobile: (open: boolean) => void; setOpenMobile: (open: boolean) => void
isMobile: boolean; isMobile: boolean
toggleSidebar: () => void; toggleSidebar: () => void
}; }
const SidebarContext = React.createContext<SidebarContext | null>(null); const SidebarContext = React.createContext<SidebarContext | null>(null)
function useSidebar() { function useSidebar() {
const context = React.useContext(SidebarContext); const context = React.useContext(SidebarContext)
if (!context) { if (!context) {
throw new Error("useSidebar must be used within a SidebarProvider."); throw new Error("useSidebar must be used within a SidebarProvider.")
} }
return context; return context
} }
const SidebarProvider = React.forwardRef< function SidebarProvider({
HTMLDivElement, defaultOpen = true,
React.ComponentProps<"div"> & { open: openProp,
defaultOpen?: boolean; onOpenChange: setOpenProp,
open?: boolean; className,
onOpenChange?: (open: boolean) => void; style,
} children,
>(({defaultOpen = true, open: openProp, onOpenChange: setOpenProp, className, style, children, ...props}, ref) => { ...props
const isMobile = useIsMobile(); }: React.ComponentProps<"div"> & {
const [openMobile, setOpenMobile] = React.useState(false); defaultOpen?: boolean
open?: boolean
onOpenChange?: (open: boolean) => void
}) {
const isMobile = useIsMobile()
const [openMobile, setOpenMobile] = React.useState(false)
// This is the internal state of the sidebar. // This is the internal state of the sidebar.
// We use openProp and setOpenProp for control from outside the component. // We use openProp and setOpenProp for control from outside the component.
const [_open, _setOpen] = React.useState(defaultOpen); const [_open, _setOpen] = React.useState(defaultOpen)
const open = openProp ?? _open; const open = openProp ?? _open
const setOpen = React.useCallback( const setOpen = React.useCallback(
(value: boolean | ((value: boolean) => boolean)) => { (value: boolean | ((value: boolean) => boolean)) => {
const openState = typeof value === "function" ? value(open) : value; const openState = typeof value === "function" ? value(open) : value
if (setOpenProp) { if (setOpenProp) {
setOpenProp(openState); setOpenProp(openState)
} else { } else {
_setOpen(openState); _setOpen(openState)
} }
// This sets the cookie to keep the sidebar state. // This sets the cookie to keep the sidebar state.
document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`; document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`
}, },
[setOpenProp, open] [setOpenProp, open]
); )
// Helper to toggle the sidebar. // Helper to toggle the sidebar.
const toggleSidebar = React.useCallback(() => { const toggleSidebar = React.useCallback(() => {
return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open); return isMobile ? setOpenMobile((open) => !open) : setOpen((open) => !open)
}, [isMobile, setOpen, setOpenMobile]); }, [isMobile, setOpen, setOpenMobile])
// Adds a keyboard shortcut to toggle the sidebar. // Adds a keyboard shortcut to toggle the sidebar.
React.useEffect(() => { React.useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => { const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) { if (
event.preventDefault(); event.key === SIDEBAR_KEYBOARD_SHORTCUT &&
toggleSidebar(); (event.metaKey || event.ctrlKey)
) {
event.preventDefault()
toggleSidebar()
}
} }
};
window.addEventListener("keydown", handleKeyDown); window.addEventListener("keydown", handleKeyDown)
return () => window.removeEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown)
}, [toggleSidebar]); }, [toggleSidebar])
// We add a state so that we can do data-state="expanded" or "collapsed". // We add a state so that we can do data-state="expanded" or "collapsed".
// This makes it easier to style the sidebar with Tailwind classes. // This makes it easier to style the sidebar with Tailwind classes.
const state = open ? "expanded" : "collapsed"; const state = open ? "expanded" : "collapsed"
const contextValue = React.useMemo<SidebarContext>( const contextValue = React.useMemo<SidebarContext>(
() => ({ () => ({
@@ -103,7 +122,7 @@ const SidebarProvider = React.forwardRef<
toggleSidebar, toggleSidebar,
}), }),
[state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar] [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
); )
return ( return (
<SidebarContext.Provider value={contextValue}> <SidebarContext.Provider value={contextValue}>
@@ -121,16 +140,14 @@ const SidebarProvider = React.forwardRef<
"group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full", "group/sidebar-wrapper has-data-[variant=inset]:bg-sidebar flex min-h-svh w-full",
className className
)} )}
ref={ref}
{...props} {...props}
> >
{children} {children}
</div> </div>
</TooltipProvider> </TooltipProvider>
</SidebarContext.Provider> </SidebarContext.Provider>
); )
}); }
SidebarProvider.displayName = "SidebarProvider";
function Sidebar({ function Sidebar({
side = "left", side = "left",
@@ -140,22 +157,25 @@ function Sidebar({
children, children,
...props ...props
}: React.ComponentProps<"div"> & { }: React.ComponentProps<"div"> & {
side?: "left" | "right"; side?: "left" | "right"
variant?: "sidebar" | "floating" | "inset"; variant?: "sidebar" | "floating" | "inset"
collapsible?: "offcanvas" | "icon" | "none"; collapsible?: "offcanvas" | "icon" | "none"
}) { }) {
const {isMobile, state, openMobile, setOpenMobile} = useSidebar(); const { isMobile, state, openMobile, setOpenMobile } = useSidebar()
if (collapsible === "none") { if (collapsible === "none") {
return ( return (
<div <div
data-slot="sidebar" data-slot="sidebar"
className={cn("bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col", className)} className={cn(
"bg-sidebar text-sidebar-foreground flex h-full w-(--sidebar-width) flex-col",
className
)}
{...props} {...props}
> >
{children} {children}
</div> </div>
); )
} }
if (isMobile) { if (isMobile) {
@@ -180,7 +200,7 @@ function Sidebar({
<div className="flex h-full w-full flex-col">{children}</div> <div className="flex h-full w-full flex-col">{children}</div>
</SheetContent> </SheetContent>
</Sheet> </Sheet>
); )
} }
return ( return (
@@ -225,11 +245,15 @@ function Sidebar({
</div> </div>
</div> </div>
</div> </div>
); )
} }
function SidebarTrigger({className, onClick, ...props}: React.ComponentProps<typeof Button>) { function SidebarTrigger({
const {toggleSidebar} = useSidebar(); className,
onClick,
...props
}: React.ComponentProps<typeof Button>) {
const { toggleSidebar } = useSidebar()
return ( return (
<Button <Button
@@ -239,19 +263,19 @@ function SidebarTrigger({className, onClick, ...props}: React.ComponentProps<typ
size="icon" size="icon"
className={cn("h-7 w-7", className)} className={cn("h-7 w-7", className)}
onClick={(event) => { onClick={(event) => {
onClick?.(event); onClick?.(event)
toggleSidebar(); toggleSidebar()
}} }}
{...props} {...props}
> >
<PanelLeftIcon /> <PanelLeftIcon />
<span className="sr-only">Toggle Sidebar</span> <span className="sr-only">Toggle Sidebar</span>
</Button> </Button>
); )
} }
function SidebarRail({ className, ...props }: React.ComponentProps<"button">) { function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
const {toggleSidebar} = useSidebar(); const { toggleSidebar } = useSidebar()
return ( return (
<button <button
@@ -272,7 +296,7 @@ function SidebarRail({className, ...props}: React.ComponentProps<"button">) {
)} )}
{...props} {...props}
/> />
); )
} }
function SidebarInset({ className, ...props }: React.ComponentProps<"main">) { function SidebarInset({ className, ...props }: React.ComponentProps<"main">) {
@@ -286,10 +310,13 @@ function SidebarInset({className, ...props}: React.ComponentProps<"main">) {
)} )}
{...props} {...props}
/> />
); )
} }
function SidebarInput({className, ...props}: React.ComponentProps<typeof Input>) { function SidebarInput({
className,
...props
}: React.ComponentProps<typeof Input>) {
return ( return (
<Input <Input
data-slot="sidebar-input" data-slot="sidebar-input"
@@ -297,7 +324,7 @@ function SidebarInput({className, ...props}: React.ComponentProps<typeof Input>)
className={cn("bg-background h-8 w-full shadow-none", className)} className={cn("bg-background h-8 w-full shadow-none", className)}
{...props} {...props}
/> />
); )
} }
function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) { function SidebarHeader({ className, ...props }: React.ComponentProps<"div">) {
@@ -308,7 +335,7 @@ function SidebarHeader({className, ...props}: React.ComponentProps<"div">) {
className={cn("flex flex-col gap-2 p-2", className)} className={cn("flex flex-col gap-2 p-2", className)}
{...props} {...props}
/> />
); )
} }
function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) { function SidebarFooter({ className, ...props }: React.ComponentProps<"div">) {
@@ -319,10 +346,13 @@ function SidebarFooter({className, ...props}: React.ComponentProps<"div">) {
className={cn("flex flex-col gap-2 p-2", className)} className={cn("flex flex-col gap-2 p-2", className)}
{...props} {...props}
/> />
); )
} }
function SidebarSeparator({className, ...props}: React.ComponentProps<typeof Separator>) { function SidebarSeparator({
className,
...props
}: React.ComponentProps<typeof Separator>) {
return ( return (
<Separator <Separator
data-slot="sidebar-separator" data-slot="sidebar-separator"
@@ -330,7 +360,7 @@ function SidebarSeparator({className, ...props}: React.ComponentProps<typeof Sep
className={cn("bg-sidebar-border mx-2 w-auto", className)} className={cn("bg-sidebar-border mx-2 w-auto", className)}
{...props} {...props}
/> />
); )
} }
function SidebarContent({ className, ...props }: React.ComponentProps<"div">) { function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
@@ -344,7 +374,7 @@ function SidebarContent({className, ...props}: React.ComponentProps<"div">) {
)} )}
{...props} {...props}
/> />
); )
} }
function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) { function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
@@ -355,24 +385,28 @@ function SidebarGroup({className, ...props}: React.ComponentProps<"div">) {
className={cn("relative flex w-full min-w-0 flex-col p-2", className)} className={cn("relative flex w-full min-w-0 flex-col p-2", className)}
{...props} {...props}
/> />
); )
} }
function SidebarGroupLabel({className, asChild = false, ...props}: React.ComponentProps<"div"> & {asChild?: boolean}) { function SidebarGroupLabel({
const Comp = asChild ? Slot : "div"; className,
asChild = false,
...props
}: React.ComponentProps<"div"> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "div"
return ( return (
<Comp <Comp
data-slot="sidebar-group-label" data-slot="sidebar-group-label"
data-sidebar="group-label" data-sidebar="group-label"
className={cn( className={cn(
"text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opa] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0", "text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-hidden transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0", "group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
className className
)} )}
{...props} {...props}
/> />
); )
} }
function SidebarGroupAction({ function SidebarGroupAction({
@@ -380,7 +414,7 @@ function SidebarGroupAction({
asChild = false, asChild = false,
...props ...props
}: React.ComponentProps<"button"> & { asChild?: boolean }) { }: React.ComponentProps<"button"> & { asChild?: boolean }) {
const Comp = asChild ? Slot : "button"; const Comp = asChild ? Slot : "button"
return ( return (
<Comp <Comp
@@ -395,10 +429,13 @@ function SidebarGroupAction({
)} )}
{...props} {...props}
/> />
); )
} }
function SidebarGroupContent({className, ...props}: React.ComponentProps<"div">) { function SidebarGroupContent({
className,
...props
}: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="sidebar-group-content" data-slot="sidebar-group-content"
@@ -406,7 +443,7 @@ function SidebarGroupContent({className, ...props}: React.ComponentProps<"div">)
className={cn("w-full text-sm", className)} className={cn("w-full text-sm", className)}
{...props} {...props}
/> />
); )
} }
function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) { function SidebarMenu({ className, ...props }: React.ComponentProps<"ul">) {
@@ -417,7 +454,7 @@ function SidebarMenu({className, ...props}: React.ComponentProps<"ul">) {
className={cn("flex w-full min-w-0 flex-col gap-1", className)} className={cn("flex w-full min-w-0 flex-col gap-1", className)}
{...props} {...props}
/> />
); )
} }
function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) { function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
@@ -428,7 +465,7 @@ function SidebarMenuItem({className, ...props}: React.ComponentProps<"li">) {
className={cn("group/menu-item relative", className)} className={cn("group/menu-item relative", className)}
{...props} {...props}
/> />
); )
} }
const sidebarMenuButtonVariants = cva( const sidebarMenuButtonVariants = cva(
@@ -451,7 +488,7 @@ const sidebarMenuButtonVariants = cva(
size: "default", size: "default",
}, },
} }
); )
function SidebarMenuButton({ function SidebarMenuButton({
asChild = false, asChild = false,
@@ -462,12 +499,12 @@ function SidebarMenuButton({
className, className,
...props ...props
}: React.ComponentProps<"button"> & { }: React.ComponentProps<"button"> & {
asChild?: boolean; asChild?: boolean
isActive?: boolean; isActive?: boolean
tooltip?: string | React.ComponentProps<typeof TooltipContent>; tooltip?: string | React.ComponentProps<typeof TooltipContent>
} & VariantProps<typeof sidebarMenuButtonVariants>) { } & VariantProps<typeof sidebarMenuButtonVariants>) {
const Comp = asChild ? Slot : "button"; const Comp = asChild ? Slot : "button"
const {isMobile, state} = useSidebar(); const { isMobile, state } = useSidebar()
const button = ( const button = (
<Comp <Comp
@@ -478,24 +515,29 @@ function SidebarMenuButton({
className={cn(sidebarMenuButtonVariants({ variant, size }), className)} className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
{...props} {...props}
/> />
); )
if (!tooltip) { if (!tooltip) {
return button; return button
} }
if (typeof tooltip === "string") { if (typeof tooltip === "string") {
tooltip = { tooltip = {
children: tooltip, children: tooltip,
}; }
} }
return ( return (
<Tooltip> <Tooltip>
<TooltipTrigger asChild>{button}</TooltipTrigger> <TooltipTrigger asChild>{button}</TooltipTrigger>
<TooltipContent side="right" align="center" hidden={state !== "collapsed" || isMobile} {...tooltip} /> <TooltipContent
side="right"
align="center"
hidden={state !== "collapsed" || isMobile}
{...tooltip}
/>
</Tooltip> </Tooltip>
); )
} }
function SidebarMenuAction({ function SidebarMenuAction({
@@ -504,10 +546,10 @@ function SidebarMenuAction({
showOnHover = false, showOnHover = false,
...props ...props
}: React.ComponentProps<"button"> & { }: React.ComponentProps<"button"> & {
asChild?: boolean; asChild?: boolean
showOnHover?: boolean; showOnHover?: boolean
}) { }) {
const Comp = asChild ? Slot : "button"; const Comp = asChild ? Slot : "button"
return ( return (
<Comp <Comp
@@ -527,10 +569,13 @@ function SidebarMenuAction({
)} )}
{...props} {...props}
/> />
); )
} }
function SidebarMenuBadge({className, ...props}: React.ComponentProps<"div">) { function SidebarMenuBadge({
className,
...props
}: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="sidebar-menu-badge" data-slot="sidebar-menu-badge"
@@ -546,7 +591,7 @@ function SidebarMenuBadge({className, ...props}: React.ComponentProps<"div">) {
)} )}
{...props} {...props}
/> />
); )
} }
function SidebarMenuSkeleton({ function SidebarMenuSkeleton({
@@ -554,12 +599,12 @@ function SidebarMenuSkeleton({
showIcon = false, showIcon = false,
...props ...props
}: React.ComponentProps<"div"> & { }: React.ComponentProps<"div"> & {
showIcon?: boolean; showIcon?: boolean
}) { }) {
// Random width between 50 to 90%. // Random width between 50 to 90%.
const width = React.useMemo(() => { const width = React.useMemo(() => {
return `${Math.floor(Math.random() * 40) + 50}%`; return `${Math.floor(Math.random() * 40) + 50}%`
}, []); }, [])
return ( return (
<div <div
@@ -568,7 +613,12 @@ function SidebarMenuSkeleton({
className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)} className={cn("flex h-8 items-center gap-2 rounded-md px-2", className)}
{...props} {...props}
> >
{showIcon && <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />} {showIcon && (
<Skeleton
className="size-4 rounded-md"
data-sidebar="menu-skeleton-icon"
/>
)}
<Skeleton <Skeleton
className="h-4 max-w-(--skeleton-width) flex-1" className="h-4 max-w-(--skeleton-width) flex-1"
data-sidebar="menu-skeleton-text" data-sidebar="menu-skeleton-text"
@@ -579,7 +629,7 @@ function SidebarMenuSkeleton({
} }
/> />
</div> </div>
); )
} }
function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) { function SidebarMenuSub({ className, ...props }: React.ComponentProps<"ul">) {
@@ -594,10 +644,13 @@ function SidebarMenuSub({className, ...props}: React.ComponentProps<"ul">) {
)} )}
{...props} {...props}
/> />
); )
} }
function SidebarMenuSubItem({className, ...props}: React.ComponentProps<"li">) { function SidebarMenuSubItem({
className,
...props
}: React.ComponentProps<"li">) {
return ( return (
<li <li
data-slot="sidebar-menu-sub-item" data-slot="sidebar-menu-sub-item"
@@ -605,7 +658,7 @@ function SidebarMenuSubItem({className, ...props}: React.ComponentProps<"li">) {
className={cn("group/menu-sub-item relative", className)} className={cn("group/menu-sub-item relative", className)}
{...props} {...props}
/> />
); )
} }
function SidebarMenuSubButton({ function SidebarMenuSubButton({
@@ -615,11 +668,11 @@ function SidebarMenuSubButton({
className, className,
...props ...props
}: React.ComponentProps<"a"> & { }: React.ComponentProps<"a"> & {
asChild?: boolean; asChild?: boolean
size?: "sm" | "md"; size?: "sm" | "md"
isActive?: boolean; isActive?: boolean
}) { }) {
const Comp = asChild ? Slot : "a"; const Comp = asChild ? Slot : "a"
return ( return (
<Comp <Comp
@@ -637,7 +690,7 @@ function SidebarMenuSubButton({
)} )}
{...props} {...props}
/> />
); )
} }
export { export {
@@ -665,4 +718,4 @@ export {
SidebarSeparator, SidebarSeparator,
SidebarTrigger, SidebarTrigger,
useSidebar, useSidebar,
}; }

View File

@@ -1,7 +1,13 @@
import {cn} from "../../lib/utils"; import { cn } from "@/lib/utils"
function Skeleton({ className, ...props }: React.ComponentProps<"div">) { function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
return <div data-slot="skeleton" className={cn("bg-primary/10 animate-pulse rounded-md", className)} {...props} />; return (
<div
data-slot="skeleton"
className={cn("bg-primary/10 animate-pulse rounded-md", className)}
{...props}
/>
)
} }
export {Skeleton}; export { Skeleton }

View File

@@ -1,29 +1,40 @@
"use client"; import * as React from "react"
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
import * as React from "react"; import { cn } from "@/lib/utils"
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
import {cn} from "../../lib/utils"; function TooltipProvider({
delayDuration = 0,
function TooltipProvider({delayDuration = 0, ...props}: React.ComponentProps<typeof TooltipPrimitive.Provider>) { ...props
return <TooltipPrimitive.Provider data-slot="tooltip-provider" delayDuration={delayDuration} {...props} />; }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
return (
<TooltipPrimitive.Provider
data-slot="tooltip-provider"
delayDuration={delayDuration}
{...props}
/>
)
} }
function Tooltip({...props}: React.ComponentProps<typeof TooltipPrimitive.Root>) { function Tooltip({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return ( return (
<TooltipProvider> <TooltipProvider>
<TooltipPrimitive.Root data-slot="tooltip" {...props} /> <TooltipPrimitive.Root data-slot="tooltip" {...props} />
</TooltipProvider> </TooltipProvider>
); )
} }
function TooltipTrigger({...props}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) { function TooltipTrigger({
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />; ...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />
} }
function TooltipContent({ function TooltipContent({
className, className,
sideOffset = 4, sideOffset = 0,
children, children,
...props ...props
}: React.ComponentProps<typeof TooltipPrimitive.Content>) { }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
@@ -33,7 +44,7 @@ function TooltipContent({
data-slot="tooltip-content" data-slot="tooltip-content"
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-w-sm rounded-md px-3 py-1.5 text-xs", "bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit rounded-md px-3 py-1.5 text-xs text-balance",
className className
)} )}
{...props} {...props}
@@ -42,7 +53,7 @@ function TooltipContent({
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" /> <TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
</TooltipPrimitive.Content> </TooltipPrimitive.Content>
</TooltipPrimitive.Portal> </TooltipPrimitive.Portal>
); )
} }
export {Tooltip, TooltipTrigger, TooltipContent, TooltipProvider}; export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }

View File

@@ -1,6 +1,6 @@
import { clsx, type ClassValue } from "clsx" 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

@@ -5,74 +5,74 @@
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));
:root { :root {
--background: hsl(0 0% 100%); --background: oklch(1 0 0);
--foreground: hsl(0 0% 3.9%); --foreground: oklch(0.145 0 0);
--card: hsl(0 0% 100%); --card: oklch(1 0 0);
--card-foreground: hsl(0 0% 3.9%); --card-foreground: oklch(0.145 0 0);
--popover: hsl(0 0% 100%); --popover: oklch(1 0 0);
--popover-foreground: hsl(0 0% 3.9%); --popover-foreground: oklch(0.145 0 0);
--primary: hsl(0 0% 9%); --primary: oklch(0.205 0 0);
--primary-foreground: hsl(0 0% 98%); --primary-foreground: oklch(0.985 0 0);
--secondary: hsl(0 0% 96.1%); --secondary: oklch(0.97 0 0);
--secondary-foreground: hsl(0 0% 9%); --secondary-foreground: oklch(0.205 0 0);
--muted: hsl(0 0% 96.1%); --muted: oklch(0.97 0 0);
--muted-foreground: hsl(0 0% 45.1%); --muted-foreground: oklch(0.556 0 0);
--accent: hsl(0 0% 96.1%); --accent: oklch(0.97 0 0);
--accent-foreground: hsl(0 0% 9%); --accent-foreground: oklch(0.205 0 0);
--destructive: hsl(0 84.2% 60.2%); --destructive: oklch(0.577 0.245 27.325);
--destructive-foreground: hsl(0 0% 98%); --destructive-foreground: oklch(0.577 0.245 27.325);
--border: hsl(0 0% 89.8%); --border: oklch(0.922 0 0);
--input: hsl(0 0% 89.8%); --input: oklch(0.922 0 0);
--ring: hsl(0 0% 3.9%); --ring: oklch(0.87 0 0);
--chart-1: hsl(12 76% 61%); --chart-1: oklch(0.646 0.222 41.116);
--chart-2: hsl(173 58% 39%); --chart-2: oklch(0.6 0.118 184.704);
--chart-3: hsl(197 37% 24%); --chart-3: oklch(0.398 0.07 227.392);
--chart-4: hsl(43 74% 66%); --chart-4: oklch(0.828 0.189 84.429);
--chart-5: hsl(27 87% 67%); --chart-5: oklch(0.769 0.188 70.08);
--radius: 0.6rem; --radius: 0.625rem;
--sidebar: hsl(0 0% 98%); --sidebar: oklch(0.985 0 0);
--sidebar-foreground: hsl(240 5.3% 26.1%); --sidebar-foreground: oklch(0.145 0 0);
--sidebar-primary: hsl(240 5.9% 10%); --sidebar-primary: oklch(0.205 0 0);
--sidebar-primary-foreground: hsl(0 0% 98%); --sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: hsl(240 4.8% 95.9%); --sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: hsl(240 5.9% 10%); --sidebar-accent-foreground: oklch(0.205 0 0);
--sidebar-border: hsl(220 13% 91%); --sidebar-border: oklch(0.922 0 0);
--sidebar-ring: hsl(217.2 91.2% 59.8%); --sidebar-ring: oklch(0.87 0 0);
} }
.dark { .dark {
--background: hsl(0 0% 3.9%); --background: oklch(0.145 0 0);
--foreground: hsl(0 0% 98%); --foreground: oklch(0.985 0 0);
--card: hsl(0 0% 3.9%); --card: oklch(0.145 0 0);
--card-foreground: hsl(0 0% 98%); --card-foreground: oklch(0.985 0 0);
--popover: hsl(0 0% 3.9%); --popover: oklch(0.145 0 0);
--popover-foreground: hsl(0 0% 98%); --popover-foreground: oklch(0.985 0 0);
--primary: hsl(0 0% 98%); --primary: oklch(0.985 0 0);
--primary-foreground: hsl(0 0% 9%); --primary-foreground: oklch(0.205 0 0);
--secondary: hsl(0 0% 14.9%); --secondary: oklch(0.269 0 0);
--secondary-foreground: hsl(0 0% 98%); --secondary-foreground: oklch(0.985 0 0);
--muted: hsl(0 0% 14.9%); --muted: oklch(0.269 0 0);
--muted-foreground: hsl(0 0% 63.9%); --muted-foreground: oklch(0.708 0 0);
--accent: hsl(0 0% 14.9%); --accent: oklch(0.269 0 0);
--accent-foreground: hsl(0 0% 98%); --accent-foreground: oklch(0.985 0 0);
--destructive: hsl(0 62.8% 30.6%); --destructive: oklch(0.396 0.141 25.723);
--destructive-foreground: hsl(0 0% 98%); --destructive-foreground: oklch(0.637 0.237 25.331);
--border: hsl(0 0% 14.9%); --border: oklch(0.269 0 0);
--input: hsl(0 0% 14.9%); --input: oklch(0.269 0 0);
--ring: hsl(0 0% 83.1%); --ring: oklch(0.439 0 0);
--chart-1: hsl(220 70% 50%); --chart-1: oklch(0.488 0.243 264.376);
--chart-2: hsl(160 60% 45%); --chart-2: oklch(0.696 0.17 162.48);
--chart-3: hsl(30 80% 55%); --chart-3: oklch(0.769 0.188 70.08);
--chart-4: hsl(280 65% 60%); --chart-4: oklch(0.627 0.265 303.9);
--chart-5: hsl(340 75% 55%); --chart-5: oklch(0.645 0.246 16.439);
--sidebar: hsl(240 5.9% 10%); --sidebar: oklch(0.205 0 0);
--sidebar-foreground: hsl(240 4.8% 95.9%); --sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: hsl(224.3 76.3% 48%); --sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: hsl(0 0% 100%); --sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: hsl(240 3.7% 15.9%); --sidebar-accent: oklch(0.269 0 0);
--sidebar-accent-foreground: hsl(240 4.8% 95.9%); --sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: hsl(240 3.7% 15.9%); --sidebar-border: oklch(0.269 0 0);
--sidebar-ring: hsl(217.2 91.2% 59.8%); --sidebar-ring: oklch(0.439 0 0);
} }
@theme inline { @theme inline {
@@ -104,14 +104,14 @@
--radius-md: calc(var(--radius) - 2px); --radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius); --radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px); --radius-xl: calc(var(--radius) + 4px);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar); --color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
} }
@layer base { @layer base {

View File

@@ -20,7 +20,11 @@
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true "noUncheckedSideEffectImports": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}, },
"include": ["src"] "include": ["src"]
} }

View File

@@ -2,7 +2,7 @@
"files": [], "files": [],
"references": [{"path": "./tsconfig.app.json"}, {"path": "./tsconfig.node.json"}], "references": [{"path": "./tsconfig.app.json"}, {"path": "./tsconfig.node.json"}],
"compilerOptions": { "compilerOptions": {
// "baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
} }

View File

@@ -7,10 +7,6 @@ import path from "path";
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react(), tailwindcss(), TanStackRouterVite({autoCodeSplitting: true})], plugins: [react(), tailwindcss(), TanStackRouterVite({autoCodeSplitting: true})],
// build: {
// outDir: path.resolve(__dirname, "../../dist/frontend/dist"),
// emptyOutDir: true,
// },
resolve: { resolve: {
alias: { alias: {
"@": path.resolve(__dirname, "./src"), "@": path.resolve(__dirname, "./src"),
@@ -18,7 +14,7 @@ export default defineConfig({
}, },
server: { server: {
proxy: { proxy: {
"/api": {target: `http://localhost:4000`, changeOrigin: true}, "/api": {target: `http://localhost:4400`, changeOrigin: true},
}, },
}, },
}); });

10
globals.d.ts vendored
View File

@@ -1,10 +0,0 @@
declare global {
namespace NodeJS {
interface ProcessEnv {
JWT_SECRET: string;
JWT_EXPIRES: string;
}
}
}
export {};

View File

@@ -1,10 +0,0 @@
const path = require("path");
const dotenv = require("dotenv");
const dotenvExpand = require("dotenv-expand");
// Load the root .env file
const envPath = path.resolve(__dirname, ".env");
const envConfig = dotenv.config({path: envPath});
// Expand variables (e.g., `${VAR}`) in the .env file
dotenvExpand.expand(envConfig);

6574
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,52 +1,40 @@
{ {
"name": "lstv2", "name": "fullstack-app",
"version": "1.2.0", "version": "1.0.0",
"description": "", "type": "module",
"main": "index.ts",
"scripts": { "scripts": {
"dev": "concurrently -n 'server,frontend' -c '#007755,#2f6da3' 'bun --watch server/index.ts' 'cd frontend && bunx --bun vite'", "dev": "concurrently -n \"server,frontend\" -c \"#007755,#2f6da3\" \"npm run dev:server\" \"cd frontend && npm run dev\"",
"dev:server": "bun --watch server/index.ts", "dev:server": "dotenvx run -f .env -- tsx watch server/src/index.ts",
"dev:ocme": "bun --watch ocme/index.ts", "dev:frontend": "cd frontend && npm run dev",
"dev:front": "cd frontend && bunx --bun vite", "build": "npm run build:server && npm run build:frontend",
"build:server": "cd apps/server && bun build index.js --outdir ../../dist/server", "build:server": "cd server && npm run build",
"build:ocme": "rimraf dist/ocme && cd apps/ocme && bun build index.js --outdir ../../dist/ocme", "build:frontend": "cd frontend && npm run build",
"build:front": "cd frontend && rimraf frontend/dist && bun run build", "start": "npm run start:server",
"start": "bun --env-file .env server/index.js", "start:server": "cd server && npm start",
"commit": "cz", "db:generate": "npx drizzle-kit generate",
"clean": "rimraf dist/server", "db:migrate": "npx drizzle-kit push",
"deploy": "standard-version --conventional-commits", "deploy": "standard-version --conventional-commits",
"ui:add": "cd frontend && bun shadcn add ", "commit": "cz"
"db:dev": "bun drizzle-kit generate && bun drizzle-kit migrate"
}, },
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": { "dependencies": {
"@dotenvx/dotenvx": "^1.35.0", "@dotenvx/dotenvx": "^1.38.3",
"@hono/zod-openapi": "^0.18.4", "hono": "^4.7.2",
"@scalar/hono-api-reference": "^0.5.175",
"@types/bun": "^1.2.2",
"@types/jsonwebtoken": "^9.0.8",
"axios": "^1.7.9",
"bcrypt": "^5.1.1",
"compression": "^1.8.0",
"cookie": "^1.0.2",
"date-fns": "^4.1.0",
"drizzle-orm": "^0.39.3", "drizzle-orm": "^0.39.3",
"drizzle-zod": "^0.7.0", "drizzle-zod": "^0.7.0",
"hono": "^4.7.1",
"jsonwebtoken": "^9.0.2",
"pg": "^8.13.3", "pg": "^8.13.3",
"postgres": "^3.4.5", "postgres": "^3.4.5",
"zod": "^3.24.2" "zod": "^3.24.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^22.13.5",
"concurrently": "^8.2.0",
"dotenv": "^16.3.1",
"drizzle-kit": "^0.30.4",
"tsx": "^4.7.1",
"@types/bcrypt": "^5.0.2", "@types/bcrypt": "^5.0.2",
"@types/js-cookie": "^3.0.6", "@types/js-cookie": "^3.0.6",
"@types/pg": "^8.11.11", "@types/pg": "^8.11.11",
"concurrently": "^9.1.2",
"cz-conventional-changelog": "^3.3.0", "cz-conventional-changelog": "^3.3.0",
"drizzle-kit": "^0.30.4",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"standard-version": "^9.5.0", "standard-version": "^9.5.0",
"typescript": "~5.7.3" "typescript": "~5.7.3"

28
server/.gitignore vendored Normal file
View File

@@ -0,0 +1,28 @@
# dev
.yarn/
!.yarn/releases
.vscode/*
!.vscode/launch.json
!.vscode/*.code-snippets
.idea/workspace.xml
.idea/usage.statistics.xml
.idea/shelf
# deps
node_modules/
# env
.env
.env.production
# logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# misc
.DS_Store

8
server/README.md Normal file
View File

@@ -0,0 +1,8 @@
```
npm install
npm run dev
```
```
open http://localhost:3000
```

View File

@@ -1,15 +0,0 @@
CREATE TABLE "users" (
"user_id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"username" text NOT NULL,
"email" text NOT NULL,
"password" text NOT NULL,
"passwordToken" text,
"passwordTokenExpires" timestamp,
"active" boolean DEFAULT true NOT NULL,
"pingcode" numeric,
"lastLogin" timestamp DEFAULT now(),
"add_User" text DEFAULT 'LST_System' NOT NULL,
"add_Date" timestamp DEFAULT now(),
"upd_User" text DEFAULT 'LST_System' NOT NULL,
"upd_date" timestamp DEFAULT now()
);

View File

@@ -1 +0,0 @@
CREATE UNIQUE INDEX "username" ON "users" USING btree ("username");

View File

@@ -1,2 +0,0 @@
ALTER TABLE "userRoles" RENAME COLUMN "roles" TO "role";--> statement-breakpoint
ALTER TABLE "modules" ADD COLUMN "roles" text NOT NULL;

View File

@@ -1 +0,0 @@
ALTER TABLE "modules" ALTER COLUMN "roles" SET DEFAULT '["view", "systemAdmin"]';

View File

@@ -1 +0,0 @@
ALTER TABLE "users" ADD COLUMN "role" text DEFAULT 'user' NOT NULL;

View File

@@ -1,117 +0,0 @@
{
"id": "d1b4a8a6-caa3-4c45-a3a1-cdfc99ca7bea",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.users": {
"name": "users",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": true
},
"passwordToken": {
"name": "passwordToken",
"type": "text",
"primaryKey": false,
"notNull": false
},
"passwordTokenExpires": {
"name": "passwordTokenExpires",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"active": {
"name": "active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"pingcode": {
"name": "pingcode",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"lastLogin": {
"name": "lastLogin",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"add_User": {
"name": "add_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -1,133 +0,0 @@
{
"id": "d6c99236-0eea-49f1-817b-13139c0f42f5",
"prevId": "d1b4a8a6-caa3-4c45-a3a1-cdfc99ca7bea",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.users": {
"name": "users",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": true
},
"passwordToken": {
"name": "passwordToken",
"type": "text",
"primaryKey": false,
"notNull": false
},
"passwordTokenExpires": {
"name": "passwordTokenExpires",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"active": {
"name": "active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"pingcode": {
"name": "pingcode",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"lastLogin": {
"name": "lastLogin",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"add_User": {
"name": "add_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"username": {
"name": "username",
"columns": [
{
"expression": "username",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -1,407 +0,0 @@
{
"id": "9a2ddf11-fd30-4dd9-bf2d-259fe7aed201",
"prevId": "d6c99236-0eea-49f1-817b-13139c0f42f5",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.modules": {
"name": "modules",
"schema": "",
"columns": {
"module_id": {
"name": "module_id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"active": {
"name": "active",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"add_User": {
"name": "add_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"module_name": {
"name": "module_name",
"columns": [
{
"expression": "name",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.roles": {
"name": "roles",
"schema": "",
"columns": {
"role_id": {
"name": "role_id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"add_User": {
"name": "add_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"role_name": {
"name": "role_name",
"columns": [
{
"expression": "name",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.userRoles": {
"name": "userRoles",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"role_id": {
"name": "role_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"module_id": {
"name": "module_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"roles": {
"name": "roles",
"type": "text",
"primaryKey": false,
"notNull": true
},
"add_User": {
"name": "add_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"user_module_unique": {
"name": "user_module_unique",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "module_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"userRoles_user_id_users_user_id_fk": {
"name": "userRoles_user_id_users_user_id_fk",
"tableFrom": "userRoles",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"user_id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"userRoles_role_id_roles_role_id_fk": {
"name": "userRoles_role_id_roles_role_id_fk",
"tableFrom": "userRoles",
"tableTo": "roles",
"columnsFrom": [
"role_id"
],
"columnsTo": [
"role_id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"userRoles_module_id_modules_module_id_fk": {
"name": "userRoles_module_id_modules_module_id_fk",
"tableFrom": "userRoles",
"tableTo": "modules",
"columnsFrom": [
"module_id"
],
"columnsTo": [
"module_id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": true
},
"passwordToken": {
"name": "passwordToken",
"type": "text",
"primaryKey": false,
"notNull": false
},
"passwordTokenExpires": {
"name": "passwordTokenExpires",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"active": {
"name": "active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"pingcode": {
"name": "pingcode",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"lastLogin": {
"name": "lastLogin",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"add_User": {
"name": "add_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"username": {
"name": "username",
"columns": [
{
"expression": "username",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -1,413 +0,0 @@
{
"id": "ad424668-721e-414f-ad0b-a8e7301e50c6",
"prevId": "9a2ddf11-fd30-4dd9-bf2d-259fe7aed201",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.modules": {
"name": "modules",
"schema": "",
"columns": {
"module_id": {
"name": "module_id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"active": {
"name": "active",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"roles": {
"name": "roles",
"type": "text",
"primaryKey": false,
"notNull": true
},
"add_User": {
"name": "add_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"module_name": {
"name": "module_name",
"columns": [
{
"expression": "name",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.roles": {
"name": "roles",
"schema": "",
"columns": {
"role_id": {
"name": "role_id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"add_User": {
"name": "add_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"role_name": {
"name": "role_name",
"columns": [
{
"expression": "name",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.userRoles": {
"name": "userRoles",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"role_id": {
"name": "role_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"module_id": {
"name": "module_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true
},
"add_User": {
"name": "add_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"user_module_unique": {
"name": "user_module_unique",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "module_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"userRoles_user_id_users_user_id_fk": {
"name": "userRoles_user_id_users_user_id_fk",
"tableFrom": "userRoles",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"user_id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"userRoles_role_id_roles_role_id_fk": {
"name": "userRoles_role_id_roles_role_id_fk",
"tableFrom": "userRoles",
"tableTo": "roles",
"columnsFrom": [
"role_id"
],
"columnsTo": [
"role_id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"userRoles_module_id_modules_module_id_fk": {
"name": "userRoles_module_id_modules_module_id_fk",
"tableFrom": "userRoles",
"tableTo": "modules",
"columnsFrom": [
"module_id"
],
"columnsTo": [
"module_id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": true
},
"passwordToken": {
"name": "passwordToken",
"type": "text",
"primaryKey": false,
"notNull": false
},
"passwordTokenExpires": {
"name": "passwordTokenExpires",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"active": {
"name": "active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"pingcode": {
"name": "pingcode",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"lastLogin": {
"name": "lastLogin",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"add_User": {
"name": "add_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"username": {
"name": "username",
"columns": [
{
"expression": "username",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -1,414 +0,0 @@
{
"id": "313590a8-2068-45b5-96fc-cfa5d2b32b56",
"prevId": "ad424668-721e-414f-ad0b-a8e7301e50c6",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.modules": {
"name": "modules",
"schema": "",
"columns": {
"module_id": {
"name": "module_id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"active": {
"name": "active",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"roles": {
"name": "roles",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'[\"view\", \"systemAdmin\"]'"
},
"add_User": {
"name": "add_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"module_name": {
"name": "module_name",
"columns": [
{
"expression": "name",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.roles": {
"name": "roles",
"schema": "",
"columns": {
"role_id": {
"name": "role_id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"add_User": {
"name": "add_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"role_name": {
"name": "role_name",
"columns": [
{
"expression": "name",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.userRoles": {
"name": "userRoles",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"role_id": {
"name": "role_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"module_id": {
"name": "module_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"role": {
"name": "role",
"type": "text",
"primaryKey": false,
"notNull": true
},
"add_User": {
"name": "add_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"user_module_unique": {
"name": "user_module_unique",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "module_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"userRoles_user_id_users_user_id_fk": {
"name": "userRoles_user_id_users_user_id_fk",
"tableFrom": "userRoles",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"user_id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"userRoles_role_id_roles_role_id_fk": {
"name": "userRoles_role_id_roles_role_id_fk",
"tableFrom": "userRoles",
"tableTo": "roles",
"columnsFrom": [
"role_id"
],
"columnsTo": [
"role_id"
],
"onDelete": "no action",
"onUpdate": "no action"
},
"userRoles_module_id_modules_module_id_fk": {
"name": "userRoles_module_id_modules_module_id_fk",
"tableFrom": "userRoles",
"tableTo": "modules",
"columnsFrom": [
"module_id"
],
"columnsTo": [
"module_id"
],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": true
},
"passwordToken": {
"name": "passwordToken",
"type": "text",
"primaryKey": false,
"notNull": false
},
"passwordTokenExpires": {
"name": "passwordTokenExpires",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"active": {
"name": "active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"pingcode": {
"name": "pingcode",
"type": "numeric",
"primaryKey": false,
"notNull": false
},
"lastLogin": {
"name": "lastLogin",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"add_User": {
"name": "add_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"add_Date": {
"name": "add_Date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
},
"upd_User": {
"name": "upd_User",
"type": "text",
"primaryKey": false,
"notNull": true,
"default": "'LST_System'"
},
"upd_date": {
"name": "upd_date",
"type": "timestamp",
"primaryKey": false,
"notNull": false,
"default": "now()"
}
},
"indexes": {
"username": {
"name": "username",
"columns": [
{
"expression": "username",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": true,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -1,48 +0,0 @@
{
"version": "7",
"dialect": "postgresql",
"entries": [
{
"idx": 0,
"version": "7",
"when": 1740160921910,
"tag": "0000_typical_frightful_four",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1740161259149,
"tag": "0001_sharp_pet_avengers",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1740361491818,
"tag": "0002_bitter_oracle",
"breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1740362541964,
"tag": "0003_luxuriant_namorita",
"breakpoints": true
},
{
"idx": 4,
"version": "7",
"when": 1740364483791,
"tag": "0004_quick_mandrill",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1740445275919,
"tag": "0005_tough_emma_frost",
"breakpoints": true
}
]
}

10
server/drizzle.config.js Normal file
View File

@@ -0,0 +1,10 @@
import { defineConfig } from "drizzle-kit";
const database = process.env.DATABASE_URL || "";
export default defineConfig({
dialect: "postgresql",
schema: "database/schema",
out: "database/migrations",
dbCredentials: {
url: database,
},
});

View File

@@ -1,14 +0,0 @@
import app from "./src/app";
const port = process.env.SERVER_PORT || 4000;
Bun.serve({
port,
fetch: app.fetch,
hostname: "0.0.0.0",
});
// await Bun.build({
// entrypoints: ["./index.js"],
// outdir: "../../dist/server",
// });
console.log(`server is running on port ${port}`);

2123
server/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

31
server/package.json Normal file
View File

@@ -0,0 +1,31 @@
{
"name": "server",
"type": "module",
"scripts": {
"dev": "dotenvx run -f ../.env -- tsx watch src/index.ts",
"build": "rimraf dist && tsc --build",
"start": "dotenvx run node dist/server/src/index.js"
},
"dependencies": {
"@hono/node-server": "^1.13.8",
"@dotenvx/dotenvx": "^1.35.0",
"@hono/zod-openapi": "^0.18.4",
"@scalar/hono-api-reference": "^0.5.175",
"@types/jsonwebtoken": "^9.0.8",
"axios": "^1.7.9",
"bcrypt": "^5.1.1",
"compression": "^1.8.0",
"cookie": "^1.0.2",
"date-fns": "^4.1.0",
"dotenv": "^16.4.7",
"drizzle-orm": "^0.39.3",
"drizzle-zod": "^0.7.0",
"jsonwebtoken": "^9.0.2",
"pg": "^8.13.3",
"postgres": "^3.4.5",
"zod": "^3.24.2"
},
"devDependencies": {
"typescript": "~5.7.3"
}
}

View File

@@ -1,62 +0,0 @@
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
import {serveStatic} from "hono/bun";
import {logger} from "hono/logger";
import {cors} from "hono/cors";
import {OpenAPIHono} from "@hono/zod-openapi";
//routes
import auth from "./services/auth/authService";
import scalar from "./services/general/route/scalar";
import apiHits from "./services/general/route/apitHits";
import system from "./services/system/systemServer";
const app = new OpenAPIHono();
app.use("*", logger());
const allowedOrigins = ["http://localhost:3000", "http://localhost:4000", "http://localhost:5173"];
app.use(
"*",
cors({
origin: allowedOrigins,
allowHeaders: ["X-Custom-Header", "Upgrade-Insecure-Requests"],
allowMethods: ["POST", "GET", "OPTIONS"],
exposeHeaders: ["Content-Length", "X-Kuma-Revision"],
maxAge: 600,
credentials: true,
})
);
app.doc("/api", {
openapi: "3.0.0",
info: {
version: "1.0.0",
title: "LST API",
},
});
// as we dont want to change ocme again well use a proxy to this
// app.all("/ocme/*", async (c) => {
// return ocmeService(c);
// });
const routes = [scalar, auth, apiHits, system] as const;
routes.forEach((route) => {
app.route("/api/", route);
});
//app.basePath("/api/auth").route("/login", login).route("/session", session).route("/register", register);
//auth stuff
// app.get("/api/protected", authMiddleware, (c) => {
// return c.json({success: true, message: "is authenticated"});
// });
app.get("*", serveStatic({root: "./frontend/dist"}));
app.get("*", serveStatic({path: "./frontend/dist/index.html"}));
export default app;
//export type ApiRoute = typeof apiRoute;

26
server/src/globalUtils/apiHits.d.ts vendored Normal file
View File

@@ -0,0 +1,26 @@
import type { Context } from "hono";
import { z } from "zod";
declare const requestSchema: z.ZodObject<{
ip: z.ZodOptional<z.ZodString>;
endpoint: z.ZodString;
action: z.ZodOptional<z.ZodString>;
stats: z.ZodOptional<z.ZodString>;
}, "strip", z.ZodTypeAny, {
ip?: string;
endpoint?: string;
action?: string;
stats?: string;
}, {
ip?: string;
endpoint?: string;
action?: string;
stats?: string;
}>;
type ApiHitData = z.infer<typeof requestSchema>;
export declare const apiHit: (c: Context, data: unknown) => Promise<{
success: boolean;
data?: ApiHitData;
errors?: any[];
}>;
export {};
//# sourceMappingURL=apiHits.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"apiHits.d.ts","sourceRoot":"","sources":["apiHits.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,MAAM,CAAC;AAClC,OAAO,EAAC,CAAC,EAAW,MAAM,KAAK,CAAC;AAGhC,QAAA,MAAM,aAAa;;;;;;;;;;;;;;;EAKjB,CAAC;AAEH,KAAK,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEhD,eAAO,MAAM,MAAM,MACZ,OAAO,QACJ,OAAO,KACd,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,UAAU,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,EAAE,CAAA;CAAC,CAuB/D,CAAC"}

View File

@@ -0,0 +1,20 @@
import { z, ZodError } from "zod";
const requestSchema = z.object({
ip: z.string().optional(),
endpoint: z.string(),
action: z.string().optional(),
stats: z.string().optional(),
});
export const apiHit = async (c, data) => {
try {
const forwarded = c.req.header("host");
const validatedData = requestSchema.parse(data);
return { success: true, data: validatedData };
}
catch (error) {
if (error instanceof ZodError) {
return { success: false, errors: error.errors };
}
return { success: false, errors: [{ message: "An unknown error occurred" }] };
}
};

View File

@@ -1,5 +1,6 @@
import type {Context} from "hono";
import {z, ZodError} from "zod"; import {z, ZodError} from "zod";
import {Context} from "hono";
// Define the request body schema // Define the request body schema
const requestSchema = z.object({ const requestSchema = z.object({
ip: z.string().optional(), ip: z.string().optional(),

71
server/src/index.ts Normal file
View File

@@ -0,0 +1,71 @@
import {serve} from "@hono/node-server";
import {OpenAPIHono} from "@hono/zod-openapi";
import {serveStatic} from "@hono/node-server/serve-static";
import {logger} from "hono/logger";
import {cors} from "hono/cors";
import {db} from "../../database/dbclient.js";
import {modules} from "../../database/schema/modules.js";
// custom routes
import scalar from "./services/general/route/scalar.js";
import system from "./services/server/systemServer.js";
import auth from "./services/auth/authService.js";
const allowedOrigins = ["http://localhost:3000", "http://localhost:4000", "http://localhost:5173"];
const app = new OpenAPIHono();
// middle ware
app.use("*", logger());
app.use(
"*",
cors({
origin: allowedOrigins,
allowHeaders: ["X-Custom-Header", "Upgrade-Insecure-Requests"],
allowMethods: ["POST", "GET", "OPTIONS"],
exposeHeaders: ["Content-Length", "X-Kuma-Revision"],
maxAge: 600,
credentials: true,
})
);
app.doc("/api/ref", {
openapi: "3.0.0",
info: {
version: "1.0.0",
title: "LST API",
},
});
const routes = [
scalar,
auth,
// apiHits,
system,
] as const;
routes.forEach((route) => {
app.route("/api/", route);
});
// the catch all api route
app.all("/api/*", (c) => c.json({error: "API route not found"}, 404));
app.all("/ocme/*", async (c) => {
//return ocmeService(c);
c.json({error: "Ocme route not found"}, 404);
});
// front end static files
app.get("*", serveStatic({root: "../frontend/dist"}));
app.get("*", serveStatic({path: "../frontend/dist/index.html"}));
serve(
{
fetch: app.fetch,
port: Number(process.env.SERVER_PORT),
},
(info) => {
console.log(`Server is running on http://localhost:${info.port}`);
}
);

View File

@@ -1,8 +1,8 @@
import {OpenAPIHono} from "@hono/zod-openapi"; import {OpenAPIHono} from "@hono/zod-openapi";
import login from "./routes/login"; import login from "./routes/login.js";
import register from "./routes/register"; import register from "./routes/register.js";
import session from "./routes/session"; import session from "./routes/session.js";
const app = new OpenAPIHono(); const app = new OpenAPIHono();
app.route("auth/login", login); app.route("auth/login", login);

View File

@@ -4,8 +4,8 @@ in the login route we attach it to user under roles.
*/ */
import {eq} from "drizzle-orm"; import {eq} from "drizzle-orm";
import {db} from "../../../../database/dbClient"; import {db} from "../../../../../database/dbclient.js";
import {userRoles} from "../../../../database/schema/userRoles"; import {userRoles} from "../../../../../database/schema/userRoles.js";
export const roleCheck = async (user_id: any) => { export const roleCheck = async (user_id: any) => {
// get the user roles by the user_id // get the user roles by the user_id

View File

@@ -1,13 +1,14 @@
import {sign, verify} from "jsonwebtoken"; import jwt from "jsonwebtoken";
import {db} from "../../../../database/dbClient"; import {db} from "../../../../../database/dbclient.js";
import {users} from "../../../../database/schema/users"; import {users} from "../../../../../database/schema/users.js";
import {eq} from "drizzle-orm"; import {eq, sql} from "drizzle-orm";
import {checkPassword} from "../utils/checkPassword"; import {checkPassword} from "../utils/checkPassword.js";
import {roleCheck} from "./getUserAccess"; import {roleCheck} from "./getUserAccess.js";
/** /**
* Authenticate a user and return a JWT. * Authenticate a user and return a JWT.
*/ */
const {sign, verify} = jwt;
export async function login( export async function login(
username: string, username: string,
@@ -15,7 +16,7 @@ export async function login(
): Promise<{token: string; user: {user_id: string; username: string}}> { ): Promise<{token: string; user: {user_id: string; username: string}}> {
const user = await db.select().from(users).where(eq(users.username, username)); const user = await db.select().from(users).where(eq(users.username, username));
console.log(user); //console.log(user);
if (user.length === 0) { if (user.length === 0) {
throw new Error("Invalid or Missing user"); throw new Error("Invalid or Missing user");
} }
@@ -27,7 +28,7 @@ export async function login(
} }
// Create a JWT // Create a JWT
const secret: string = process.env.JWT_SECRET! || "bnghsjhsd"; const secret: string = process.env.JWT_SECRET!;
const expiresIn = Number(process.env.JWT_EXPIRES!) || 60; const expiresIn = Number(process.env.JWT_EXPIRES!) || 60;
// get the user roles // get the user roles
@@ -39,6 +40,15 @@ export async function login(
roles: roles || null, roles: roles || null,
role: user[0].role || null, // this should be removed onces full migration to v2 is completed role: user[0].role || null, // this should be removed onces full migration to v2 is completed
}; };
// update the user last login
// try {
// db.update(users)
// .set({lastLogin: sql`NOW()`})
// .where(eq(users.user_id, user[0].user_id));
// } catch (e) {
// console.log(e);
// }
const token = sign({user: userData}, secret, {expiresIn: expiresIn * 60}); const token = sign({user: userData}, secret, {expiresIn: expiresIn * 60});
return {token, user: userData}; return {token, user: userData};

View File

@@ -11,7 +11,7 @@ export const authMiddleware: MiddlewareHandler = async (c, next) => {
const token = authHeader.split(" ")[1]; const token = authHeader.split(" ")[1];
try { try {
const decoded = verify(token, process.env.JWT_SECRET, {ignoreExpiration: false}) as { const decoded = verify(token, process.env.JWT_SECRET!, {ignoreExpiration: false}) as {
userId: number; userId: number;
exp: number; exp: number;
}; };
@@ -22,8 +22,10 @@ export const authMiddleware: MiddlewareHandler = async (c, next) => {
// If the token has less than REFRESH_THRESHOLD seconds left, refresh it // If the token has less than REFRESH_THRESHOLD seconds left, refresh it
let newToken = null; let newToken = null;
if (timeLeft < parseInt(process.env.REFRESH_THRESHOLD)) { if (timeLeft < parseInt(process.env.REFRESH_THRESHOLD!)) {
newToken = sign({userId: decoded.userId}, process.env.JWT_SECRET, {expiresIn: process.env.EXPIRATION_TIME}); newToken = sign({userId: decoded.userId}, process.env.JWT_SECRET!, {
expiresIn: parseInt(process.env.EXPIRATION_TIME!),
});
c.res.headers.set("Authorization", `Bearer ${newToken}`); c.res.headers.set("Authorization", `Bearer ${newToken}`);
} }

View File

@@ -1,5 +1,5 @@
import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi"; import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi";
import {login} from "../controllers/login"; import {login} from "../controllers/login.js";
const app = new OpenAPIHono(); const app = new OpenAPIHono();

View File

@@ -1,8 +1,8 @@
import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi"; import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi";
import {db} from "../../../../database/dbClient"; import {db} from "../../../../../database/dbclient.js";
import {users} from "../../../../database/schema/users"; import {users} from "../../../../../database/schema/users.js";
import {apiHit} from "../../../globalUtils/apitHits"; import {apiHit} from "../../../globalUtils/apiHits.js";
import {createPassword} from "../utils/createPassword"; import {createPassword} from "../utils/createPassword.js";
import {eq} from "drizzle-orm"; import {eq} from "drizzle-orm";
const app = new OpenAPIHono(); const app = new OpenAPIHono();

View File

@@ -1,13 +0,0 @@
import {OpenAPIHono} from "@hono/zod-openapi";
const app = new OpenAPIHono();
// the doc endpoint
app.doc("/", {
openapi: "3.0.0",
info: {
version: "1.0.0",
title: "LST API",
},
});
export default app;

View File

@@ -1,5 +1,4 @@
import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi"; import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi";
import {apiHit} from "../../../globalUtils/apitHits";
const app = new OpenAPIHono(); const app = new OpenAPIHono();

View File

@@ -47,7 +47,7 @@ app.get(
"undici", "undici",
], ],
spec: { spec: {
url: "/api", url: "/api/ref",
}, },
baseServerURL: "https://scalar.com", baseServerURL: "https://scalar.com",
servers: [ servers: [

View File

@@ -1,23 +0,0 @@
import { Context } from "hono";
export const ocmeService = async (c: Context) => {
const url = new URL(c.req.url);
const ocmeUrl = `http://localhost:${
process.env.OCME_PORT
}${url.pathname.replace("/ocme", "")}`;
console.log(ocmeUrl);
const ocmeResponse = await fetch(ocmeUrl, {
method: c.req.method,
headers: c.req.raw.headers,
body:
c.req.method !== "GET" && c.req.method !== "HEAD"
? await c.req.text()
: undefined,
});
return new Response(ocmeResponse.body, {
status: ocmeResponse.status,
headers: ocmeResponse.headers,
});
};

View File

@@ -0,0 +1,19 @@
import type {Context} from "hono";
export const ocmeService = async (c: Context) => {
const url = new URL(c.req.url);
const ocmeUrl = `http://localhost:${process.env.OCME_PORT}${url.pathname.replace("/ocme", "")}`;
console.log(ocmeUrl);
const ocmeResponse = await fetch(ocmeUrl, {
method: c.req.method,
headers: c.req.raw.headers,
body: c.req.method !== "GET" && c.req.method !== "HEAD" ? await c.req.text() : undefined,
});
return new Response(ocmeResponse.body, {
status: ocmeResponse.status,
headers: ocmeResponse.headers,
});
};

View File

@@ -1,6 +1,6 @@
import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi"; import {z, createRoute, OpenAPIHono} from "@hono/zod-openapi";
import {modules} from "../../../../database/schema/modules"; import {modules} from "../../../../../database/schema/modules.js";
import {db} from "../../../../database/dbClient"; import {db} from "../../../../../database/dbclient.js";
import {eq} from "drizzle-orm"; import {eq} from "drizzle-orm";
// Define the request body schema // Define the request body schema
@@ -27,7 +27,7 @@ app.openapi(
tags: ["server"], tags: ["server"],
summary: "Returns all modules in the server", summary: "Returns all modules in the server",
method: "get", method: "get",
path: "/server/modules", path: "/",
responses: { responses: {
200: { 200: {
content: { content: {
@@ -38,6 +38,7 @@ app.openapi(
}, },
}), }),
async (c) => { async (c) => {
//console.log("system modules");
let module: any = []; let module: any = [];
try { try {
module = await db.select().from(modules).where(eq(modules.active, true)); module = await db.select().from(modules).where(eq(modules.active, true));

View File

@@ -0,0 +1,7 @@
import {OpenAPIHono} from "@hono/zod-openapi";
import modules from "./route/modules.js";
const app = new OpenAPIHono().route("server/modules", modules);
export default app;

View File

@@ -1,7 +0,0 @@
import {OpenAPIHono} from "@hono/zod-openapi";
import modules from "./route/modules";
const app = new OpenAPIHono().route("system/module", modules);
export default app;

16
server/tsconfig.json Normal file
View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "NodeNext",
"moduleResolution": "nodenext",
"strict": true,
"verbatimModuleSyntax": true,
"skipLibCheck": true,
"types": ["node"],
"jsx": "react-jsx",
"jsxImportSource": "hono/jsx",
"outDir": "./dist",
"removeComments": true
},
"exclude": ["node_modules"]
}

View File

@@ -1,25 +0,0 @@
{
"compilerOptions": {
"lib": ["ESNext"],
"module": "esnext",
"target": "esnext",
"moduleResolution": "bundler",
"moduleDetection": "force",
"allowImportingTsExtensions": true,
"noEmit": true,
"composite": true,
"strict": true,
"downlevelIteration": true,
"skipLibCheck": true,
"jsx": "react-jsx",
"removeComments": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"allowJs": true,
"types": [
"bun-types" // add Bun global
],
"outDir": "dist"
},
"include": ["./server/src", "./server/index.ts", "./server/database"]
}