This project has been created as part of the 42 curriculum by Samir, Abderrahim, Youssef, Samia, Amal.
- Overview
- Features
- Tech Stack
- Architecture
- Database Schema
- Repository Structure
- Getting Started
- Using Makefile
- Environment Variables
- API Reference
- DevOps & Observability
- Team Contributions
- Project Rules
- System-Wide Interaction Graph
- Database Visualization
- Conclusion
PinSpace is a feature-rich, production-ready social platform built as a team project. Inspired by Pinterest, it lets users discover, create, and share visual content ("pins") organized into curated boards. Beyond content sharing, PinSpace includes a full social graph (followers, friends), real-time private messaging, live notifications, OAuth 2.0 login, two-factor authentication, a secured public API, and a complete DevOps observability stack powered by the ELK suite.
The entire application runs in a single Next.js process — the API routes serve as the backend, server components leverage SSR for SEO, and a custom Socket.io server layered on top of the Next.js HTTP server powers all real-time features.
| Feature | Description |
|---|---|
| Pins | Create, view, like, comment, and tag image-based posts with optional links and descriptions |
| Boards | Organise pins into named collections; manage and delete boards |
| Feed | Personalised, paginated home feed based on who you follow |
| Search | Advanced search with filters, sorting, and pagination |
| File Upload | Upload and manage images via Vercel Blob Storage |
| Feature | Description |
|---|---|
| Follow system | Follow/unfollow any user; followers & following counts on profiles |
| Friend system | Send, accept, and remove friend requests (PENDING → ACCEPTED) |
| User profiles | Public profiles with cover image, bio, website, pins, and boards |
| Direct messaging | Real-time 1-to-1 conversations with read receipts and unread counts |
| Feature | Description |
|---|---|
| Email/Password auth | Register, login, email verification, forgot/reset password |
| OAuth 2.0 | One-click sign-in with Google, GitHub (and 42 School) |
| Two-Factor Auth (2FA) | TOTP-based 2FA with QR code setup and toggle in settings |
| Custom JWT | HTTP-only cookie-based JWT for session management |
| API Key auth | Secured public API access using generated API keys with rate limiting |
| Account management | Change email, change password, delete account |
| Feature | Description |
|---|---|
| Live notifications | Instant alerts for likes, comments, friend requests, follows |
| Online presence | See which users are online/offline in real time |
| Real-time messaging | Messages and read-receipts delivered instantly over WebSocket |
| Sidebar updates | Conversation sidebar refreshes live on new messages |
| Feature | Description |
|---|---|
| ELK Stack | Structured JSON logs shipped from the app → Logstash → Elasticsearch → Kibana |
| ILM policy | Automatic log rotation (7-day rollover, 30-day deletion) |
| Health checks | Status page and automated backup/disaster-recovery procedures |
| Docker Compose | One-command spin-up of app, PostgreSQL, Elasticsearch, Logstash, and Kibana |
| SSR | Every page is server-rendered for fast initial load and SEO |
| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, SSR, API Routes) |
| Language | TypeScript 5 |
| Styling | Tailwind CSS 4 + shadcn/ui components |
| ORM | Prisma 7 |
| Database | PostgreSQL 14 |
| Real-time (server) | Socket.io 4 |
| Real-time (push) | Pusher |
| Authentication | Custom JWT + NextAuth.js 4 (OAuth) |
| 2FA | Speakeasy + QRCode |
| Resend | |
| File Storage | Vercel Blob |
| Validation | Zod 4 |
| Logging | Custom Logstash logger → Elasticsearch 8.11 → Kibana 8.11 |
| Containerisation | Docker + Docker Compose |
┌────────────────────────────────────────────────────────────┐
│ Browser Client │
│ Next.js App Router · Socket.io-client │
└─────────────┬───────────────────────────┬──────────────────┘
│ HTTP / SSR │ WebSocket
▼ ▼
┌────────────────────────────────────────────────────────────┐
│ Node.js / Next.js Server (port 3000) │
│ │
│ ┌──────────────────┐ ┌──────────────────────────────┐ │
│ │ Next.js Handler │ │ Socket.io Server │ │
│ │ (API Routes + │ │ • online presence │ │
│ │ SSR pages) │ │ • messaging events │ │
│ └────────┬─────────┘ │ • like/comment/follow notif │ │
│ │ │ • friend-request notif │ │
│ │ └──────────────┬───────────────┘ │
│ │ │ │
│ ┌────────▼────────────────────────────▼───────────────┐ │
│ │ Prisma ORM │ │
│ └────────────────────────┬────────────────────────────┘ │
│ │ │
│ ┌────────────────────────▼─────────────────────────────┐ │
│ │ Logstash Logger → TCP :5044 │ │
│ └──────────────────────────────────────────────────────┘ │
└────────────────────────────────────────────────────────────┘
│ │
▼ ▼
┌────────────────┐ ┌──────────────────────┐
│ PostgreSQL 14 │ │ Logstash 8.11 │
└────────────────┘ │ → Elasticsearch │
│ → Kibana │
└──────────────────────┘
Transcendence/
├── Project/ # Application root
│ ├── app/
│ │ ├── (frontend)/ # All UI routes (Next.js App Router)
│ │ │ ├── (auth)/ # Login, Register, 2FA, Forgot/Reset password
│ │ │ └── feed/ # Protected app pages
│ │ │ ├── page.tsx # Home feed
│ │ │ ├── pin/[id]/ # Pin detail page
│ │ │ ├── board/[id]/ # Board detail page
│ │ │ ├── profile/ # User profiles
│ │ │ ├── message/ # Real-time messaging
│ │ │ ├── notifications/ # Notification centre
│ │ │ └── settings/ # Account, 2FA, privacy settings
│ │ └── api/ # REST API route handlers
│ │ ├── auth/ # Auth endpoints (login, register, 2FA, OAuth)
│ │ ├── pins/ # Pin CRUD
│ │ ├── boards/ # Board CRUD
│ │ ├── chat/ # Conversations & messages
│ │ ├── friends/ # Friend requests & relations
│ │ ├── notifications/ # Notification fetch & cleanup
│ │ ├── public/ # API-key-secured public endpoints
│ │ └── user/ # User update, email, password, delete
│ ├── components/ # Shared React components
│ ├── lib/ # Utility modules (JWT, Prisma, Pusher, OAuth…)
│ ├── hooks/ # Custom React hooks
│ ├── prisma/
│ │ └── schema.prisma # Full database schema
│ ├── devops/
│ │ ├── logstash/pipeline/ # Logstash pipeline config
│ │ └── setup-elk.sh # ELK bootstrap script (ILM, index template, Kibana)
│ ├── server.js # Custom HTTP + Socket.io server
│ ├── otel-config.js # Logstash structured logger
│ ├── docker-compose.yml # Full stack orchestration
│ └── Dockerfile # Multi-stage production image
└── Subject/
└── subject.pdf # Project specification
The Prisma schema defines 14 models covering the full data domain:
| Model | Purpose |
|---|---|
User |
Core user identity, profile, 2FA, online status |
Account |
OAuth provider accounts linked to a user |
Session |
NextAuth sessions |
VerificationToken |
Email verification tokens |
ResetPasswordToken |
Password reset tokens |
Pin |
User-created image posts with tags |
Board |
Named collections of pins |
Like |
User ↔ Pin likes (unique constraint) |
Comment |
User comments on pins |
LikeComment |
Likes on comments (unique constraint) |
Friendship |
Bidirectional friend requests (PENDING / ACCEPTED) |
Follow |
Unidirectional follow relationships |
Conversation + ConversationParticipant |
Private messaging threads |
Message |
Individual chat messages with read status |
Notification |
Typed notifications (LIKE, COMMENT, FRIEND_REQUEST, etc.) |
The repository contains two top-level folders:
Project: main applicationSubject: project subject/resources
- Clone the repository:
git clone https://github.com/samir-ouaammou/Transcendence.git- Enter the application folder:
cd Transcendence/Project- Use the Makefile commands below to manage the application and Docker environment.
Recommended usage for the local environment:
make up- Start the Docker Compose stack in detached mode.make up-build- Build the images and start the stack in detached mode.make down- Stop the stack and remove the Compose volumes.make restart- Stop the stack and start it again.make force-restart- Stop and remove all containers, then start the stack again.make docker-clean- Remove all local containers, volumes, and images.make clean- Run a full Docker system prune with volumes.make reset- Run the full reset workflow defined in the Makefile.
Copy .env.example to .env and populate every value:
# ── Database ──────────────────────────────────────────────────
POSTGRES_USER=your_postgres_user
POSTGRES_PASSWORD=your_postgres_password
POSTGRES_DB=your_postgres_db
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
# ── Auth ──────────────────────────────────────────────────────
AUTH_SECRET=your_auth_secret
JWT_SECRET=your_jwt_secret # signs custom JWT cookies
NEXTAUTH_SECRET=your_nextauth_secret # signs NextAuth sessions
NEXTAUTH_URL=http://localhost:3000
AUTH_TRUST_HOST=http://localhost:3000
DOMAIN=http://localhost:3000
# ── OAuth Providers ───────────────────────────────────────────
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
# ── Storage ───────────────────────────────────────────────────
BLOB_READ_WRITE_TOKEN=your_vercel_blob_token
# ── Email ─────────────────────────────────────────────────────
RESEND_API_KEY=your_resend_api_key
# ── Realtime (Pusher) ─────────────────────────────────────────
PUSHER_APP_ID=your_pusher_app_id
PUSHER_APP_SECRET=your_pusher_app_secret
NEXT_PUBLIC_PUSHER_APP_KEY=your_pusher_app_key
NEXT_PUBLIC_PUSHER_CLUSTER=your_pusher_cluster
# ── ELK Stack ─────────────────────────────────────────────────
ELASTICSEARCH_HOSTS=http://elasticsearch:9200
KIBANA_HOST=http://kibana:5601All routes are under /api. Protected routes require a valid jwtToken HTTP-only cookie (or Authorization: Bearer <api-key> for public endpoints).
| Method | Route | Description |
|---|---|---|
POST |
/api/auth/register |
Create a new account |
POST |
/api/auth/login |
Login and receive JWT cookie |
GET |
/api/auth/me |
Get the authenticated user |
POST |
/api/auth/verify |
Verify email address |
POST |
/api/auth/forgot-password |
Send reset-password email |
POST |
/api/auth/reset-password |
Set a new password via token |
GET/POST |
/api/auth/[...nextauth] |
NextAuth OAuth endpoints |
POST |
/api/auth/2fa/generate |
Generate TOTP secret + QR code |
POST |
/api/auth/2fa/verify |
Verify a TOTP code |
POST |
/api/auth/2fa/toggle |
Enable or disable 2FA |
| Method | Route | Description |
|---|---|---|
GET/POST |
/api/pins |
List / create pins |
GET |
/api/feed |
Personalised feed |
GET/POST |
/api/boards |
List / create boards |
DELETE |
/api/boards/[id]/delete_board |
Delete a board |
GET |
/api/boards/[id]/pins |
List pins in a board |
| Method | Route | Description |
|---|---|---|
POST |
/api/like |
Toggle like on a pin |
POST |
/api/comment |
Add a comment |
POST |
/api/likeComment |
Toggle like on a comment |
GET/POST |
/api/friends |
List friends / send request |
POST |
/api/friends/accept |
Accept a friend request |
POST |
/api/friends/follow |
Follow a user |
DELETE |
/api/friends/remove |
Remove friend / unfollow |
| Method | Route | Description |
|---|---|---|
GET |
/api/chat/conversations |
List conversations |
GET |
/api/chat/conversations/create-or-get/[friendId] |
Get or create DM thread |
GET/POST |
/api/chat/messages/[id] |
Fetch / send messages |
GET |
/api/chat/unread-count |
Unread message count |
| Method | Route | Description |
|---|---|---|
GET |
/api/notifications |
List notifications |
GET |
/api/notifications/count |
Unread notification count |
DELETE |
/api/notifications/cleanup |
Delete read notifications |
| Method | Route | Description |
|---|---|---|
GET |
/api/public/pins |
Browse public pins |
Every server event (connections, messages, likes, friend requests, errors) is emitted as structured JSON to a custom Logstash logger (otel-config.js). Logs are sent over TCP to Logstash on port 5044, indexed in Elasticsearch, and visualised in Kibana.
App (JSON over TCP) → Logstash :5044 → Elasticsearch :9200 → Kibana :5601
Log fields: timestamp, level (INFO | WARN | ERROR), message, plus arbitrary attributes (userId, socketId, conversationId…).
The setup-elk.sh bootstrap script configures:
- ILM policy — roll over after 7 days or 5 GB; delete after 30 days
- Index template —
logs-*pattern with typed field mappings - Kibana index pattern — auto-created for immediate dashboard use
- All Docker containers are configured with
restart: unless-stopped - PostgreSQL data is persisted in a named volume (
postgres_data) - Elasticsearch data is persisted in a named volume (
elasticsearch_data) - The
elk-setupservice waits for both Elasticsearch and Kibana to be healthy before applying configuration
This project was built collaboratively. Each feature was assigned to one or more team members.
| Member | Contributions |
|---|---|
| Samir | Complete user management system covering authentication and authorization, including registration, login, email verification, password reset, profile updates, account management, OAuth 2.0 integration (Google, GitHub, 42 School), and a full Two-Factor Authentication (2FA) workflow. |
| Abderrahim | Real-time collaborative features, advanced search with filters / sorting / pagination, file upload & management system, SSR integration, user interaction flows |
| Samia | Real-time features (WebSockets / Socket.io), user-to-user interaction flows, notification system (create / update / delete events) |
| Amal | Real-time features, public API with secured API key + rate limiting, ORM setup (Prisma), user interaction flows, SSR integration |
| Youssef | DevOps — ELK stack (Elasticsearch, Logstash, Kibana) for log management |
Score breakdown (project spec): Web features — 14 pts · Accessibility & i18n — 1 pt · User Management — 4 pts · DevOps — 4 pts · Total: 23 pts
These conventions were adopted to keep the codebase consistent across the team:
- All work happens on feature branches (
feature/<name>,fix/<name>,devops/<name>) - Pull requests are mandatory before merging into
main— no direct pushes - Every PR requires at least one review from another team member before merging
- Commit messages follow the Conventional Commits format:
feat:,fix:,chore:,docs:,style:,refactor:,test:
- All code is written in TypeScript —
anytypes are forbidden unless strictly necessary - Zod schemas are used for all API input validation
- API routes must always return typed JSON responses and use correct HTTP status codes
- Database access goes exclusively through the Prisma client — raw SQL is not allowed
- Secrets and credentials are stored in
.envonly — never hard-coded or committed
- Frontend pages live under
app/(frontend)/; API handlers live underapp/api/ - Reusable logic (auth, database, utilities) belongs in the
lib/directory - Shared UI components live in
components/; page-local components stay co-located with their route - Real-time events are handled by the custom Socket.io server in
server.js
- The
.env.examplefile must be kept up to date whenever a new environment variable is added - The application must run cleanly with
docker compose up --buildwithout manual steps - All services must start and pass health checks before the app is considered ready
flowchart LR
subgraph Client[Client Layer]
U1[Guest User]
U2[Authenticated User]
FE[Next.js Frontend<br/>App Router + React]
WSClient[Socket.io Client]
U1 --> FE
U2 --> FE
FE --> WSClient
end
subgraph App[Application Layer]
SSR[SSR + Server Components]
API[Route Handlers<br/>/api/*]
AUTH[Auth Services<br/>JWT + NextAuth + 2FA]
SOCIAL[Social Services<br/>Pins/Boards/Friends/Follows]
CHAT[Chat Services<br/>Conversations/Messages]
NOTIF[Notification Services]
PUBLICAPI[Public API<br/>/api/public/pins]
RATE[API Key Validation + Rate Limit]
FE --> SSR
FE --> API
API --> AUTH
API --> SOCIAL
API --> CHAT
API --> NOTIF
API --> PUBLICAPI
PUBLICAPI --> RATE
end
subgraph Realtime[Real-time Layer]
WSServer[Socket.io Server<br/>Project/server.js]
PRESENCE[Online Presence]
RTCHAT[Live Messaging]
RTSOCIAL[Live Social Notifications]
WSClient <--> WSServer
WSServer --> PRESENCE
WSServer --> RTCHAT
WSServer --> RTSOCIAL
end
subgraph Data[Data Layer]
PRISMA[Prisma ORM]
DB[(PostgreSQL 14)]
API --> PRISMA
CHAT --> PRISMA
SOCIAL --> PRISMA
AUTH --> PRISMA
PRISMA --> DB
end
subgraph External[External Integrations]
OAUTH[OAuth Providers<br/>Google/GitHub]
RESEND[Resend Email]
BLOB[Vercel Blob Storage]
PUSHER[Pusher]
AUTH --> OAUTH
AUTH --> RESEND
SOCIAL --> BLOB
NOTIF --> PUSHER
end
subgraph Obs[Observability]
LOGGER[Structured Logger<br/>otel-config.js]
LOGSTASH[Logstash :5044]
ES[Elasticsearch :9200]
KIBANA[Kibana :5601]
API --> LOGGER
WSServer --> LOGGER
LOGGER --> LOGSTASH --> ES --> KIBANA
end
erDiagram
User ||--o{ Board : creates
User ||--o{ Pin : creates
Board ||--o{ Pin : contains
User ||--o{ Like : gives
Pin ||--o{ Like : receives
User ||--o{ Comment : writes
Pin ||--o{ Comment : has
Comment ||--o{ Comment : replies_to
User ||--o{ LikeComment : gives
Comment ||--o{ LikeComment : receives
User ||--o{ Follow : follower
User ||--o{ Follow : following
User ||--o{ Friendship : friendA
User ||--o{ Friendship : friendB
User ||--o{ Friendship : requestedBy
Conversation ||--o{ ConversationParticipant : has
User ||--o{ ConversationParticipant : joins
Conversation ||--o{ Message : has
User ||--o{ Message : sends
User ||--o{ Notification : receives
User ||--o{ Notification : sends
Pin ||--o{ Notification : references
User ||--o{ Account : owns
User ||--o{ Session : owns
- A user can own multiple boards and pins.
- Pins can optionally belong to a board.
LikeandLikeCommentenforce uniqueness across(userId, targetId)pairs.- Friendships are tracked with status transitions (
PENDING,ACCEPTED). - Conversations are many-to-many with users through
ConversationParticipant. - Notifications uniquely combine user, sender, type, and optional relation context.
PinSpace is a complete ft_transcendence delivery that combines modern full-stack engineering, social product design, real-time communication, secure identity management, and operational observability in a single cohesive platform.
The project reflects strong collaborative execution across architecture, frontend, backend, realtime systems, and DevOps, and provides a solid foundation for continued product hardening and feature expansion.
