Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 115 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.

<img width="1917" height="871" alt="Screenshot 2025-07-21 165058" src="https://github.com/user-attachments/assets/dbbaf631-13ab-4a8a-a3b6-79a7397c832b" />
### 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/<token>` until `status.id > 2` (completed)
4. UI renders stdout / stderr / time / memory

<img width="1918" height="866" alt="Screenshot 2025-07-21 165011" src="https://github.com/user-attachments/assets/99fb3774-087a-4bb7-8b10-5d55f4b8dd63" />
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.

<img width="1884" height="874" alt="Screenshot 2025-07-21 165031" src="https://github.com/user-attachments/assets/1a1a59f1-2b44-49b5-a3a3-7b1ec3b5512c" />
### 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

<img width="1892" height="862" alt="Screenshot 2025-07-21 165045" src="https://github.com/user-attachments/assets/f14cbb40-7228-4437-826c-72d9aa4faf6f" />
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.
15 changes: 15 additions & 0 deletions app/Dashboard/layout.tsx
Original file line number Diff line number Diff line change
@@ -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}</>;
}
15 changes: 15 additions & 0 deletions app/Forums/layout.tsx
Original file line number Diff line number Diff line change
@@ -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}</>;
}
15 changes: 15 additions & 0 deletions app/Leaderboards/layout.tsx
Original file line number Diff line number Diff line change
@@ -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}</>;
}
15 changes: 15 additions & 0 deletions app/Profile/layout.tsx
Original file line number Diff line number Diff line change
@@ -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}</>;
}
15 changes: 15 additions & 0 deletions app/community/layout.tsx
Original file line number Diff line number Diff line change
@@ -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}</>;
}
52 changes: 49 additions & 3 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
15 changes: 15 additions & 0 deletions app/login/layout.tsx
Original file line number Diff line number Diff line change
@@ -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}</>;
}
15 changes: 15 additions & 0 deletions app/playground/layout.tsx
Original file line number Diff line number Diff line change
@@ -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}</>;
}
15 changes: 15 additions & 0 deletions app/signup/layout.tsx
Original file line number Diff line number Diff line change
@@ -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}</>;
}