From d8b4c673f9171353a60e83b19da9f5f6420057e0 Mon Sep 17 00:00:00 2001 From: Rufat Niftaliyev Date: Sun, 23 Nov 2025 20:52:00 -0600 Subject: [PATCH 1/4] In preparation of deployment and config update, and some optimisation --- .env.example | 46 +- Dockerfile | 52 + apps/web/.gitignore | 3 +- apps/web/package.json | 41 +- apps/web/src/app/admin/points/page.tsx | 7 - .../src/app/admin/toggles/tickets/page.tsx | 3 - apps/web/src/app/admin/users/[slug]/page.tsx | 6 + apps/web/src/app/api/trpc/[trpc]/route.ts | 34 - apps/web/src/app/dash/layout.tsx | 102 +- apps/web/src/app/dash/tickets/layout.tsx | 14 - apps/web/src/app/dash/tickets/new/page.tsx | 60 - apps/web/src/app/dash/tickets/page.tsx | 7 - apps/web/src/app/globals.css | 79 +- apps/web/src/app/layout.tsx | 14 +- apps/web/src/components/Restricted.tsx | 10 +- .../admin/users/UpdateRoleDialog.tsx | 7 +- .../src/components/dash/main/team/NewTeam.tsx | 0 .../components/dash/team/LeaveTeamButton.tsx | 0 apps/web/src/components/dash/team/invite.tsx | 0 .../components/dash/team/inviteListing.tsx | 0 .../components/dash/tickets/TicketList.tsx | 43 - .../components/events/admin/EditEventForm.tsx | 8 +- apps/web/src/components/schedule/Day.tsx | 4 - .../src/emails/RegistrationSuccessEmail.tsx | 114 - apps/web/src/env.ts | 5 - apps/web/src/lib/utils/client/zfetch.ts | 107 - apps/web/src/lib/utils/server/redis.ts | 2 +- apps/web/src/lib/utils/server/ses.ts | 47 - apps/web/src/lib/utils/server/types.ts | 4 - apps/web/src/middleware.ts | 2 +- apps/web/src/server/api/root.ts | 23 - apps/web/src/server/api/routers/post.ts | 31 - apps/web/src/server/api/routers/tickets.ts | 63 - apps/web/src/server/api/trpc.ts | 100 - apps/web/src/trpc/query-client.ts | 25 - apps/web/src/trpc/react.tsx | 76 - apps/web/src/trpc/server.ts | 30 - package.json | 3 +- packages/config/constants.ts | 818 ++ packages/config/hackckit.deploy.ts | 54 + packages/config/hackkit.config.ts | 935 +-- packages/config/package.json | 5 +- packages/db/schema.ts | 227 +- packages/db/{ => seeders}/seed.ts | 9 +- pnpm-lock.yaml | 7320 ++++------------- turbo.json | 7 +- 46 files changed, 2819 insertions(+), 7728 deletions(-) create mode 100644 Dockerfile delete mode 100644 apps/web/src/app/admin/points/page.tsx delete mode 100644 apps/web/src/app/admin/toggles/tickets/page.tsx delete mode 100644 apps/web/src/app/api/trpc/[trpc]/route.ts delete mode 100644 apps/web/src/app/dash/tickets/layout.tsx delete mode 100644 apps/web/src/app/dash/tickets/new/page.tsx delete mode 100644 apps/web/src/app/dash/tickets/page.tsx delete mode 100644 apps/web/src/components/dash/main/team/NewTeam.tsx delete mode 100644 apps/web/src/components/dash/team/LeaveTeamButton.tsx delete mode 100644 apps/web/src/components/dash/team/invite.tsx delete mode 100644 apps/web/src/components/dash/team/inviteListing.tsx delete mode 100644 apps/web/src/components/dash/tickets/TicketList.tsx delete mode 100644 apps/web/src/emails/RegistrationSuccessEmail.tsx delete mode 100644 apps/web/src/lib/utils/client/zfetch.ts delete mode 100644 apps/web/src/lib/utils/server/ses.ts delete mode 100644 apps/web/src/server/api/root.ts delete mode 100644 apps/web/src/server/api/routers/post.ts delete mode 100644 apps/web/src/server/api/routers/tickets.ts delete mode 100644 apps/web/src/server/api/trpc.ts delete mode 100644 apps/web/src/trpc/query-client.ts delete mode 100644 apps/web/src/trpc/react.tsx delete mode 100644 apps/web/src/trpc/server.ts create mode 100644 packages/config/constants.ts create mode 100644 packages/config/hackckit.deploy.ts rename packages/db/{ => seeders}/seed.ts (70%) diff --git a/.env.example b/.env.example index ece81426..68b51259 100644 --- a/.env.example +++ b/.env.example @@ -1,15 +1,15 @@ -AWS_ACCESS_KEY_ID="43242s387fg44638s244fh24" -AWS_REGION="us-east-2" -AWS_S3_BUCKET="hackathon-backups" -AWS_S3_ENDPOINT="https://dfh7346398578dhgsds.r2.cloudflarestorage.com" -AWS_S3_REGION="auto" -AWS_SECRET_ACCESS_KEY="4258722gyrgejw78gfg2u20974fhihdfs02" -AWS_SES_ACCESS_KEY="ADEIAFHJ3HKFU48FHJK" -AWS_SES_EMAIL_FROM="no-reply@[your_organization].org" -AWS_SES_SECRET_ACCESS_KEY="AfBHKrDFh/Q03u+37iyGrwjFJ" -BACKUP_CRON_SCHEDULE="0 */6 * * *" -BACKUP_DATABASE_URL="postgres://default:RtN4q5qefsTO@ui-painted-hills-e5ddlb.us-east-2.aws.neon.tech:4576/verceldb?sslmode=require" -BLOB_READ_WRITE_TOKEN="vercel_blob_rw_fhb9DH2kOEIO3EOI_3dBKKFHBGDLNSjnjDOIJHF" +# AWS_ACCESS_KEY_ID="43242s387fg44638s244fh24" +# AWS_REGION="us-east-2" +# AWS_S3_BUCKET="hackathon-backups" +# AWS_S3_ENDPOINT="https://dfh7346398578dhgsds.r2.cloudflarestorage.com" +# AWS_S3_REGION="auto" +# AWS_SECRET_ACCESS_KEY="4258722gyrgejw78gfg2u20974fhihdfs02" +# AWS_SES_ACCESS_KEY="ADEIAFHJ3HKFU48FHJK" +# AWS_SES_EMAIL_FROM="no-reply@[your_organization].org" +# AWS_SES_SECRET_ACCESS_KEY="AfBHKrDFh/Q03u+37iyGrwjFJ" +# BACKUP_CRON_SCHEDULE="0 */6 * * *" +# BACKUP_DATABASE_URL="postgres://default:RtN4q5qefsTO@ui-painted-hills-e5ddlb.us-east-2.aws.neon.tech:4576/verceldb?sslmode=require" +# BLOB_READ_WRITE_TOKEN="vercel_blob_rw_fhb9DH2kOEIO3EOI_3dBKKFHBGDLNSjnjDOIJHF" BOT_API_URL="https://hackkit-actions-bots.up.railway.app" CLERK_SECRET_KEY="sk_test_AFNJKABF2uhkbDBUKAFAKBAHBj3r" CLOUDFLARE_ACCOUNT_ID="cdcdJB3453KKJFBDSBHA54546UIBF" @@ -19,21 +19,21 @@ DISCORD_DEV_VERIFY_CHANNEL_ID="52872856278050172988890" DISCORD_PROD_SERVER_ID="528728562780501729853789" DISCORD_PROD_VERIFY_CHANNEL_ID="5287378262780501729853789" DISCORD_SECRET_TOKEN="FHBAHJABDhbHSUeyi7398.S72ljd.HKBVAfug73gifa-74gfwiyB-BHJBDHV07hAJf83" -HK_ENV="development" +NODE_ENV="development" INTERNAL_AUTH_KEY="bwgybgidbsi-4784gyfgs-475hhkdsbfs-bwgybgidbsi-4784gyfgs-475hhkdsbfs-bwgybgidbsi-4784gyfgs-475hhkdsbfs-fbnauib4783-bhfbsjfbs" NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY="pk_test_YRGIBHSBIbabffjdhvbuYGI7BK" NEXT_PUBLIC_CLERK_SIGN_IN_URL="/sign-in" NEXT_PUBLIC_CLERK_SIGN_UP_URL="/sign-up" -PLUNK_API_KEY="sk_279y482gibfai7g74gikbga345" -PLUNK_API_URL="https://plunk.[your_org_name].org/api/[version_number]" -POSTGRES_DATABASE="verceldb" -POSTGRES_HOST="ep-rising-sky-adbkfahbeke-spooler.us-east-2.aws.neon.tech" -POSTGRES_PASSWORD="dkhjbIYBFABUI" -POSTGRES_PRISMA_URL="postgres://default:dkhjbIYBFABUI@ep-rising-sky-adbkfahbeke-spooler.us-east-2.aws.neon.tech/verceldb?pgbouncer=true&connect_timeout=15&sslmode=require" -POSTGRES_URL="postgres://default:dkhjbIYBFABUI@ep-rising-sky-adbkfahbeke-spooler.us-east-2.aws.neon.tech/verceldb?sslmode=require" -POSTGRES_URL_NO_SSL="postgres://default:dkhjbIYBFABUI@ep-rising-sky-adbkfahbeke-spooler.us-east-2.aws.neon.tech/verceldb" -POSTGRES_URL_NON_POOLING="postgres://default:dkhjbIYBFABUI@ep-rising-sky-adbkfahbeke-spooler.us-east-2.aws.neon.tech/verceldb?sslmode=require" -POSTGRES_USER="default" +# PLUNK_API_KEY="sk_279y482gibfai7g74gikbga345" +# PLUNK_API_URL="https://plunk.[your_org_name].org/api/[version_number]" +# POSTGRES_DATABASE="verceldb" +# POSTGRES_HOST="ep-rising-sky-adbkfahbeke-spooler.us-east-2.aws.neon.tech" +# POSTGRES_PASSWORD="dkhjbIYBFABUI" +# POSTGRES_PRISMA_URL="postgres://default:dkhjbIYBFABUI@ep-rising-pfahbeke-spooler.us-east-2.aws.neon.tech/verceldb?pgbouncer=true&connect_timeout=15&sslmode=require" +# POSTGRES_URL="postgres://default:dkhjbIYBFABUI@ep-rising-sky-adbkfahbeke-spooler.us-east-2.aws.neon.tech/verceldb?sslmode=require" +# POSTGRES_URL_NO_SSL="postgres://default:dkhjbIYBFABUI@ep-rising-sky-adbkfahbeke-spooler.us-east-2.aws.neon.tech/verceldb" +# POSTGRES_URL_NON_POOLING="postgres://default:dkhjbIYBFABUI@ep-rising-sky-adbkfahbeke-spooler.us-east-2.aws.neon.tech/verceldb?sslmode=require" +# POSTGRES_USER="default" R2_ACCESS_KEY_ID="1bfkhbfyiayi38uhidfis" R2_BUCKET_NAME="your-org-name-userdata" R2_SECRET_ACCESS_KEY="279y45g79tgbjsbfhbsiufhbs89hg487hsiufs" diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..b61abf50 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +# syntax=docker.io/docker/dockerfile:1 + +FROM node:20-alpine AS base +RUN corepack enable + +# Install dependencies only when needed +FROM base AS deps +# Alpine sometimes needs libc6-compat for some npm packages. +RUN apk add --no-cache libc6-compat +WORKDIR /app + +# Copy workspace manifests so pnpm can install only necessary packages +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml* .npmrc* ./ +RUN pnpm install --frozen-lockfile + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Disable Next.js telemetry during build +ENV NEXT_TELEMETRY_DISABLED=1 + +# Build only the web app from the pnpm workspace +RUN pnpm --filter web build + +# Production image, copy only the standalone output for `apps/web` +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production + +# Disable Next.js telemetry at runtime +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy public and standalone output from the workspace app +COPY --from=builder /app/apps/web/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +CMD ["node", "server.js"] \ No newline at end of file diff --git a/apps/web/.gitignore b/apps/web/.gitignore index c77e391b..867448ea 100644 --- a/apps/web/.gitignore +++ b/apps/web/.gitignore @@ -35,4 +35,5 @@ yarn-error.log* next-env.d.ts # react-email -.react-email/ \ No newline at end of file +.react-email/ +certificates \ No newline at end of file diff --git a/apps/web/package.json b/apps/web/package.json index 00f634f2..c92d4485 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -11,10 +11,8 @@ }, "dependencies": { "@aws-sdk/client-s3": "^3.750.0", - "@aws-sdk/client-ses": "^3.616.0", "@aws-sdk/s3-request-presigner": "^3.750.0", "@clerk/nextjs": "^6.12.12", - "@gsap/react": "^2.1.1", "@hookform/resolvers": "^3.9.0", "@internationalized/date": "^3.5.4", "@planetscale/database": "^1.18.0", @@ -32,22 +30,9 @@ "@react-email/components": "^0.0.21", "@t3-oss/env-nextjs": "^0.10.1", "@tanstack/match-sorter-utils": "^8.19.4", - "@tanstack/react-query": "^5.51.11", "@tanstack/react-table": "^8.19.3", - "@trpc/client": "11.0.0-rc.466", - "@trpc/next": "11.0.0-rc.466", - "@trpc/react-query": "11.0.0-rc.466", - "@trpc/server": "11.0.0-rc.466", - "@types/node": "20.14.11", - "@types/react": "18.3.3", - "@types/react-dom": "18.3.0", "@upstash/redis": "^1.34.4", - "@vercel/analytics": "^1.3.1", - "@vercel/blob": "^0.10.0", - "@vercel/postgres": "^0.9.0", "@yudiel/react-qr-scanner": "^2.1.0", - "autoprefixer": "10.4.19", - "axios": "^1.7.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "^1.0.0", @@ -55,32 +40,21 @@ "date-fns": "^3.6.0", "date-fns-tz": "^3.1.3", "db": "workspace:*", - "dotenv": "^16.4.5", "drizzle-orm": "^0.39.3", "drizzle-zod": "^0.7.0", "embla-carousel": "8.1.7", - "embla-carousel-autoplay": "8.1.7", "embla-carousel-react": "8.1.7", - "framer-motion": "^11.3.8", - "gsap": "^3.12.5", - "jiti": "^1.21.6", "lucide-react": "^0.411.0", "nanoid": "^5.0.7", "next": "14.2.25", "next-safe-action": "^7.9.3", "nextjs@4": "link:clerk/nextjs@4", "no-profanity": "^1.5.1", - "pg": "^8.12.0", - "postcss": "8.4.39", - "postgres": "^3.4.4", - "pusher-js": "8.4.0-rc2", "react": "18.3.1", "react-aria": "^3.33.1", "react-confetti": "^6.1.0", "react-dom": "18.3.1", "react-dropzone": "^14.2.3", - "react-email": "^2.1.5", - "react-fast-marquee": "^1.6.5", "react-hook-form": "7.52.1", "react-loader-spinner": "^6.1.6", "react-parallax-tilt": "^1.7.232", @@ -92,9 +66,6 @@ "sonner": "^1.5.0", "superjson": "^2.2.1", "tailwind-merge": "^2.4.0", - "tailwindcss": "3.4.6", - "tailwindcss-animate": "^1.0.7", - "typescript": "5.5.3", "use-debounce": "^10.0.1", "usehooks-ts": "^3.1.0", "vaul": "^0.9.1", @@ -102,6 +73,16 @@ }, "devDependencies": { "@cloudflare/next-on-pages": "^1.13.10", - "esbuild-register": "^3.5.0" + "@types/node": "20.14.11", + "@types/react": "18.3.3", + "@types/react-dom": "18.3.0", + "autoprefixer": "10.4.19", + "dotenv": "16.4.5", + "esbuild-register": "^3.5.0", + "jiti": "1.21.6", + "postcss": "8.4.39", + "tailwindcss": "3.4.6", + "tailwindcss-animate": "1.0.7", + "typescript": "5.5.3" } } diff --git a/apps/web/src/app/admin/points/page.tsx b/apps/web/src/app/admin/points/page.tsx deleted file mode 100644 index 3c7c79b3..00000000 --- a/apps/web/src/app/admin/points/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -export default function Points() { - return ( -
-

Coming soon...

-
- ); -} diff --git a/apps/web/src/app/admin/toggles/tickets/page.tsx b/apps/web/src/app/admin/toggles/tickets/page.tsx deleted file mode 100644 index c82873b7..00000000 --- a/apps/web/src/app/admin/toggles/tickets/page.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export default function Page() { - return

Coming soon

; -} diff --git a/apps/web/src/app/admin/users/[slug]/page.tsx b/apps/web/src/app/admin/users/[slug]/page.tsx index de613c26..ba125b2a 100644 --- a/apps/web/src/app/admin/users/[slug]/page.tsx +++ b/apps/web/src/app/admin/users/[slug]/page.tsx @@ -40,6 +40,10 @@ export default async function Page({ params }: { params: { slug: string } }) { return

User Not Found

; } + const roles = await db.query.roles.findMany({ + columns: { id: true, name: true }, + }); + const banInstance = await db.query.bannedUsers.findFirst({ where: eq(bannedUsers.userID, subject.clerkID), }); @@ -83,6 +87,7 @@ export default async function Page({ params }: { params: { slug: string } }) { name={`${subject.firstName} ${subject.lastName}`} currentRoleId={subject.role_id} userID={subject.clerkID} + roles={roles} /> @@ -146,6 +151,7 @@ export default async function Page({ params }: { params: { slug: string } }) { name={`${subject.firstName} ${subject.lastName}`} currentRoleId={subject.role_id} userID={subject.clerkID} + roles={roles} /> diff --git a/apps/web/src/app/api/trpc/[trpc]/route.ts b/apps/web/src/app/api/trpc/[trpc]/route.ts deleted file mode 100644 index b30be48a..00000000 --- a/apps/web/src/app/api/trpc/[trpc]/route.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; -import { type NextRequest } from "next/server"; - -import { env } from "@/env"; -import { appRouter } from "@/server/api/root"; -import { createTRPCContext } from "@/server/api/trpc"; - -/** - * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when - * handling a HTTP request (e.g. when you make requests from Client Components). - */ -const createContext = async (req: NextRequest) => { - return createTRPCContext({ - headers: req.headers, - }); -}; - -const handler = (req: NextRequest) => - fetchRequestHandler({ - endpoint: "/api/trpc", - req, - router: appRouter, - createContext: () => createContext(req), - onError: - env.NODE_ENV === "development" - ? ({ path, error }) => { - console.error( - `❌ tRPC failed on ${path ?? ""}: ${error.message}`, - ); - } - : undefined, - }); - -export { handler as GET, handler as POST }; diff --git a/apps/web/src/app/dash/layout.tsx b/apps/web/src/app/dash/layout.tsx index 5e85035c..fb838aad 100644 --- a/apps/web/src/app/dash/layout.tsx +++ b/apps/web/src/app/dash/layout.tsx @@ -8,8 +8,6 @@ import DashNavItem from "@/components/dash/shared/DashNavItem"; import { redirect } from "next/navigation"; import ProfileButton from "@/components/shared/ProfileButton"; import ClientToast from "@/components/shared/ClientToast"; - -import { TRPCReactProvider } from "@/trpc/react"; import { getUser } from "db/functions"; interface DashLayoutProps { @@ -35,60 +33,58 @@ export default async function DashLayout({ children }: DashLayoutProps) { return ( <> - - -
-
- - {c.hackathonName - + +
+
+ + {c.hackathonName + -
-

Dashboard

-
-
- - - - - - - - - - -
-
- -
+
+

Dashboard

+
+
+ + + + + + + + + +
-
- {Object.entries(c.dashPaths.dash).map(([name, path]) => ( - - ))} +
+
- {children} - +
+
+ {Object.entries(c.dashPaths.dash).map(([name, path]) => ( + + ))} +
+ {children} ); } diff --git a/apps/web/src/app/dash/tickets/layout.tsx b/apps/web/src/app/dash/tickets/layout.tsx deleted file mode 100644 index 57fa0115..00000000 --- a/apps/web/src/app/dash/tickets/layout.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import TicketList from "@/components/dash/tickets/TicketList"; - -export default function TicketsLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( -
- -
{children}
-
- ); -} diff --git a/apps/web/src/app/dash/tickets/new/page.tsx b/apps/web/src/app/dash/tickets/new/page.tsx deleted file mode 100644 index 82e0ddff..00000000 --- a/apps/web/src/app/dash/tickets/new/page.tsx +++ /dev/null @@ -1,60 +0,0 @@ -"use client"; -import { useState } from "react"; -import { Input } from "@/components/shadcn/ui/input"; -import { Label } from "@/components/shadcn/ui/label"; -import { Textarea } from "@/components/shadcn/ui/textarea"; -import { Button } from "@/components/shadcn/ui/button"; -import { api } from "@/trpc/react"; -import { toast } from "sonner"; - -export default function Page() { - const [title, setTitle] = useState(""); - const [description, setDescription] = useState(""); - - const createTicket = api.tickets.create.useMutation(); - - async function runCreateTicket() { - if (title.length > 3 && description.length > 10) { - const result = await createTicket.mutateAsync({ - title, - description, - }); - if (result.success) { - toast.success("Ticket created successfully!"); - console.log( - "created ticket with ID " + - result.ticketID + - " and chat with ID " + - result.chatID, - ); - } - } else { - toast.error( - "Your title or description is too short! Please try again.", - ); - } - } - - return ( -
-
-

New Ticket

-
-
- - setTitle(e.target.value)} /> -
-
- -