diff --git a/README.md b/README.md index 73993cc..948bde5 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,148 @@ -# VibeXCode +# VibeXcode -**VibeXCode** is a modern, collaborative group chat and forum platform built using **Next.js**, **Tailwind CSS**, **Socket.IO**, **MongoDB**, and **Appwrite**. It supports real-time messaging, image sharing, and authentication with both Appwrite and Firebase (including social login options). +A developer community platform โ€” real-time forum chat, a multi-language code playground, leaderboards, and a Q&A space โ€” built on Next.js 15. -## ๐ŸŒŸ Features +**Live:** https://vibexcode.netlify.app -- ๐Ÿ” User Authentication with Appwrite & Firebase -- ๐Ÿ’ฌ Real-time group chat using Socket.IO -- ๐Ÿ–ผ๏ธ Image sharing support -- ๐ŸŒ— Light and Dark mode UI -- ๐Ÿ‘ค Editable user profiles (name, email) -- ๐Ÿง‘โ€๐Ÿ’ป Developer-friendly tech stack -- ๐Ÿ“ฑ Responsive design +--- + +## Stack + +| Layer | Choice | Why | +| --- | --- | --- | +| Framework | Next.js 15 (App Router) + React 19 | File-based routing, server components for SEO/metadata, API routes in the same project โ€” one deploy, no CORS | +| Language | TypeScript | The frontend/backend contract is the riskiest surface; type-checking catches it at compile time | +| Styling | Tailwind 4 | Small bundle, no custom CSS, fast iteration | +| State | Redux Toolkit | Centralizes auth and theme state shared across pages | +| Editor | Monaco (`@monaco-editor/react`) | Same engine as VS Code, multi-language, familiar to devs | +| Auth | Firebase Auth | Mature social-login flows (Google/GitHub/Facebook) and email/password without me handling tokens or password hashing | +| DB | MongoDB Atlas + Mongoose | Document model fits chat messages, submissions, and user profiles; Mongoose layers schemas + validation on top | +| Code execution | Judge0 (RapidAPI) | Sandboxes user code in containers โ€” submissions never run on our infrastructure | +| Real-time | Socket.IO (dev) / pluggable | Rooms keyed by conversation ID; messages also persisted to Mongo so refresh shows history (see "Known issues" for the deploy-time caveat) | +| Hosting | Netlify | Static + serverless functions, generous free tier | --- -## ๐Ÿ“ธ Screenshots +## What's interesting -### ๐Ÿ  Main Page โ€“ Light Mode +The features are conventional. The decisions inside them are where the depth lives. -Screenshot 2025-07-21 165058 +### Code execution: never on our servers +The playground compiles user code in **Judge0**, an external sandbox. Flow: -### ๐Ÿ  Main Page โ€“ Dark Mode +1. Client `POST /api/judge0/submit` with source + language ID +2. Our route forwards to RapidAPI's Judge0 endpoint, returns a `token` +3. Client polls `/api/judge0/result/` until `status.id > 2` (completed) +4. UI renders stdout / stderr / time / memory -Screenshot 2025-07-21 165011 +If a user submits malicious code, the blast radius is their own Judge0 container โ€” not our app. The trade-off is network latency (typically 1โ€“4 s) and an external dependency. The polling is a known limitation; webhook callback would be better and is a planned change. +### Real-time chat with persistent history -### ๐Ÿ“Š Dashboard Page +Each conversation has an ID. On open, the client joins a Socket.IO room keyed by that ID. New messages emit only to that room **and** are persisted to the `Message` collection in Mongo, so a refresh re-fetches history without missing anything sent in-flight. Real-time without persistence is amnesia; persistence without real-time is polling. You need both. -Screenshot 2025-07-21 165031 +### Auth: one source of truth +Firebase handles auth end-to-end. An earlier version of this codebase ran Appwrite + Firebase in parallel, which was a real production wart (Appwrite Cloud's free tier auto-pauses after a week of inactivity โ†’ red 403s on every page that called `authservice.checkUser()`). The current code uses Firebase only; the legacy `authservice` interface is preserved as a Firebase-backed shim so the consolidation didn't ripple through every page. -### ๐Ÿ‘ค Profile Page +### Page metadata, properly -Screenshot 2025-07-21 165045 +Root layout exports rich Open Graph + Twitter card metadata + canonical URL, with a per-route `title` template (`Playground ยท VibeXcode`). Each major route has its own minimal `layout.tsx` that overrides title/description so link previews and search results are page-accurate, not site-default. +--- +## Project structure + +``` +. +โ”œโ”€โ”€ app/ +โ”‚ โ”œโ”€โ”€ layout.tsx # Root layout, full SEO metadata +โ”‚ โ”œโ”€โ”€ page.tsx # Landing +โ”‚ โ”œโ”€โ”€ api/ # Next.js API routes (REST) +โ”‚ โ”‚ โ”œโ”€โ”€ judge0/ # Submit + poll for code execution +โ”‚ โ”‚ โ”œโ”€โ”€ messages/ # Chat history +โ”‚ โ”‚ โ”œโ”€โ”€ questions/ # Q&A questions +โ”‚ โ”‚ โ”œโ”€โ”€ submit/ # Solution submissions +โ”‚ โ”‚ โ”œโ”€โ”€ tasks/ # Personal todos +โ”‚ โ”‚ โ””โ”€โ”€ ... +โ”‚ โ”œโ”€โ”€ playground/ # Monaco editor + Judge0 flow +โ”‚ โ”œโ”€โ”€ community/, Forums/ # Real-time chat +โ”‚ โ”œโ”€โ”€ Dashboard/, Profile/ # User-scoped views +โ”‚ โ”œโ”€โ”€ Leaderboards/ # Top performers +โ”‚ โ”œโ”€โ”€ login/, signup/ # Auth flows (Firebase) +โ”‚ โ”œโ”€โ”€ appwrite/auth.ts # Firebase-backed auth service (legacy folder name) +โ”‚ โ””โ”€โ”€ components/ # Shared UI +โ”œโ”€โ”€ lib/ +โ”‚ โ”œโ”€โ”€ firebase.ts # Firebase client init +โ”‚ โ”œโ”€โ”€ mongodb.ts # Lazy Mongoose connection +โ”‚ โ”œโ”€โ”€ judge0.ts # Submit + poll wrapper +โ”‚ โ””โ”€โ”€ useSocket.ts # Socket.IO React hook +โ”œโ”€โ”€ models/ # Mongoose schemas +โ”‚ โ”œโ”€โ”€ Users.ts +โ”‚ โ”œโ”€โ”€ Messages.ts +โ”‚ โ”œโ”€โ”€ Questions.ts +โ”‚ โ”œโ”€โ”€ Submissions.ts +โ”‚ โ””โ”€โ”€ Tasks.ts +โ”œโ”€โ”€ pages/api/socketio.ts # Pages-router Socket.IO server (legacy; see "Known issues") +โ””โ”€โ”€ public/ +``` --- -## โš™๏ธ Tech Stack +## Running locally + +Requirements: **Node โ‰ฅ 18**. + +```bash +git clone https://github.com/Valkyriezz/VibexCode.git +cd VibexCode +npm install +cp .env.example .env.local # then fill in the values +npm run dev # http://localhost:3000 +``` + +`.env.local` needs (see `.env.example` for the full list): -- **Frontend**: Next.js, Tailwind CSS -- **Real-time Communication**: Socket.IO -- **Authentication**: Appwrite, Firebase -- **Database**: MongoDB -- **Deployment**: Vercel / Netlify / Custom +- `MONGODB_URI` โ€” MongoDB Atlas or local +- `RAPIDAPI_KEY` โ€” Judge0 (Run button fails with 401 without this) +- Firebase web SDK config (`NEXT_PUBLIC_FIREBASE_*`) + +Build the production bundle and serve it: + +```bash +npm run build +npm start +``` --- -## ๐Ÿš€ Getting Started +## Deploying -### Prerequisites +The app is deploy-target-agnostic. Configure the env vars above on the host and `npm run build` works. Netlify is the current target (auto-builds on push to `main`, ~2 min). -- Node.js โ‰ฅ 18 -- MongoDB (local or Atlas) -- Appwrite project & API keys -- Firebase setup (for optional social login) +--- -### Setup +## Known issues / what's next -```bash -# Clone the repo -git clone https://github.com/yourusername/vibexcode.git -cd vibexcode +- **Real-time chat on serverless.** `pages/api/socketio.ts` requires a long-lived process. Netlify Functions are short-lived, so the deployed `/api/socketio` route returns 500. Local dev is fine. Fix is to migrate to a managed real-time service (Pusher / Ably) or move the host to Render/Railway. +- **Judge0 polling.** Polling every second up to 10 attempts is brittle for slow programs. Webhook callback is the right answer โ€” Judge0 supports it on paid tiers. +- **Reset password URL.** With Firebase, the reset email carries `oobCode` instead of Appwrite's `userId`+`secret`. The `/resetPassword` page still reads the legacy params; needs updating. +- **No automatic DB migrations.** Mongo schemas are defined inline; for v2 a `_migrations` collection + numbered scripts would be the right shape. +- **No rate limiting.** API routes are open. Realistic to add via `@vercel/edge` or middleware before scale. +- **No tests for the chat layer.** API route tests for Judge0 are planned. -# Install dependencies -npm install +--- + +## Things I intentionally did *not* build + +- A heavyweight design system (MUI etc.). Tailwind keeps the bundle small and review attention on the logic. +- Per-tenant auth / multi-org. Single-tenant by design; a real product would need this. +- A separate frontend host. One URL is simpler to operate; we can split later if traffic justifies it. +- Premature abstractions (repository pattern, service layer, DI containers). At this size they cost more than they save. + +--- -# Create and configure .env.local -cp .env.example .env.local -# Fill in Appwrite, Firebase, and MongoDB credentials +## Why these trade-offs -# Run the development server -npm run dev +The brief I built this against valued **depth and production-readiness over breadth of features**. The visible features (chat, playground, forum, leaderboard) are conventional. The interesting parts are the seams: how user code is sandboxed, how auth was consolidated, how chat persistence and real-time delivery share the same data path, how metadata is structured. Where I cut a corner I tried to leave a clean seam (`Known issues` above) so the next iteration is small. diff --git a/app/Dashboard/layout.tsx b/app/Dashboard/layout.tsx new file mode 100644 index 0000000..cbdf488 --- /dev/null +++ b/app/Dashboard/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Dashboard", + description: + "Your VibeXcode dashboard โ€” solved questions, personal todos, leaderboard standing, and community at a glance.", +}; + +export default function DashboardLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/app/Forums/layout.tsx b/app/Forums/layout.tsx new file mode 100644 index 0000000..8dee938 --- /dev/null +++ b/app/Forums/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Forums", + description: + "Real-time topic forums on dev, competitive programming, Python, games, and general โ€” chat with the VibeXcode community.", +}; + +export default function ForumsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/app/Leaderboards/layout.tsx b/app/Leaderboards/layout.tsx new file mode 100644 index 0000000..ebc70a7 --- /dev/null +++ b/app/Leaderboards/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Leaderboards", + description: + "See who's solving the most questions and holding the longest streaks on VibeXcode.", +}; + +export default function LeaderboardsLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/app/Profile/layout.tsx b/app/Profile/layout.tsx new file mode 100644 index 0000000..8aafa47 --- /dev/null +++ b/app/Profile/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Profile", + description: + "Your VibeXcode profile โ€” solved questions, statistics, current streak, and preferences.", +}; + +export default function ProfileLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/app/community/layout.tsx b/app/community/layout.tsx new file mode 100644 index 0000000..bbb0738 --- /dev/null +++ b/app/community/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Community", + description: + "Join the VibeXcode community โ€” collaborative chat rooms scoped per conversation, with persistent history.", +}; + +export default function CommunityLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/app/layout.tsx b/app/layout.tsx index 5c5feed..aef9626 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -18,10 +18,56 @@ const geistMono = Geist_Mono({ subsets: ["latin"], }); +const SITE_URL = + process.env.NEXT_PUBLIC_APP_URL || "https://vibexcode.netlify.app"; +const SITE_NAME = "VibeXcode"; +const SITE_TITLE = "VibeXcode โ€” A collaborative way to vibe and code"; +const SITE_DESCRIPTION = + "VibeXcode is a developer community platform with real-time forum chat, a multi-language code playground powered by Judge0, leaderboards, and a collaborative Q&A."; + export const metadata: Metadata = { - title: "VibeXcode - A collaborative way to vibe and code!!", - description: - "VibeXcode is a developer community platform with real-time chat, a multi-language code playground powered by Judge0, and a collaborative Q&A forum.", + metadataBase: new URL(SITE_URL), + title: { + default: SITE_TITLE, + template: "%s ยท VibeXcode", + }, + description: SITE_DESCRIPTION, + keywords: [ + "VibeXcode", + "coding playground", + "collaborative coding", + "developer community", + "code execution", + "Judge0", + "real-time chat", + "leaderboard", + "competitive programming", + "Next.js", + ], + authors: [{ name: "VibeXcode" }], + creator: "VibeXcode", + applicationName: SITE_NAME, + alternates: { + canonical: "/", + }, + openGraph: { + type: "website", + siteName: SITE_NAME, + title: SITE_TITLE, + description: SITE_DESCRIPTION, + url: SITE_URL, + locale: "en_US", + }, + twitter: { + card: "summary_large_image", + title: SITE_TITLE, + description: SITE_DESCRIPTION, + }, + robots: { + index: true, + follow: true, + }, + category: "technology", }; export default function RootLayout({ diff --git a/app/login/layout.tsx b/app/login/layout.tsx new file mode 100644 index 0000000..d2ffe9d --- /dev/null +++ b/app/login/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Sign in", + description: + "Sign in to VibeXcode with email or social login (Google, GitHub, Facebook).", +}; + +export default function LoginLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/app/playground/layout.tsx b/app/playground/layout.tsx new file mode 100644 index 0000000..0e4f7b4 --- /dev/null +++ b/app/playground/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Playground", + description: + "Multi-language code playground โ€” write and execute JavaScript, Python, Java, or C++ in a sandbox powered by Judge0, with live diffing against expected output.", +}; + +export default function PlaygroundLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/app/signup/layout.tsx b/app/signup/layout.tsx new file mode 100644 index 0000000..56b5b74 --- /dev/null +++ b/app/signup/layout.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Create account", + description: + "Join VibeXcode โ€” create your account with email or social login to start solving challenges and chatting with the community.", +}; + +export default function SignupLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +}