before remove routers
This commit is contained in:
26
package.json
26
package.json
@@ -18,12 +18,12 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@clerk/nextjs": "^6.23.2",
|
||||
"@clerk/nextjs": "^6.27.1",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||
"@fortawesome/react-fontawesome": "^0.2.3",
|
||||
"@gsap/react": "^2.1.2",
|
||||
"@hookform/resolvers": "^5.1.1",
|
||||
"@hookform/resolvers": "^5.2.0",
|
||||
"@neondatabase/serverless": "^1.0.1",
|
||||
"@radix-ui/react-accordion": "^1.2.11",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.14",
|
||||
@@ -52,13 +52,13 @@
|
||||
"@radix-ui/react-toggle-group": "^1.1.10",
|
||||
"@radix-ui/react-tooltip": "^1.2.7",
|
||||
"@t3-oss/env-nextjs": "^0.12.0",
|
||||
"@tanstack/react-query": "^5.81.5",
|
||||
"@tanstack/react-query-next-experimental": "^5.81.5",
|
||||
"@tanstack/react-query": "^5.83.0",
|
||||
"@tanstack/react-query-next-experimental": "^5.83.0",
|
||||
"@trpc/client": "^11.4.3",
|
||||
"@trpc/next": "^11.4.3",
|
||||
"@trpc/react-query": "^11.4.3",
|
||||
"@trpc/server": "^11.4.3",
|
||||
"@uiw/react-md-editor": "^4.0.7",
|
||||
"@uiw/react-md-editor": "^4.0.8",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
@@ -74,33 +74,33 @@
|
||||
"next": "15.4.0-canary.17",
|
||||
"next-themes": "^0.4.6",
|
||||
"postgres": "^3.4.7",
|
||||
"react": "^19.1.0",
|
||||
"react": "^19.1.1",
|
||||
"react-day-picker": "8.10.1",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-hook-form": "^7.59.0",
|
||||
"react-dom": "^19.1.1",
|
||||
"react-hook-form": "^7.61.1",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-resizable-panels": "^3.0.3",
|
||||
"recharts": "^2.15.4",
|
||||
"rehype-highlight": "^7.0.2",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"server-only": "^0.0.1",
|
||||
"sonner": "^2.0.5",
|
||||
"sonner": "^2.0.6",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"tailwindcss-motion": "^1.1.1",
|
||||
"type-fest": "^4.41.0",
|
||||
"vaul": "^1.1.2",
|
||||
"zod": "^3.25.70"
|
||||
"zod": "^3.25.76"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.9.4",
|
||||
"@tailwindcss/postcss": "^4.1.11",
|
||||
"@types/node": "^20.19.4",
|
||||
"@types/node": "^20.19.9",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"drizzle-kit": "^0.30.6",
|
||||
"postcss": "^8.5.6",
|
||||
"tailwindcss": "^4.1.11",
|
||||
"tw-animate-css": "^1.3.4",
|
||||
"tw-animate-css": "^1.3.6",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"ct3aMetadata": {
|
||||
|
||||
1597
pnpm-lock.yaml
generated
1597
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
import Link from "next/link";
|
||||
import { Sidebar, SidebarContent, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem, SidebarProvider, SidebarTrigger } from "~/components/ui/sidebar";
|
||||
import SimpleSidebarGroup from "~/components/ui/simple-sidebar-group";
|
||||
|
||||
export default async function AdminSideBar() {
|
||||
return (
|
||||
@@ -8,63 +9,19 @@ export default async function AdminSideBar() {
|
||||
<Sidebar className="z-[51]">
|
||||
<SidebarTrigger className="absolute z-[52] left-65 top-100" />
|
||||
<SidebarContent>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>
|
||||
CV
|
||||
</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<SimpleSidebarGroup lable="CV">
|
||||
<Link href={"/admin/cv/category/create"}> Create Category </Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href={"/admin/cv/entry/create"}> Create Entry </Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href={"/admin/cv/category/list"}> Category List </Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href={"/admin/cv/entry/list"}> Entry List </Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>
|
||||
Projects
|
||||
</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
</SimpleSidebarGroup>
|
||||
<SimpleSidebarGroup lable="Projects">
|
||||
<Link href={"/admin/project/create"}> Create Project </Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>
|
||||
Blog
|
||||
</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton asChild>
|
||||
<Link href={"/admin/project/techStack/create"}> Create Stack </Link>
|
||||
</SimpleSidebarGroup>
|
||||
<SimpleSidebarGroup lable="Blog">
|
||||
<Link href={"/"}> Some Blog Action </Link>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
</SimpleSidebarGroup>
|
||||
</SidebarContent>
|
||||
</Sidebar>
|
||||
</SidebarProvider>
|
||||
|
||||
@@ -7,6 +7,6 @@ export default async function Page({params}:{params: Promise<{id:string}>}) {
|
||||
console.log(params)
|
||||
const {id} = await params;
|
||||
return (
|
||||
<UpdateCvCategoryForm id={id} className={undefined}/>
|
||||
<UpdateCvCategoryForm id={id} />
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { CategoryRouterOutputs } from "~/server/routers/cv/category";
|
||||
import { type Element } from "~/lib/utils";
|
||||
import UpdateCvCategoryForm from "./UpdateForm";
|
||||
import CreateCvCategoryForm from "./CreateForm";
|
||||
export default function CollapsibleCvCategoryForm(params:{category:Element<CategoryRouterOutputs['list']>|undefined}) {
|
||||
export default function CollapsibleCvCategoryForm(params:{category?:Element<CategoryRouterOutputs['list']>}) {
|
||||
return (
|
||||
<Collapsible >
|
||||
<CollapsibleTrigger asChild>
|
||||
|
||||
@@ -9,20 +9,35 @@ import { Select, SelectContent, SelectTrigger, SelectItem, SelectValue } from "~
|
||||
import { trpc } from "~/app/_trpc/Client";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import * as Card from '~/components/ui/card'
|
||||
|
||||
export default function CreateCvCategoryForm(params:{className:string|undefined}) {
|
||||
const form = useForm<z.infer<typeof insertSchema>>({
|
||||
resolver: zodResolver(insertSchema),
|
||||
import type { IterableElement } from 'type-fest'
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import { entitySchemas } from "~/lib/utils";
|
||||
import type { RouterOutputs } from "~/server/routers/_app";
|
||||
export default function CreateCvCategoryForm(params:{className?:string,category?:IterableElement<RouterOutputs['cv']['categoryv2']['select']>}) {
|
||||
const schemas = entitySchemas('cvCategory')
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const form = useForm<z.infer<typeof schemas.insert>>({
|
||||
resolver: zodResolver(schemas.insert),
|
||||
defaultValues: {
|
||||
id: crypto.randomUUID(),
|
||||
layoutPosition: "col1"
|
||||
}
|
||||
})
|
||||
const mutation = trpc.cv.category.create.useMutation()
|
||||
function onSubmit(values: z.infer<typeof insertSchema>) {
|
||||
mutation.mutate(values)
|
||||
form.setValue("id",crypto.randomUUID())
|
||||
const createMutation = trpc.cv.categoryv2.insert.useMutation()
|
||||
function onSubmit(values: z.infer<typeof schemas.insert>) {
|
||||
const res = createMutation.mutate(values)
|
||||
}
|
||||
const updateMutation = trpc.cv.categoryv2.update.useMutation()
|
||||
const deleteMutation = trpc.cv.categoryv2.delete.useMutation({
|
||||
onSuccess: () => {
|
||||
if (pathname.includes('list')) {
|
||||
router.refresh()
|
||||
} else {
|
||||
router.back()
|
||||
}
|
||||
}
|
||||
})
|
||||
return (
|
||||
<Card.Card className={params.className ? params.className : "w-5/6 lg:w-1/2"}>
|
||||
<Card.CardHeader>
|
||||
@@ -76,8 +91,8 @@ export default function CreateCvCategoryForm(params:{className:string|undefined}
|
||||
>
|
||||
</FormField>
|
||||
<Button type="submit"> Create </Button>
|
||||
<FormMessage className={mutation.status == "success" ? "text-green-500" : "text-red-500"}>
|
||||
{mutation.error ? mutation.error.message : mutation.status}
|
||||
<FormMessage className={createMutation.status == "success" ? "text-green-500" : "text-red-500"}>
|
||||
{createMutation.error ? createMutation.error.message : createMutation.status}
|
||||
</FormMessage>
|
||||
</form>
|
||||
</Form>
|
||||
|
||||
@@ -12,11 +12,10 @@ import * as Card from '~/components/ui/card'
|
||||
import { useRouter, usePathname } from "next/navigation";
|
||||
import { Delete } from "lucide-react";
|
||||
import { cn } from "~/lib/utils";
|
||||
export default function UpdateCvCategoryForm(params: { id: string, className: string | undefined }) {
|
||||
export default function UpdateCvCategoryForm(params: { id: string, className?: string }) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const id = params.id;
|
||||
console.log(id)
|
||||
const category = trpc.cv.category.get.useQuery({ id: id })
|
||||
const form = useForm<z.infer<typeof updateRouteSchema>>({
|
||||
resolver: zodResolver(updateRouteSchema),
|
||||
|
||||
@@ -41,21 +41,21 @@ export default function CvPage() {
|
||||
cat.cvEntry.length > 0 ? (
|
||||
<>
|
||||
{cat.cvEntry.map((entry) => (
|
||||
<CollapsibleCvEntryForm key={entry.id} entry={entry} categoryId={undefined} />
|
||||
<CollapsibleCvEntryForm key={entry.id} entry={entry}/>
|
||||
))}
|
||||
</>
|
||||
) : (<></>)
|
||||
}
|
||||
</div>
|
||||
<div className="flex flex-col w-full">
|
||||
<CollapsibleCvEntryForm entry={undefined} categoryId={cat.id} />
|
||||
<CollapsibleCvEntryForm categoryId={cat.id} />
|
||||
</div>
|
||||
</div>
|
||||
</Card.CardContent>
|
||||
</Card.Card>
|
||||
)
|
||||
})}
|
||||
<CollapsibleCvCategoryForm category={undefined} />
|
||||
<CollapsibleCvCategoryForm />
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { CategoryRouterOutputs } from "~/server/routers/cv/category";
|
||||
import { type Element } from "~/lib/utils";
|
||||
import UpdateCvEntryForm from "./UpdateForm";
|
||||
import CreateCvEntryForm from "./CreateForm";
|
||||
export default function CollapsibleCvEntryForm(params:{entry:Element<Element<CategoryRouterOutputs['list']>['cvEntry']>|EntryRouterOutputs['get']|Element<EntryRouterOutputs['list']>|undefined, categoryId: string|undefined}) {
|
||||
export default function CollapsibleCvEntryForm(params:{entry?:Element<Element<CategoryRouterOutputs['list']>['cvEntry']>|EntryRouterOutputs['get']|Element<EntryRouterOutputs['list']>, categoryId?: string}) {
|
||||
return (
|
||||
<Collapsible>
|
||||
<CollapsibleTrigger asChild>
|
||||
|
||||
@@ -13,7 +13,7 @@ import type { Entries } from 'type-fest'
|
||||
import { type Element } from "~/lib/utils";
|
||||
import type { ProjectRouterOutputs } from "~/server/routers/project";
|
||||
import { Suspense } from "react";
|
||||
export default function CreateUpdateProjectForm(params:{className:string|undefined, project:Element<ProjectRouterOutputs['list']>|undefined}) {
|
||||
export default function CreateUpdateProjectForm(params:{className?:string, project?:Element<ProjectRouterOutputs['list']>}) {
|
||||
const [techStacks,] = trpc.project.stack.list.useSuspenseQuery()
|
||||
const form = useForm<z.infer<typeof insertSchema>>({
|
||||
resolver: zodResolver(insertSchema),
|
||||
@@ -26,8 +26,8 @@ export default function CreateUpdateProjectForm(params:{className:string|undefin
|
||||
})
|
||||
const updateMutation = trpc.project.update.useMutation({
|
||||
onSuccess: (data) => {
|
||||
if (null !== data) {
|
||||
let entries = Object.entries(data) as Entries<typeof data>
|
||||
if (data.length > 0 && data[0] !== undefined) {
|
||||
let entries = Object.entries(data[0]) as Entries<typeof data[0]>
|
||||
entries.forEach( (entry) => {
|
||||
form.setValue(entry[0],entry[1])
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
import CreateUpdateProjectForm from "../_components/CreateForm";
|
||||
import CreateUpdateProjectForm from "../_components/CreateUpdateProjectForm";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<CreateUpdateProjectForm className="" project={undefined}/>
|
||||
<CreateUpdateProjectForm />
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ChevronsUpDown, Plus } from "lucide-react"
|
||||
import { Button } from "~/components/ui/button";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "~/components/ui/collapsible";
|
||||
import { type Element } from "~/lib/utils";
|
||||
import CreateUpdateProjectForm from "./CreateForm";
|
||||
import type { ProjectRouterOutputs } from "~/server/routers/project";
|
||||
export default function CollapsibleForm(params:{project:Element<ProjectRouterOutputs['list']>|undefined}) {
|
||||
return (
|
||||
<Collapsible >
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button variant={"ghost"}>
|
||||
{ params.project ?
|
||||
<>{params.project.title} <ChevronsUpDown/></> : <>New <Plus/></>
|
||||
}
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
<CollapsibleContent className="autoAlpha">
|
||||
{
|
||||
params.project ?
|
||||
<CreateUpdateProjectForm className="w-full" project={params.project}/> :
|
||||
<CreateUpdateProjectForm className="w-full" project={params.project}/>
|
||||
|
||||
}
|
||||
</CollapsibleContent>
|
||||
</Collapsible>
|
||||
)
|
||||
}
|
||||
100
src/app/admin/project/techStack/_components/CreateUpdateForm.tsx
Normal file
100
src/app/admin/project/techStack/_components/CreateUpdateForm.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
"use client"
|
||||
import { insertSchema } from "~/lib/schema/project/techStack"
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { z } from "zod";
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "~/components/ui/form";
|
||||
|
||||
import { trpc } from "~/app/_trpc/Client";
|
||||
import { Button } from "~/components/ui/button";
|
||||
import * as Card from '~/components/ui/card'
|
||||
import type { Entries } from 'type-fest'
|
||||
import { type Element } from "~/lib/utils";
|
||||
import type { StackRouter, StackRouterOutputs } from "~/server/routers/project/techStack";
|
||||
import { stackItemEnum } from "~/server/db/schema";
|
||||
import { ToggleGroup, ToggleGroupItem } from "~/components/ui/toggle-group";
|
||||
|
||||
export default function CreateUpdateForm(params:{className:string|undefined, techStack?:Element<StackRouterOutputs['list']>}) {
|
||||
const form = useForm<z.infer<typeof insertSchema>>({
|
||||
resolver: zodResolver(insertSchema),
|
||||
defaultValues: {
|
||||
id: params.techStack ? params.techStack.id : crypto.randomUUID(),
|
||||
stackItems: params.techStack ? params.techStack.stackItems : [],
|
||||
}
|
||||
})
|
||||
const createMutation = trpc.project.stack.create.useMutation({
|
||||
onSuccess: (data) => { form.setValue("id", data[0] ? data[0].id : "") }
|
||||
})
|
||||
const updateMutation = trpc.project.stack.update.useMutation({
|
||||
onSuccess: (data) => {
|
||||
if (data.length > 0 && data[0] !== undefined) {
|
||||
let entries = Object.entries(data[0]) as Entries<typeof data[0]>
|
||||
entries.forEach( (entry) => {
|
||||
form.setValue(entry[0],entry[1])
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
function onSubmit(values: z.infer<typeof insertSchema>) {
|
||||
params.techStack ?
|
||||
updateMutation.mutate({by: {id: values.id}, update: { ...values}}) :
|
||||
createMutation.mutate(values)
|
||||
}
|
||||
return (
|
||||
<Card.Card className={params.className ? params.className : "w-5/6 lg:w-1/2"}>
|
||||
<Card.CardHeader>
|
||||
<Card.CardTitle>
|
||||
{params.techStack ? "Update" : "Create"} Tech Stack
|
||||
</Card.CardTitle>
|
||||
</Card.CardHeader>
|
||||
<Card.CardContent>
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-8"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="stackItems"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Stack Items
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<ToggleGroup
|
||||
variant="outline"
|
||||
type="multiple"
|
||||
onValueChange={field.onChange}
|
||||
className="flex flex-wrap"
|
||||
>
|
||||
{stackItemEnum.enumValues.map((v) => {
|
||||
return (
|
||||
<ToggleGroupItem className="w-fit" key={v} value={v}>
|
||||
{v}
|
||||
</ToggleGroupItem>
|
||||
)
|
||||
})}
|
||||
</ToggleGroup>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button type="submit"> {params.techStack ? "Update" : "Create"} </Button>
|
||||
<FormMessage className={updateMutation.status == "success" || createMutation.status == "success" ? "text-green-500" : "text-red-500"}>
|
||||
{ params.techStack ?
|
||||
(
|
||||
<>{updateMutation.error ? updateMutation.error.message : updateMutation.status}</>
|
||||
) :
|
||||
(
|
||||
<>{createMutation.error ? createMutation.error.message : createMutation.status}</>
|
||||
)
|
||||
|
||||
}
|
||||
</FormMessage>
|
||||
</form>
|
||||
</Form>
|
||||
</Card.CardContent>
|
||||
</Card.Card>
|
||||
)
|
||||
}
|
||||
7
src/app/admin/project/techStack/create/page.tsx
Normal file
7
src/app/admin/project/techStack/create/page.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import CreateUpdateForm from "../_components/CreateUpdateForm";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<CreateUpdateForm className=""/>
|
||||
)
|
||||
}
|
||||
24
src/components/ui/simple-sidebar-group.tsx
Normal file
24
src/components/ui/simple-sidebar-group.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "./sidebar";
|
||||
export default function SimpleSidebarGroup(params: { lable: string, children: ReactNode|ReactNode[] }) {
|
||||
return (
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>
|
||||
{params.lable}
|
||||
</SidebarGroupLabel>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{ (params.children instanceof Array) ? params.children.map((n) => {
|
||||
return (
|
||||
<SidebarMenuItem key={crypto.randomUUID()}>
|
||||
<SidebarMenuButton asChild>
|
||||
{n}
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
)
|
||||
}) : <>{params.children}</> }
|
||||
</SidebarMenu>
|
||||
</SidebarGroupContent>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
@@ -1,8 +1,36 @@
|
||||
import { clsx, type ClassValue } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { createInsertSchema, createUpdateSchema, createSelectSchema, type BuildSchema } from 'drizzle-zod'
|
||||
import * as schema from "~/server/db/schema";
|
||||
import { PgColumn, PgTable } from "drizzle-orm/pg-core";
|
||||
import type { Entries } from "type-fest";
|
||||
import z from "zod";
|
||||
import type { Prettify } from "node_modules/zod/v4/core/util.cjs";
|
||||
import type { ExtractTablesWithRelations } from "drizzle-orm";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
}
|
||||
|
||||
export type Element<T extends Iterable<any>|undefined|null> = T extends Iterable<infer E> ? E : never;
|
||||
export type Element<T extends Iterable<any> | undefined | null> = T extends Iterable<infer E> ? E : never;
|
||||
|
||||
export type Schema = typeof schema;
|
||||
|
||||
export type SchemaKeys<S> = {
|
||||
[K in keyof S]: S[K] extends PgTable ? K : never
|
||||
}[keyof S]
|
||||
|
||||
export function entitySchemas<T extends SchemaKeys<Schema>>(table: T): {
|
||||
insert: BuildSchema<'insert', Schema[T]['_']['columns'], undefined>
|
||||
update: BuildSchema<'update', Schema[T]['_']['columns'], undefined>
|
||||
select: BuildSchema<'select', Schema[T]['_']['columns'], undefined>
|
||||
} {
|
||||
const insertSchema = createInsertSchema<Schema[T]>(schema[table]);
|
||||
const updateSchema = createUpdateSchema<Schema[T]>(schema[table]);
|
||||
const selectSchema = createSelectSchema<Schema[T]>(schema[table]);
|
||||
return {
|
||||
insert: insertSchema,
|
||||
update: updateSchema,
|
||||
select: selectSchema,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// https://orm.drizzle.team/docs/sql-schema-declaration
|
||||
|
||||
import { relations, sql } from "drizzle-orm";
|
||||
import { index, pgEnum, pgTableCreator } from "drizzle-orm/pg-core";
|
||||
import { index, pgEnum, pgSchema, pgTableCreator } from "drizzle-orm/pg-core";
|
||||
|
||||
/**
|
||||
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
|
||||
@@ -63,7 +63,9 @@ export const project = createTable(
|
||||
id: d.uuid().primaryKey().notNull(),
|
||||
title: d.varchar({length: 50}).notNull(),
|
||||
sourceType: sourceTypeEnum(),
|
||||
sourceLink: d.varchar({length: 200}),
|
||||
releaseStatus: releaseStatus(),
|
||||
releaseLink: d.varchar({length: 200}),
|
||||
stackId: d.uuid(),
|
||||
})
|
||||
)
|
||||
|
||||
136
src/server/lib.ts
Normal file
136
src/server/lib.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import type { Entries } from "type-fest";
|
||||
import { publicProcedure, router } from "~/server/trpc";
|
||||
import { db } from "~/server/db";
|
||||
import * as schema from "~/server/db/schema";
|
||||
import { isAdmin } from "~/app/actions";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { entitySchemas, type Schema, type SchemaKeys } from "~/lib/utils";
|
||||
import { eq, and } from "drizzle-orm";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function isKeyOf<T extends object>(k: any, obj: T): k is keyof T {
|
||||
return k in obj
|
||||
}
|
||||
|
||||
export function trpcCrudRouterFromDrizzleEntity<T extends SchemaKeys<Schema>>(table: T) {
|
||||
const schemas = entitySchemas<T>(table)
|
||||
if (!isKeyOf(table, schema)) {
|
||||
throw new Error('table not found')
|
||||
}
|
||||
let r = router({
|
||||
select: publicProcedure.input(schemas.update).query(async (opts) => {
|
||||
const { input } = opts;
|
||||
if (input === undefined) {
|
||||
return await db.select().from(schema[table]).execute();
|
||||
}
|
||||
const conds = (Object.entries(input) as Entries<typeof input>).map(([k, v]) => {
|
||||
if (!isKeyOf(k, schema[table])) {
|
||||
throw new TRPCError({ message: "Invalid key for column", code: "BAD_REQUEST" });
|
||||
}
|
||||
return eq(schema[table][k], v)
|
||||
})
|
||||
if (conds.length > 0) {
|
||||
return await db.select().from(schema[table]).where(and(...conds)).execute();
|
||||
}
|
||||
return await db.select().from(schema[table]).execute();
|
||||
}),
|
||||
update: publicProcedure.input(schemas.update).mutation(async (opts) => {
|
||||
let admin = await isAdmin();
|
||||
if (!admin) {
|
||||
throw new TRPCError(
|
||||
{ message: "Access denied", code: "FORBIDDEN", }
|
||||
)
|
||||
}
|
||||
const { input } = opts;
|
||||
if (input === undefined) {
|
||||
throw new TRPCError({ message: "no update input", code: "BAD_REQUEST" })
|
||||
}
|
||||
const conds = (Object.entries(input) as Entries<typeof input>).map(([k, v]) => {
|
||||
if (!isKeyOf(k, schema[table])) {
|
||||
throw new TRPCError({ message: "Invalid key for column", code: "BAD_REQUEST" });
|
||||
}
|
||||
return eq(schema[table][k], v)
|
||||
})
|
||||
if (conds.length > 0) {
|
||||
return await db.update(schema[table]).set(input).where(and(...conds)).returning().execute();
|
||||
}
|
||||
throw new TRPCError({ message: "trying to update all entities", code: "BAD_REQUEST" })
|
||||
}),
|
||||
insert: publicProcedure.input(schemas.insert).mutation(async (opts) => {
|
||||
let admin = await isAdmin();
|
||||
if (!admin) {
|
||||
throw new TRPCError(
|
||||
{ message: "Access denied", code: "FORBIDDEN", }
|
||||
)
|
||||
}
|
||||
const { input } = opts;
|
||||
return await db.insert(schema[table]).values(input).returning().execute();
|
||||
}),
|
||||
delete: publicProcedure.input(schemas.update).mutation(async (opts) => {
|
||||
let admin = await isAdmin();
|
||||
if (!admin) {
|
||||
throw new TRPCError(
|
||||
{ message: "Access denied", code: "FORBIDDEN", }
|
||||
)
|
||||
}
|
||||
const { input } = opts;
|
||||
const inputEntrie = Object.entries(input) as Entries<typeof input>;
|
||||
const conds = (Object.entries(input) as Entries<typeof input>).map(([k, v]) => {
|
||||
if (!isKeyOf(k, schema[table])) {
|
||||
throw new TRPCError({ message: "Invalid key for column", code: "BAD_REQUEST" });
|
||||
}
|
||||
return eq(schema[table][k], v)
|
||||
})
|
||||
if (conds.length > 0) {
|
||||
return await db.update(schema[table]).where(and(...conds)).returning().execute();
|
||||
}
|
||||
throw new TRPCError({ message: "trying to delete all entities", code: "BAD_REQUEST" })
|
||||
}),
|
||||
// list: publicProcedure.input(z.optional(z.object({
|
||||
// orderBy: selectSchema.keyof(),
|
||||
// direction: z.enum(["asc","desc"])
|
||||
// }))).query(async (opts) => {
|
||||
// const { input } = opts;
|
||||
// const query = db.select().from(schema[table])
|
||||
// if (input !== undefined) {
|
||||
// switch (input.direction) {
|
||||
// case "asc" :
|
||||
// query.orderBy(asc(schema[table]['_']['columns'][input.orderBy]));
|
||||
// case "desc" :
|
||||
// query.orderBy(desc(schema[table]['_']['columns'][input.orderBy]));
|
||||
// }
|
||||
// }
|
||||
// return await query.execute()
|
||||
// }),
|
||||
// get: publicProcedure.input(z.optional(insertSchema)).query(async (opts) => {
|
||||
// const { input } = opts
|
||||
// return await db.select().from(schema[table]).where(eq(schema[table]['_']['columns']['id'], input)).limit(1)
|
||||
// }),
|
||||
// delete: publicProcedure.input(z.string()).query(async (opts) => {
|
||||
// const { input } = opts
|
||||
// let admin = await isAdmin();
|
||||
// if (!admin) {
|
||||
// throw new TRPCError(
|
||||
// { message: "Access denied", code: "FORBIDDEN", }
|
||||
// )
|
||||
// }
|
||||
// return await db.delete(schema[table]).where(eq(schema[table]['_']['columns']['id'], input)).returning()
|
||||
// }),
|
||||
// create: publicProcedure.input(insertSchema).mutation(async (opts) => {
|
||||
// let admin = await isAdmin();
|
||||
// if (!admin) {
|
||||
// throw new TRPCError(
|
||||
// { message: "Access denied", code: "FORBIDDEN", }
|
||||
// )
|
||||
// }
|
||||
// const { input } = opts;
|
||||
// return await db.insert(schema[table]).values(input).returning().execute();
|
||||
// })
|
||||
});
|
||||
return { router: r };
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { router } from "~/server/trpc"
|
||||
import { CategoryRouter } from "./category"
|
||||
import { EntryRouter } from "./entry"
|
||||
import { trpcCrudRouterFromDrizzleEntity } from "~/server/lib"
|
||||
import * as schema from '~/server/db/schema'
|
||||
|
||||
const { router : categoryv2 } = trpcCrudRouterFromDrizzleEntity('cvCategory')
|
||||
const { router : entryv2 } = trpcCrudRouterFromDrizzleEntity('cvEntry')
|
||||
export const CvRouter = router({
|
||||
category: CategoryRouter,
|
||||
entry: EntryRouter
|
||||
category: categoryv2,
|
||||
entry:entryv2
|
||||
})
|
||||
|
||||
@@ -7,6 +7,9 @@ import { isAdmin } from "~/app/actions";
|
||||
import z from "zod";
|
||||
import { StackRouter } from "./techStack";
|
||||
import { TRPCError, type inferRouterOutputs} from "@trpc/server";
|
||||
import { trpcCrudRouterFromDrizzleEntity } from "~/server/lib";
|
||||
const { router : project } = trpcCrudRouterFromDrizzleEntity('project')
|
||||
const { router : techStack } = trpcCrudRouterFromDrizzleEntity('techStack')
|
||||
export const ProjectRouter = router({
|
||||
list: publicProcedure.query(async () => {
|
||||
return await db.query.project.findMany({
|
||||
@@ -61,15 +64,10 @@ export const ProjectRouter = router({
|
||||
)
|
||||
}
|
||||
const { input } = opts;
|
||||
const updateProj = await db.update(project)
|
||||
return await db.update(project)
|
||||
.set(input.update)
|
||||
.returning()
|
||||
.where(eq(project.id,input.by.id));
|
||||
if (updateProj[0] !== undefined) {
|
||||
return updateProj[0]
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}),
|
||||
stack: StackRouter,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user