Skip to content

Latest commit

 

History

History
804 lines (688 loc) · 37.7 KB

File metadata and controls

804 lines (688 loc) · 37.7 KB

#implement YJS for conflict free usage and to connect client

⚡ CodeSync — Pro Real-time Collaborative IDE

Build, share, and collaborate on code with instant synchronization, live activity monitoring, and enterprise-grade performance.


📑 Table of Contents


🌐 Overview

CodeSync is a full-stack, real-time collaborative IDE that enables multiple developers to write, edit, and manage code simultaneously within shared workspaces. It features a professional-grade Monaco Editor, role-based access control (RBAC), live cursor tracking, version history, activity feeds, and instant file synchronization — all wrapped in a premium dark-theme UI inspired by Linear, Vercel, and Render.


📸 Screenshots

Login (Split-Screen Auth) Dashboard & Workspace Management Collaborative IDE
Login Page Dashboard Workspace IDE

🏗️ Architecture & Flowcharts

High-Level System Architecture

┌─────────────────────────────────────────────────────────────────────┐
│                          CLIENT (Browser)                           │
│                                                                     │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────────┐  │
│  │   React 18   │  │  Monaco      │  │    Socket.IO Client      │  │
│  │   + Vite     │  │  Editor      │  │  (Real-time events)      │  │
│  └──────┬───────┘  └──────┬───────┘  └────────────┬─────────────┘  │
│         │                 │                        │                │
│         └─────────────────┼────────────────────────┘                │
│                           │                                         │
└───────────────────────────┼─────────────────────────────────────────┘
                            │  HTTP REST + WebSocket (wss://)
                            ▼
┌─────────────────────────────────────────────────────────────────────┐
│                        SERVER (Node.js)                             │
│                                                                     │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────────┐  │
│  │   Express    │  │  Socket.IO   │  │  Middleware Layer         │  │
│  │   REST API   │  │  Server      │  │  (Auth, RBAC, FileAccess)│  │
│  └──────┬───────┘  └──────┬───────┘  └──────────────────────────┘  │
│         │                 │                                         │
│         └─────────┬───────┘                                         │
│                   │                                                 │
│  ┌────────────────▼─────────────────────────────────────────────┐   │
│  │  Controllers: Auth | Workspace | File | Member | Version    │   │
│  │                     Activity | Session | Ownership           │   │
│  └──────────────────────────┬───────────────────────────────────┘   │
│                              │                                      │
└──────────────────────────────┼──────────────────────────────────────┘
                               │
                               ▼
                    ┌─────────────────────┐
                    │    MongoDB Atlas     │
                    │                     │
                    │  Users              │
                    │  Workspaces         │
                    │  WorkspaceMembers   │
                    │  Files              │
                    │  FileVersions       │
                    │  ActivityLogs       │
                    └─────────────────────┘

User Authentication Workflow

┌──────────┐    POST /api/auth/signup     ┌──────────┐
│          │ ──────────────────────────▶   │          │
│  Client  │    { username, email, pwd }  │  Server  │
│          │                              │          │
│          │    POST /api/auth/login       │          │
│          │ ──────────────────────────▶   │          │
│          │    { email, password }        │          │
│          │                              │          │
│          │  ◀────────────────────────── │          │
│          │    { token, user }           │          │
│          │                              │          │
│          │  ─── All subsequent reqs ──▶ │          │
│          │    Authorization: Bearer JWT │          │
└──────────┘                              └──────────┘
                                               │
                                               ▼
                                        ┌──────────────┐
                                        │  bcrypt hash  │
                                        │  + JWT sign   │
                                        └──────────────┘

Real-Time Collaboration Workflow

 User A (Editor)                   Server                    User B (Editor)
       │                              │                              │
       │   join_workspace             │                              │
       │ ────────────────────────────▶│                              │
       │                              │  user_joined (broadcast)     │
       │                              │─────────────────────────────▶│
       │                              │                              │
       │   join_file (fileId)         │                              │
       │ ────────────────────────────▶│                              │
       │                              │                              │
       │   code_change (content)      │                              │
       │ ────────────────────────────▶│                              │
       │                              │  code_update (debounced)     │
       │                              │─────────────────────────────▶│
       │                              │                              │
       │   cursor_position            │                              │
       │ ────────────────────────────▶│                              │
       │                              │  cursor_update (throttled)   │
       │                              │─────────────────────────────▶│
       │                              │                              │
       │                              │  file_created / deleted      │
       │                              │◀────────────────────────────▶│
       │                              │  (synced to all clients)     │
       │                              │                              │
       │   disconnect                 │                              │
       │ ────────────────────────────▶│                              │
       │                              │  user_left (broadcast)       │
       │                              │─────────────────────────────▶│

RBAC Permission Model

┌────────────────────────────────────────────────────────────┐
│                     ROLE HIERARCHY                          │
├──────────┬─────────────────────────────────────────────────┤
│          │                                                 │
│  OWNER   │  ✅ All permissions                             │
│          │  ✅ Delete workspace                            │
│          │  ✅ Invite / remove members                     │
│          │  ✅ Change member roles                         │
│          │  ✅ Transfer ownership                          │
│          │  ✅ Update workspace settings                   │
│          │  ✅ Create / edit / rename / delete files       │
│          │  ✅ Save & restore version history              │
│          │  ✅ View all files & activity                   │
│          │                                                 │
├──────────┼─────────────────────────────────────────────────┤
│          │                                                 │
│  EDITOR  │  ❌ Cannot manage members or settings           │
│          │  ✅ Create / edit / rename / delete files       │
│          │  ✅ Save & restore version history              │
│          │  ✅ Real-time code editing & cursor sync        │
│          │  ✅ View all files & activity                   │
│          │                                                 │
├──────────┼─────────────────────────────────────────────────┤
│          │                                                 │
│  VIEWER  │  ❌ Cannot edit or create files                 │
│          │  ❌ Code changes are blocked at socket level    │
│          │  ✅ View all files (read-only)                  │
│          │  ✅ View activity feed                          │
│          │                                                 │
└──────────┴─────────────────────────────────────────────────┘

File Operations Workflow

                    ┌──────────────────┐
                    │  File Explorer   │
                    │   (Frontend)     │
                    └────────┬─────────┘
                             │
              ┌──────────────┼──────────────┐
              ▼              ▼              ▼
       ┌────────────┐ ┌───────────┐ ┌────────────┐
       │ Create File│ │  Rename   │ │   Delete   │
       │ /Folder    │ │ File/Fold │ │  File/Fold │
       └──────┬─────┘ └─────┬─────┘ └──────┬─────┘
              │              │              │
              ▼              ▼              ▼
       ┌─────────────────────────────────────────┐
       │          REST API (Express)              │
       │    POST /api/files                       │
       │    PATCH /api/files/rename/:id           │
       │    DELETE /api/files/:id                 │
       └────────────────┬────────────────────────┘
                        │
              ┌─────────┼─────────┐
              ▼         ▼         ▼
       ┌───────────┐ ┌──────┐ ┌──────────┐
       │ Name      │ │ RBAC │ │ Activity │
       │ Normalize │ │Check │ │ Logging  │
       └───────────┘ └──────┘ └──────────┘
                        │
                        ▼
              ┌──────────────────┐
              │  Socket.IO emit  │
              │  file_created /  │
              │  file_renamed /  │
              │  file_deleted    │
              │  (all clients)   │
              └──────────────────┘

✅ Feature Status — What's Implemented vs. Planned

✅ Implemented Features

Category Feature Status
Authentication User Registration (signup) ✅ Done
Authentication User Login (JWT-based) ✅ Done
Authentication Protected Routes (frontend guard) ✅ Done
Authentication Token verification middleware ✅ Done
Workspace Create workspace with language template ✅ Done
Workspace List user's workspaces (with roles) ✅ Done
Workspace View workspace details (files, members) ✅ Done
Workspace Delete workspace (owner only, cascading) ✅ Done
Workspace Update workspace settings (name, language) ✅ Done
Collaboration Real-time code sync via Socket.IO ✅ Done
Collaboration Live cursor position tracking ✅ Done
Collaboration User presence indicators (join/leave) ✅ Done
Collaboration Debounced auto-save to MongoDB ✅ Done
File Management Create files & folders ✅ Done
File Management Rename files & folders (with child path update) ✅ Done
File Management Delete files & folders (recursive) ✅ Done
File Management Case-insensitive duplicate name prevention ✅ Done
File Management File name normalization (sanitize specials) ✅ Done
File Management Multi-level nested folder structure ✅ Done
Version History Save file version snapshots ✅ Done
Version History View version history per file ✅ Done
Version History Restore to a previous version ✅ Done
Members Invite member by email ✅ Done
Members Join workspace via Room ID ✅ Done
Members Change member roles (owner action) ✅ Done
Members Remove member from workspace ✅ Done
Members Transfer workspace ownership ✅ Done
Members Members panel with live online status ✅ Done
RBAC Owner / Editor / Viewer role enforcement ✅ Done
RBAC Socket-level viewer write blocking ✅ Done
RBAC Route-level role middleware ✅ Done
Activity Activity logging (joins, edits, role changes) ✅ Done
Activity Real-time activity feed sidebar ✅ Done
Session Leave workspace session (manual) ✅ Done
Session Auto-cleanup on socket disconnect ✅ Done
UI/UX Premium dark theme (Linear/Vercel inspired) ✅ Done
UI/UX Split-screen auth pages ✅ Done
UI/UX Shimmer skeleton loaders ✅ Done
UI/UX Route-based lazy loading ✅ Done
UI/UX Hover prefetch navigation ✅ Done
Performance HTTP gzip compression ✅ Done
Performance .lean() Mongoose queries ✅ Done
Performance Compound indexes on MongoDB ✅ Done
Multi-Language JS, TS, Python, C++, Java, Go, Rust templates ✅ Done
Services User cascade delete (files, members, logs) ✅ Done

🔜 Planned / Not Yet Implemented

Category Feature Status
Auth OAuth2 (GitHub/Google SSO) 🔜 Planned
Auth Password reset / forgot password 🔜 Planned
Auth Email verification on signup 🔜 Planned
Collaboration Integrated terminal (run code in-browser) 🔜 Planned
Collaboration In-editor chat / comments 🔜 Planned
Collaboration Conflict resolution (OT / CRDT) 🔜 Planned
File Management Drag-and-drop file/folder reorder 🔜 Planned
File Management File upload / download 🔜 Planned
File Management File search across workspace 🔜 Planned
Workspace Workspace templates / cloning 🔜 Planned
Workspace Workspace privacy (public/private toggle) 🔜 Planned
Notifications In-app notification system 🔜 Planned
Notifications Email notifications for invites 🔜 Planned
DevOps Docker containerization 🔜 Planned
DevOps CI/CD pipeline 🔜 Planned
Testing Unit & integration test suite 🔜 Planned
UI Light theme / theme toggle 🔜 Planned
UI Mobile responsive layout 🔜 Planned

🔑 Key Features Deep Dive

🧠 Monaco Editor Integration

The in-browser IDE is powered by Monaco Editor (the same engine behind VS Code), providing:

  • Syntax highlighting for 7+ languages
  • IntelliSense / autocomplete
  • Minimap preview
  • Multi-cursor editing
  • Bracket pairing and indentation

⚡ Real-Time Synchronization

  • Code changes are broadcast via Socket.IO with debounced auto-save (5-second delay) to reduce DB writes.
  • Cursor positions are throttled and broadcast to all participants viewing the same file.
  • File operations (create, rename, delete) are instantly pushed to all connected clients.
  • User presence is tracked — join/leave events are emitted in real time.

📂 Hierarchical File System

Full support for nested directory structures:

  • / root-level files
  • /src/, /src/components/, etc.
  • Folders can be created, renamed, and deleted recursively (all child files/folders are cleaned up).
  • Names are normalized and sanitized (trimmed, special characters stripped, case-insensitive uniqueness enforced via compound indexes).

🕑 Version History

Any editor can snapshot the current state of a file. The full history of snapshots is viewable, and any past version can be restored — effectively a manual "git" within the IDE.

🤝 Multi-Method Workspace Sharing

  1. Invite by Email — Owner enters a registered user's email and assigns a role.
  2. Join by Room ID — Any registered user can paste a workspace ID and join (defaults to editor role).

🔒 Ownership Transfer

Workspace owners can transfer ownership to any existing member. The previous owner is demoted to editor, and the new owner gets full admin privileges.


🛠️ Tech Stack

Layer Technology Purpose
Frontend Framework React 18 + Vite SPA with fast HMR and lazy loading
Code Editor Monaco Editor VS Code-grade in-browser editing
Styling CSS3 + Tailwind CSS 4 Dark theme, glassmorphism, animations
Routing React Router v6 Client-side navigation with protected routes
HTTP Client Axios REST API communication
Notifications React Hot Toast In-app toast notifications
Backend Runtime Node.js (ES Modules) Server runtime
API Framework Express.js REST endpoint handling
Real-Time Engine Socket.IO WebSocket bidirectional events
Database MongoDB (Mongoose 8) Document store with indexing
Auth JWT + bcrypt Stateless token auth with hashed passwords
Compression compression (gzip) Response payload optimization
Dev Server Nodemon Auto-restart on backend file changes

📂 Project Structure

CodeSync/
│
├── assets/                          # Screenshots for README
│   ├── login.png
│   ├── dashboard.png
│   └── workspace.png
│
├── backend/
│   ├── controllers/
│   │   ├── activityController.js    # GET activity logs
│   │   ├── authController.js        # Signup / Login (JWT)
│   │   ├── fileController.js        # CRUD files & folders
│   │   ├── memberController.js      # Invite, join, role change, remove
│   │   ├── ownershipController.js   # Transfer workspace ownership
│   │   ├── sessionController.js     # Leave workspace session
│   │   ├── versionController.js     # Save / view / restore versions
│   │   └── workspaceController.js   # CRUD workspaces + settings
│   │
│   ├── middleware/
│   │   ├── authMiddleware.js        # JWT token verification
│   │   ├── filePermission.js        # Verify user access to file's workspace
│   │   ├── permissionMiddleware.js  # RBAC role checks (isOwner, isEditor, requireRole)
│   │   └── workspaceAuth.js         # Workspace membership verification
│   │
│   ├── models/
│   │   ├── ActivityLog.js           # Action audit trail (indexed)
│   │   ├── File.js                  # Files & folders (compound unique index)
│   │   ├── FileVersion.js           # Version snapshots
│   │   ├── User.js                  # User accounts (bcrypt hashed)
│   │   ├── Workspace.js             # Workspace metadata
│   │   └── WorkspaceMember.js       # User-workspace membership & role
│   │
│   ├── routes/
│   │   ├── authRoutes.js            # /api/auth/*
│   │   ├── fileRoutes.js            # /api/files/*
│   │   └── workspaceRoutes.js       # /api/workspaces/*
│   │
│   ├── services/
│   │   └── deleteUserCascade.js     # Cascade-delete user data
│   │
│   ├── utils/
│   │   └── logger.js                # Activity logging utility
│   │
│   ├── server.js                    # Express + Socket.IO entry point
│   ├── .env.example                 # Environment template
│   └── package.json
│
├── frontend/
│   ├── src/
│   │   ├── components/
│   │   │   ├── ActivityFeed.jsx     # Live activity sidebar
│   │   │   ├── CreateWorkspaceModal.jsx  # Create workspace dialog
│   │   │   ├── FileExplorer.jsx     # Tree-view file browser
│   │   │   ├── InviteModal.jsx      # Email invite dialog
│   │   │   ├── JoinWorkspaceModal.jsx    # Join via Room ID
│   │   │   ├── MembersPanel.jsx     # Online members list with roles
│   │   │   ├── ShareWorkspaceModal.jsx   # Share workspace dialog
│   │   │   └── VersionHistory.jsx   # File version timeline
│   │   │
│   │   ├── context/
│   │   │   └── AuthContext.jsx      # Auth state provider
│   │   │
│   │   ├── pages/
│   │   │   ├── Dashboard.jsx        # Workspace listing & management
│   │   │   ├── Login.jsx            # Split-screen login
│   │   │   ├── Signup.jsx           # Split-screen signup
│   │   │   └── Workspace.jsx        # Collaborative IDE view
│   │   │
│   │   ├── routes/
│   │   │   └── ProtectedRoute.jsx   # Auth guard wrapper
│   │   │
│   │   ├── services/
│   │   │   ├── api.js               # Axios instance (base URL + token)
│   │   │   ├── fileApi.js           # File CRUD API calls
│   │   │   ├── memberApi.js         # Member management API calls
│   │   │   └── workspaceApi.js      # Workspace API calls
│   │   │
│   │   ├── App.jsx                  # Root router + lazy loader
│   │   ├── main.jsx                 # React DOM entry point
│   │   └── index.css                # Global styles & design system
│   │
│   ├── index.html                   # HTML shell
│   ├── vite.config.js               # Vite config
│   ├── tailwind.config.js           # Tailwind CSS 4 config
│   ├── postcss.config.js            # PostCSS config
│   └── package.json
│
├── Readme.md                        # Original README (legacy)
├── README.md                        # ← You are here
└── .gitignore

📡 API Reference

Auth Routes — /api/auth

Method Endpoint Description Auth Required
POST /signup Register a new user
POST /login Login and receive JWT

Workspace Routes — /api/workspaces

Method Endpoint Description Min. Role
POST / Create a new workspace Authenticated
GET / List user's workspaces Authenticated
GET /:id Get workspace details (files, members) Viewer
DELETE /:id Delete workspace + cascade Owner
PATCH /:id/settings Update name & language Owner
GET /:id/activity Get activity log Viewer
DELETE /:id/session Leave workspace session Authenticated
POST /:id/invite Invite user by email Owner
POST /:id/join Join workspace via Room ID Authenticated
PATCH /:id/role Change member role Owner
DELETE /:id/member/:userId Remove a member Owner
POST /:id/transfer Transfer ownership Owner

File Routes — /api/files

Method Endpoint Description Min. Role
POST / Create file or folder Editor
GET /open/:id Get file content Viewer
PUT /:id Update file content Editor
PATCH /rename/:id Rename file or folder Editor
DELETE /:id Delete file or folder (recursive) Editor
POST /:fileId/version Save version snapshot Editor
GET /:fileId/history Get version history Viewer
POST /restore/:versionId Restore a file version Editor

🔌 Socket.IO Real-Time Events

Client → Server

Event Payload Description
join_workspace { workspaceId, userId, username, color } Join a workspace room
join_file { fileId, username, color } Join a file editing room
leave_file { fileId } Leave a file editing room
code_change { fileId, code, userId } Send code changes (debounced save)
cursor_position { fileId, position, username, color } Broadcast cursor location

Server → Client

Event Payload Description
user_joined { userId, profileId, username, color, role } A user joined the workspace
user_left { userId, username } A user left the workspace
code_update { fileId, code } Code was changed by another user
cursor_update { userId, position, username, color } Cursor moved by another user
file_created { file } A new file/folder was created
file_renamed { fileId, name, oldName, path } A file/folder was renamed
file_deleted { fileId } A file/folder was deleted
folder_children_deleted { parentPath } Children of a folder were deleted
member_updated { userId, role, oldRole } A member's role was changed
member_removed { userId } A member was removed
member_joined { _id, user, role } A new member joined
activity_update Refresh activity feed

🗃️ Database Models

User

Field Type Details
username String Required, trimmed
email String Required, unique, lowercase
password String bcrypt-hashed
timestamps Auto createdAt, updatedAt

Workspace

Field Type Details
name String Required, max 80 chars
owner ObjectId → User Required
language Enum javascript, typescript, python, java, cpp, go, rust, html, css, json, txt

WorkspaceMember

Field Type Details
workspaceId ObjectId → Workspace Required
userId ObjectId → User Required
role Enum owner / editor / viewer
invitedBy ObjectId → User Optional

File

Field Type Details
workspaceId ObjectId → Workspace Required
name String Required, trimmed
nameLower String Auto (pre-save hook)
path String Directory path, default /
type Enum file / folder
content String File contents
language String Auto-detected from extension
createdBy ObjectId → User
lastEditedBy ObjectId → User
Index Compound Unique (workspaceId, path, nameLower)

FileVersion

Field Type Details
fileId ObjectId → File Required
content String Snapshot content
editedBy ObjectId → User Required

ActivityLog

Field Type Details
workspaceId ObjectId → Workspace Required
userId ObjectId → User Required
actionType String e.g. USER_JOINED, FILE_CREATED, ROLE_CHANGED
targetId ObjectId Optional target entity
metadata Mixed Additional context

🔄 Workflows

1. Creating a New Workspace

User clicks "Create Workspace" on Dashboard
        │
        ▼
┌─────────────────────────┐
│  CreateWorkspaceModal    │
│  - Enter workspace name  │
│  - Select language        │
└───────────┬──────────────┘
            │ POST /api/workspaces
            ▼
┌─────────────────────────────┐
│  Server creates:             │
│  1. Workspace document       │
│  2. WorkspaceMember (owner)  │
│  3. Starter file (template)  │
└───────────┬──────────────────┘
            │
            ▼
  Redirect to /workspace/:id
  (Monaco Editor opens with starter file)

2. Inviting a Collaborator

Owner clicks "Share" → ShareWorkspaceModal
        │
        ├─── Option A: Invite by Email
        │    │  POST /api/workspaces/:id/invite
        │    │  { email, role: 'editor' | 'viewer' }
        │    ▼
        │    Server adds WorkspaceMember + emits member_joined
        │
        └─── Option B: Share Room ID
             │  Invitee uses JoinWorkspaceModal
             │  POST /api/workspaces/:id/join
             ▼
             Server adds WorkspaceMember (editor) + emits member_joined

3. Real-Time Editing Session

1. Navigate to /workspace/:id
2. Socket connects → join_workspace event
3. Server broadcasts user_joined to all
4. User clicks a file → join_file event
5. User types → code_change events
6. Server broadcasts code_update to others viewing same file
7. Server debounces (5s) and saves to MongoDB
8. Cursor movements broadcast via cursor_position → cursor_update
9. On disconnect → user_left broadcast + active user cleanup

4. Version History

Editor clicks "Save Version" icon
        │  POST /api/files/:fileId/version
        ▼
  FileVersion snapshot created (content + editedBy + timestamp)
        │
        ▼
  VersionHistory panel shows all snapshots for this file
        │
        ▼
  User clicks "Restore" on a past version
        │  POST /api/files/restore/:versionId
        ▼
  File.content overwritten with snapshot content
  (Socket will pick up and sync to all clients)

5. Ownership Transfer

Owner opens MembersPanel → clicks "Transfer Ownership"
        │  POST /api/workspaces/:id/transfer
        │  { targetUserId }
        ▼
  ┌───────────────────────────────────────┐
  │ 1. Current owner → demoted to editor  │
  │ 2. Target user → promoted to owner    │
  │ 3. Workspace.owner ref updated        │
  │ 4. Activity logged                    │
  └───────────────────────────────────────┘

🚀 Quick Start Guide

Prerequisites

  • Node.js v16+ (v18+ recommended)
  • MongoDB instance (local or MongoDB Atlas)
  • npm v8+

1. Clone the Repository

git clone https://github.com/your-username/CodeSync.git
cd CodeSync

2. Setup Backend

cd backend
npm install

Create a .env file (use .env.example as reference):

PORT=3001
MONGO_URI=your_mongodb_connection_string
JWT_SECRET=your_jwt_secret_key
FRONTEND_URL=http://localhost:5173

Start the backend:

npm run dev    # Development (with nodemon)
npm start      # Production

3. Setup Frontend

cd frontend
npm install

Create a .env.development file:

VITE_BACKEND_URL=http://localhost:3001

Start the frontend:

npm run dev

4. Open the App

Navigate to http://localhost:5173 in your browser.


🔐 Environment Variables

Backend (.env)

Variable Description Example
PORT Server port 3001
MONGO_URI MongoDB connection string mongodb+srv://...
JWT_SECRET Secret key for JWT signing your-secret-key
FRONTEND_URL Frontend origin for CORS http://localhost:5173

Frontend (.env.development)

Variable Description Example
VITE_BACKEND_URL Backend API base URL http://localhost:3001

📈 Performance Metrics

Metric Value How
Auth page initial focus ~0ms Instant render, no blocking
Workspace navigation (prefetched) < 150ms Route-based lazy loading + hover prefetch
Database query latency reduction ~40% .lean() on all read queries
Real-time message latency < 50ms Throttled Socket.IO broadcasting
API response size reduction ~60% Gzip compression middleware
Auto-save debounce interval 5 seconds Prevents excessive MongoDB writes

🤝 Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License

This project is licensed under the MIT License — see the LICENSE file for details.


Built with ❤️ Alok Kumar Sharma