Skip to content

pariksith/LETS-CHAT-CARTRABBIT

Repository files navigation

💬 Let's Chat — Full-Stack Real-Time Messaging App

License: MIT PHP: 8.1+ Laravel: 10 JavaScript: ES6+ Vite: 5

Welcome to the ultimate Let's Chat documentation. This repository contains the complete source code and infrastructure for a highly scalable, real-time messaging application deeply inspired by WhatsApp. It features a modern, responsive single-page application (SPA) frontend built with Vanilla JavaScript and Vite, backed by a robust RESTful API built on PHP 8.1+ and Laravel 10.


📋 Table of Contents

  1. Executive Summary
  2. Core Features & Capabilities
  3. Technology Stack Detailed Breakdown
  4. Architecture & System Design
  5. Directory Structure Analysis
  6. Comprehensive Installation Guide
  7. Environment Configuration
  8. Database Schema in Detail
  9. Comprehensive API Reference
  10. Frontend Modules & Event Flow
  11. Backend Architecture
  12. Real-time Communication Flow (WebRTC & WebSockets)
  13. Extensive Testing Guide
  14. Deployment & Hosting
  15. Security & Privacy Model
  16. Performance Optimization
  17. Contributing & Code of Conduct
  18. Roadmap & Future Scope
  19. Frequently Asked Questions (FAQ)
  20. License

1. Executive Summary

Let's Chat goes beyond simple text messaging, offering a complete multimedia experience. From voice notes, image uploads, and file attachments to GIFs and Stickers, the application aims to recreate a premium messaging experience.

Key pillars of the project:

  • WhatsApp-like Presence: Real-time "Online" status, "Last seen" timestamps, and message read receipts (Single tick ✓, Double tick ✓✓, Blue tick ✓✓).
  • Real-time Communications: Fully functional peer-to-peer Audio and Video calling powered by WebRTC, utilizing a custom PHP WebSocket signaling server.
  • Performant UI: Employing optimistic UI updates and lightweight HTTP polling for messaging to avoid heavy WebSocket overhead, while reserving raw TCP WebSockets exclusively for WebRTC call signaling.

2. Core Features & Capabilities

2.1 Core Messaging Engine

  • Text Messages: Send and receive with optimistic UI updates (instant local display).
  • Voice Messages: Record, send, and playback with duration tracking via Web Audio API. Long-press to record, release to send.
  • Image Messages: Attach and preview images inline. Base64 encoding for smooth transport.
  • GIF & Sticker Support: Integrated seamlessly with the GIPHY API.
  • File Attachments: Share documents, PDFs, and archives with immediate download links.
  • Validation: 1000-character limit on the frontend UI, with strict 5000-character backend validation to prevent abuse.

2.2 WhatsApp-Style Features

  • Message Status Ticks:
    • Single ✓: Sent to the server successfully.
    • Double ✓✓: Delivered to the recipient's device.
    • Blue ✓✓: Read by the recipient (thread opened).
  • Online Presence: Green dot for active users. "Last seen X min ago" for offline users.
  • Heartbeat System: 30-second background pulse to track real-time user presence.
  • Strict Privacy: Conversations are strictly isolated between two participants. Group chats are not currently supported by design.

2.3 Voice & Video Calling

  • WebRTC P2P: Peer-to-peer audio/video calls ensuring end-to-end privacy and zero server bandwidth consumption for media streams.
  • Signaling Server: Custom PHP WebSocket server for call negotiation (SDP exchange).
  • NAT Traversal: Out-of-the-box ICE/STUN/TURN server support.
  • Call States: Comprehensive state machine handling Ringing → Accepted → Ended/Declined.

2.4 UX & Design Aesthetics

  • Dynamic Theming: Dark / Light theme toggle with localStorage persistence.
  • State Persistence: Conversation caching in localStorage for instant sidebar loads upon refresh.
  • Client-Side Routing: Route-based SPA with hash-free client-side routing (history.pushState).
  • Responsive Layout: Fluid flexbox/grid layout featuring a sidebar, active thread, and profile panels.
  • Internationalization (i18n): Multi-language / Unicode support including RTL-ready input fields.
  • Rich Input: Emoji picker with 50+ built-in emojis and smooth micro-animations.

3. Technology Stack Detailed Breakdown

3.1 Frontend Stack

  • Framework: Vanilla JavaScript (ES Modules). We avoided heavy frameworks (React/Vue) to demonstrate deep understanding of DOM manipulation and native browser APIs.
  • Build Tool: Vite 5 for blazing-fast HMR and optimized production bundling.
  • Styling: Vanilla CSS (Custom Design System with CSS variables).
  • Typography: Google Fonts (Plus Jakarta Sans, Sora).

3.2 Backend Stack

  • Framework: Laravel 10 (PHP 8.1+). Provides robust routing, ORM, and dependency injection.
  • Authentication: Laravel Sanctum (Token-based, stateless API authentication).
  • Database: MySQL 8.0 (using utf8mb4_unicode_ci for robust emoji and multilingual support).

3.3 Real-time & Media Services

  • WebSockets: Custom raw TCP socket server written in PHP (stream_socket_server).
  • Media Transports: WebRTC (RTCPeerConnection, getUserMedia API).
  • External APIs: GIPHY API for dynamic media.

4. Architecture & System Design

graph TD
    A[Client Browser / Frontend SPA] -->|HTTP/REST - Polling 1.2s| B(Laravel API:8001)
    A -->|WebSocket wss://| C(PHP Signaling Server:8081)
    A <-->|WebRTC Peer-to-Peer| A2[Other Client Browser]
    B -->|SQL Queries| D[(MySQL Database 8.0)]
    C -->|Memory State| C
Loading

Design Philosophy:

  1. Polling-based messaging: A 1.2-second interval handles chat polling. This drastically reduces the persistent connection overhead on the server compared to maintaining thousands of idle WebSockets.
  2. WebSocket strictly for calls: The signaling server purely handles WebRTC negotiation payloads (SDPs/ICE candidates) where sub-millisecond latency is crucial.
  3. Optimistic UI: Messages are rendered to the DOM instantly before the server responds, hiding the network latency of the polling architecture.
  4. Local media storage: Voice and image files are initially captured as base64 data URLs, sent to the server, and saved as physical files to keep database sizes minimal.

5. Directory Structure Analysis

chat_app_full/
├── backend/                        # Laravel 10 REST API
│   ├── app/
│   │   ├── Http/
│   │   │   ├── Controllers/        # Core business logic
│   │   │   │   ├── AuthController.php      # Login, register, logout, /me
│   │   │   │   ├── ChatController.php      # Messages, media upload, read receipts
│   │   │   │   ├── CallController.php      # WebRTC session state persistence
│   │   │   │   └── UserController.php      # Presence heartbeat, user listing
│   │   │   ├── Middleware/         # CORS, auth verification, API rate limits
│   │   │   └── Requests/           # Form request validation logic
│   │   ├── Models/                 # Eloquent ORM Models
│   │   │   ├── User.php
│   │   │   ├── Message.php
│   │   │   └── CallSession.php
│   │   └── Providers/
│   ├── config/                     # Laravel configuration files
│   ├── realtime/
│   │   └── call_signal_server.php  # Raw PHP WebSocket signaling daemon
│   ├── routes/
│   │   └── api.php                 # Centralized API route definitions
│   ├── storage/                    # Uploaded media, logs, framework cache
│   ├── .env.example
│   └── composer.json
│
├── frontend/                       # Vanilla JS SPA (Vite)
│   ├── src/
│   │   ├── modules/                # Core logic broken into ES modules
│   │   │   ├── api.js              # Fetch wrapper with auto-auth headers
│   │   │   ├── app.js              # Page lifecycle and main render logic
│   │   │   ├── auth.js             # Authentication form handlers
│   │   │   ├── chat.js             # Core chat engine (Polling, rendering)
│   │   │   ├── config.js           # Env variables and STUN server config
│   │   │   ├── dom.js              # DOM manipulation and XSS prevention
│   │   │   ├── events.js           # Global event delegation
│   │   │   ├── router.js           # PushState client-side router
│   │   │   ├── state.js            # Global state manager & LocalStorage sync
│   │   │   ├── status.js           # Presence and read receipt UI logic
│   │   │   └── voice.js            # Web Audio API MediaRecorder logic
│   │   └── styles/
│   │       └── app.css             # Comprehensive design system (63KB+)
│   ├── index.html                  # Main SPA entry point
│   ├── vite.config.js              # Dev server configuration
│   ├── .env.example
│   └── package.json
│
├── database/
│   └── schema.sql                  # Complete raw SQL database dump
├── docs/                           # Extended internal documentation
├── screenshots/                    # UI showcases
└── start-dev.bat                   # 1-Click Windows Development Launcher

6. Comprehensive Installation Guide

Prerequisites

  • PHP 8.1 or higher (Extensions: pdo_mysql, mbstring, openssl)
  • Composer 2.x
  • Node.js 18+ and npm 9+
  • MySQL 8.0+ (or MariaDB 10.4+)
  • XAMPP/MAMP/WAMP (optional, recommended for local dev)

Step 1: Clone the Repository

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

Step 2: Database Initialization

Create your database and import the schema:

CREATE DATABASE IF NOT EXISTS chat_app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE chat_app;
SOURCE d:/path/to/chat_app_full/database/schema.sql;

Step 3: Backend Setup

cd backend
composer install
cp .env.example .env
php artisan key:generate

Update your .env file with your MySQL credentials.

Step 4: Frontend Setup

cd ../frontend
npm install
cp .env.example .env

Ensure your VITE_API_BASE_URL points to your local Laravel server.

Step 5: Launch the Application

If you are on Windows, simply double-click the start-dev.bat file in the root directory. It will launch the Laravel server, the Vite frontend server, and the PHP signaling server in separate command windows.

To run manually:

  1. API Server: cd backend && php -S 127.0.0.1:8001 -t public public/index.php
  2. Frontend: cd frontend && npm run dev
  3. Signaling Server: cd backend && php realtime/call_signal_server.php

Open http://localhost:5173 in your browser.


7. Environment Configuration

Backend (backend/.env)

Variable Description Example
APP_ENV Application environment local / production
APP_KEY Encryption key base64:...
APP_DEBUG Enable debug logs true
APP_URL Base URL of the API http://127.0.0.1:8001
DB_CONNECTION Database driver mysql
DB_HOST Database host IP 127.0.0.1
DB_PORT Database port 3306
DB_DATABASE Database name chat_app
DB_USERNAME Database user root
DB_PASSWORD Database password secret
SANCTUM_STATEFUL_DOMAINS Domains allowed to access API localhost:5173,127.0.0.1:5173

Frontend (frontend/.env)

Variable Description Default
VITE_API_BASE_URL Laravel API Endpoint http://127.0.0.1:8001/api
VITE_GIPHY_API_KEY Optional Giphy Key your_giphy_key
VITE_SIGNALING_WS_URL WebSocket signaling server ws://127.0.0.1:8081

8. Database Schema in Detail

users Table

Handles all user authentication and real-time presence markers.

CREATE TABLE users (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    is_online TINYINT(1) DEFAULT 0,
    last_seen_at TIMESTAMP NULL,
    created_at TIMESTAMP NULL,
    updated_at TIMESTAMP NULL
);

messages Table

The central nervous system of the application, holding all chat data.

CREATE TABLE messages (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    sender_id BIGINT UNSIGNED NOT NULL,
    receiver_id BIGINT UNSIGNED NOT NULL,
    type VARCHAR(50) DEFAULT 'text', -- text, gif, sticker, image, file, audio, voice
    content TEXT NULL,
    media_url LONGTEXT NULL,
    duration_seconds INT NULL,
    status ENUM('sent', 'delivered', 'read') DEFAULT 'sent',
    delivered_at TIMESTAMP NULL,
    read_at TIMESTAMP NULL,
    created_at TIMESTAMP NULL,
    updated_at TIMESTAMP NULL,
    FOREIGN KEY (sender_id) REFERENCES users(id),
    FOREIGN KEY (receiver_id) REFERENCES users(id)
);

call_sessions Table

Persists WebRTC states allowing recovery and audit trails of calls.

CREATE TABLE call_sessions (
    id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    caller_id BIGINT UNSIGNED NOT NULL,
    callee_id BIGINT UNSIGNED NOT NULL,
    type VARCHAR(50) DEFAULT 'audio', -- audio or video
    status VARCHAR(50) DEFAULT 'ringing', -- ringing, accepted, declined, ended
    offer_sdp LONGTEXT NULL,
    answer_sdp LONGTEXT NULL,
    caller_candidates JSON NULL,
    callee_candidates JSON NULL,
    created_at TIMESTAMP NULL,
    updated_at TIMESTAMP NULL,
    FOREIGN KEY (caller_id) REFERENCES users(id),
    FOREIGN KEY (callee_id) REFERENCES users(id)
);

9. Comprehensive API Reference

All requests must have the Accept: application/json header. Protected endpoints require the Authorization: Bearer {token} header.

Authentication Endpoints

  • POST /api/register
    • Payload: name, email, password, password_confirmation
    • Returns: { user: {...}, token: "..." }
  • POST /api/login
    • Payload: email, password
    • Returns: { user: {...}, token: "..." }
  • POST /api/logout (Protected)
    • Revokes current Sanctum token.

Presence & Users Endpoints (Protected)

  • GET /api/users
    • Returns a list of all registered users excluding the current user. Includes is_online flags evaluated against last_seen_at.
  • POST /api/presence/heartbeat
    • Hit by the frontend every 30 seconds. Updates the user's last_seen_at to now().

Messaging Endpoints (Protected)

  • GET /api/messages/{userId}
    • Fetches the conversation thread between the authenticated user and {userId}.
  • POST /api/messages
    • Payload Example: { receiver_id: 2, type: 'text', content: 'Hello World' }
    • Media Payload: { receiver_id: 2, type: 'image', media_url: 'data:image/png;base64,...' }
    • Returns: The newly created Message object.
  • POST /api/messages/delivered
    • Marks pending messages in the database as delivered.
  • POST /api/messages/{userId}/read
    • Upgrades messages from {userId} to read status.

WebRTC Call Endpoints (Protected)

  • POST /api/calls: Initiates a new call session.
  • POST /api/calls/{id}/offer: Submits the SDP offer.
  • POST /api/calls/{id}/answer: Submits the SDP answer.
  • POST /api/calls/{id}/candidate: Transmits ICE candidates.
  • POST /api/calls/{id}/end: Terminates the call session.

10. Frontend Modules & Event Flow

api.js Core Wrapper

A robust wrapper around the native fetch API. It automatically retrieves the token from localStorage and appends it to headers. If a 401 Unauthorized response is intercepted, it clears local state and forces a redirect to the /login route, protecting against expired sessions.

The Polling Engine (chat.js)

Rather than keeping a persistent WebSocket connection open for chat data, chat.js initiates a setInterval that triggers every 1.2 seconds.

  1. It calls GET /api/messages/{activeUserId}.
  2. It compares the response array length against the local DOM.
  3. If new messages exist, it updates the local state and triggers POST /api/messages/delivered automatically.
  4. If the active tab is focused, it triggers POST /api/messages/read.

Web Audio Integration (voice.js)

Utilizes navigator.mediaDevices.getUserMedia({ audio: true }).

  • Implements MediaRecorder to capture audio chunks.
  • A requestAnimationFrame loop updates the UI recording timer.
  • On completion, chunks are assembled into a Blob, converted to Base64 using FileReader, and sent to the API.

11. Backend Architecture

The Controller Pattern

Laravel controllers are kept extremely thin, strictly handling request validation and delegating database queries to Eloquent Models.

  • ChatController.php strictly enforces privacy using a where(function($q) use ($authId, $targetId) { ... }) wrapper to ensure User C cannot query messages belonging to User A and B.

Sanctum Stateless Auth

Unlike traditional Laravel sessions, Sanctum provides lightweight Personal Access Tokens. These are issued upon login, stored in the frontend's localStorage, and validated on every request without requiring PHP session files.


12. Real-time Communication Flow (WebRTC & WebSockets)

The PHP Signaling Daemon

Located at realtime/call_signal_server.php, this script binds a socket listener using stream_socket_server('tcp://0.0.0.0:8081').

  • Handles raw TCP packet framing to decode WebSocket protocols (RFC 6455).
  • Maintains an in-memory array of $clients mapped to $user_id.
  • Instantly proxies JSON payloads between Caller and Callee without touching MySQL.

Call Setup Sequence

  1. Alice wants to call Bob.
  2. Alice's browser creates a RTCPeerConnection, generating an Offer SDP.
  3. Alice sends this Offer to the PHP WebSocket server, targeting Bob's ID.
  4. The PHP Server relays the message instantly to Bob's open WebSocket connection.
  5. Bob's UI shows a ringing screen. Bob clicks Accept.
  6. Bob's browser processes the Offer, generates an Answer SDP, and sends it back.
  7. Both clients exchange network paths (ICE Candidates) via the server.
  8. Connection Established: P2P Audio/Video stream begins directly between Alice and Bob. Server traffic drops to zero.

13. Extensive Testing Guide

Manual QA & Feature Verification

Testing Text Messaging & Privacy

  1. Open two Incognito browsers.
  2. Register User A in Browser 1, User B in Browser 2.
  3. User A sends "Hello". Ensure the bubble appears instantly (Optimistic UI) with a Single Tick ✓.
  4. Once User B's polling fetches it, User A's tick should become a Double Tick ✓✓.
  5. Register User C in Browser 3. User C must NOT be able to intercept or view User A/B messages.

Testing WebRTC Calls

  1. Ensure the signaling server is running in the background.
  2. User A clicks the "Audio Call" icon next to User B's name.
  3. User B's screen should immediately show an incoming call overlay.
  4. User B accepts. Grant microphone permissions in both browsers.
  5. Verify audio transmission using headphones to avoid feedback loops.

Testing Online Status & Heartbeat

  1. View User B's profile from User A's screen. It should show a Green dot ("Online").
  2. Close User B's browser completely.
  3. Wait exactly 45-60 seconds.
  4. User A's screen should automatically update User B's status to "Last seen 1 minute ago".

14. Deployment & Hosting

14.1 Production Frontend (Vite)

Build the frontend for production:

cd frontend
npm run build

This generates a dist/ directory. Deploy this directory to a static host such as:

  • Vercel
  • Netlify
  • AWS S3 + CloudFront
  • Nginx (Serving as static files)

14.2 Production Backend (Laravel)

Deploying the PHP API to a VPS (DigitalOcean, AWS EC2, or Heroku):

  1. Configure your web server (Nginx/Apache) to map the domain to the backend/public/ folder.
  2. Run composer install --optimize-autoloader --no-dev.
  3. Set APP_ENV=production and APP_DEBUG=false in .env.
  4. Ensure storage/ and bootstrap/cache/ directories have writable permissions (chmod -R 775 storage).
  5. Run php artisan storage:link to expose uploaded media to the web.

14.3 Daemonizing the WebSocket Server

The PHP WebSocket server must run perpetually. Use Supervisor on Linux:

[program:chat-signaling]
process_name=%(program_name)s
command=php /var/www/chat_app_full/backend/realtime/call_signal_server.php
autostart=true
autorestart=true
user=www-data
redirect_stderr=true
stdout_logfile=/var/log/chat-signaling.log

15. Security & Privacy Model

  • Media Upload Limits: Base64 strings are aggressively validated. Requests exceeding 500KB for images or 5MB for voice are instantly rejected to prevent payload bombs.
  • SQL Injection Prevention: Eloquent ORM is used strictly across all controllers; raw queries are entirely avoided.
  • Cross-Site Scripting (XSS): The frontend dom.js module implements strict character escaping str.replace(/[&<>'"]/g, ...) before injecting any user-generated content into innerHTML.
  • Stateless Tokens: Auth tokens are stored in memory and localStorage—there are no cookies vulnerable to CSRF attacks.

16. Performance Optimization

  • Database Indexes: The messages table is optimized with composite indexes on sender_id and receiver_id to make the WHERE clauses blazing fast during the 1.2s polling cycle.
  • Optimistic Rendering: The frontend doesn't wait for server confirmation to paint the DOM. This provides an illusion of zero-latency messaging.
  • Vite Bundling: All CSS and JS are minified and bundled into single artifacts, heavily reducing HTTP request bottlenecks on initial page load.

17. Contributing & Code of Conduct

We welcome contributions from the open-source community!

How to Contribute

  1. Fork the repository on GitHub.
  2. Clone your fork locally.
  3. Create a Feature Branch (git checkout -b feature/amazing-feature).
  4. Write your code and ensure you adhere to the existing architectural patterns.
  5. Commit your changes (git commit -m 'Add some amazing feature').
  6. Push to the branch (git push origin feature/amazing-feature).
  7. Open a Pull Request against the main branch.

Please ensure all PRs include relevant updates to documentation and follow the existing coding standard (PSR-12 for PHP, ES6 modular patterns for JS).


18. Roadmap & Future Scope

While the current application is highly functional, the following features are planned for future iterations:

  • [ ] Group Chats: Extending the database schema to support many-to-many message distributions.
  • [ ] WebSockets for Text: Transitioning the 1.2s HTTP polling mechanism to full WebSockets for text messaging, completely eliminating HTTP overhead.
  • [ ] End-to-End Encryption (E2EE): Implementing the Signal Protocol in JavaScript to encrypt text payloads before they hit the Laravel server.
  • [ ] Push Notifications: Integrating Web Push API and Service Workers to notify offline users of new messages.
  • [ ] Read Receipts for Voice Notes: Distinct blue microphone icons when a voice note is actually played.

19. Frequently Asked Questions (FAQ)

Q: Why use Polling instead of WebSockets for text messages? A: In standard VPS hosting environments, keeping 10,000 idle WebSocket connections open for text chatting consumes massive memory. Short-polling every 1.2s, combined with Optimistic UI, scales incredibly well on standard Apache/Nginx setups without needing a dedicated Redis/Node.js socket infrastructure. We reserve heavy WebSockets only for WebRTC where it is strictly required.

Q: Can I use React or Vue instead of Vanilla JS? A: Yes. The frontend is completely decoupled from the Laravel API. You can safely delete the frontend folder and build a React SPA pointing to the same /api endpoints.

Q: Why is my WebSocket server crashing? A: Ensure your server firewall (e.g., UFW or AWS Security Groups) has TCP Port 8081 open to the public. If it crashes, use a process manager like Supervisor to auto-restart it.


20. License

This project is fully open-source and available under the MIT License.

You are free to use, modify, distribute, and commercialize this software as long as you include the original copyright and license notice.


Built with ❤️ for the open-source community. Let's Chat!


21. Complete Source Code Reference

The following sections contain the complete source code for the application for exhaustive reference.

File: backend\composer.json

`json { "name": "chat-app/backend", "type": "project", "description": "Laravel Chat App Backend", "require": { "php": "^8.1", "laravel/framework": "^10.0", "laravel/sanctum": "^3.2" }, "require-dev": { "fakerphp/faker": "^1.9", "phpunit/phpunit": "^10.0" }, "autoload": { "psr-4": { "App\": "app/", "Database\Factories\": "database/factories/", "Database\Seeders\": "database/seeders/" } }, "autoload-dev": { "psr-4": { "Tests\": "tests/" } }, "scripts": { "post-autoload-dump": [ "Illuminate\Foundation\ComposerScripts::postAutoloadDump", "@php artisan package:discover --ansi" ], "post-root-package-install": [ "@php -r "file_exists('.env') || copy('.env.example', '.env');"" ], "post-create-project-cmd": [ "@php artisan key:generate --ansi" ] }, "minimum-stability": "stable", "prefer-stable": true }

`

File: backend\app\Console\Kernel.php

`php

load(__DIR__.'/Commands'); if (file_exists(base_path('routes/console.php'))) { require base_path('routes/console.php'); } } } ` ### File: backend\app\Exceptions\Handler.php `php reportable(function (Throwable $e) { // }); } } ` ### File: backend\app\Http\Kernel.php `php [ \App\Http\Middleware\EncryptCookies::class, \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, \Illuminate\Session\Middleware\StartSession::class, \Illuminate\View\Middleware\ShareErrorsFromSession::class, \App\Http\Middleware\VerifyCsrfToken::class, \Illuminate\Routing\Middleware\SubstituteBindings::class, ], 'api' => [ 'throttle:api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], ]; protected $middlewareAliases = [ 'auth' => \App\Http\Middleware\Authenticate::class, 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class, 'can' => \Illuminate\Auth\Middleware\Authorize::class, 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class, 'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class, 'signed' => \App\Http\Middleware\ValidateSignature::class, 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, ]; } ` ### File: backend\app\Http\Controllers\AuthController.php `php validated(); $payload = [ 'name' => $validated['name'], 'email' => $validated['email'], 'password' => Hash::make($validated['password']), ]; if ($this->supportsPresenceColumns()) { $payload['is_online'] = true; $payload['last_seen_at'] = now(); } $user = User::create($payload); $token = $user->createToken('auth_token')->plainTextToken; return response()->json([ 'message' => 'User registered successfully', 'user' => $user, 'token' => $token, ], 201); } // Login user public function login(LoginRequest $request) { $validated = $request->validated(); $user = User::where('email', $validated['email'])->first(); if (!$user || !Hash::check($validated['password'], $user->password)) { throw ValidationException::withMessages([ 'email' => ['The provided credentials are incorrect.'], ]); } // Revoke old tokens $user->tokens()->delete(); $this->touchPresence($user, true); $token = $user->createToken('auth_token')->plainTextToken; return response()->json([ 'message' => 'Login successful', 'user' => $user, 'token' => $token, ]); } // Logout user public function logout(Request $request) { $this->touchPresence($request->user(), false); $request->user()->currentAccessToken()->delete(); return response()->json([ 'message' => 'Logged out successfully', ]); } // Get authenticated user public function me(Request $request) { $user = $request->user(); $this->touchPresence($user, true); return response()->json($user->fresh()); } private function supportsPresenceColumns(): bool { static $supported = null; if ($supported === null) { $supported = Schema::hasColumn('users', 'is_online') && Schema::hasColumn('users', 'last_seen_at'); } return $supported; } private function touchPresence(User $user, bool $isOnline): void { if (!$this->supportsPresenceColumns()) { return; } $user->forceFill([ 'is_online' => $isOnline, 'last_seen_at' => now(), ])->save(); } } ` ### File: backend\app\Http\Controllers\CallController.php `php caller_id === $userId || $session->callee_id === $userId, 403, 'You are not part of this call session.' ); } private function serializeSession(CallSession $session) { $session->loadMissing([ 'caller:id,name,email', 'callee:id,name,email', ]); return response()->json($session); } public function current(Request $request) { $userId = $request->user()->id; $session = CallSession::with([ 'caller:id,name,email', 'callee:id,name,email', ]) ->whereIn('status', ['ringing', 'accepted']) ->where(function ($query) use ($userId) { $query->where('caller_id', $userId) ->orWhere('callee_id', $userId); }) ->latest('id') ->first(); return response()->json($session); } public function store(StoreCallRequest $request) { $session = CallSession::create([ 'caller_id' => $request->user()->id, 'callee_id' => $request->callee_id, 'type' => $request->type, 'status' => 'ringing', 'caller_candidates' => [], 'callee_candidates' => [], ]); return $this->serializeSession($session); } public function show(Request $request, CallSession $call) { $this->authorizeParticipant($call, $request->user()->id); return $this->serializeSession($call); } public function offer(OfferCallRequest $request, CallSession $call) { abort_unless($call->caller_id === $request->user()->id, 403, 'Only the caller can send the offer.'); $call->offer_sdp = $request->offer_sdp; $call->save(); return $this->serializeSession($call); } public function answer(AnswerCallRequest $request, CallSession $call) { abort_unless($call->callee_id === $request->user()->id, 403, 'Only the callee can answer this call.'); $call->answer_sdp = $request->answer_sdp; $call->status = 'accepted'; $call->started_at = now(); $call->save(); return $this->serializeSession($call); } public function candidate(CandidateCallRequest $request, CallSession $call) { $userId = $request->user()->id; $this->authorizeParticipant($call, $userId); if ($call->caller_id === $userId) { $candidates = $call->caller_candidates ?? []; $candidates[] = $request->candidate; $call->caller_candidates = $candidates; } else { $candidates = $call->callee_candidates ?? []; $candidates[] = $request->candidate; $call->callee_candidates = $candidates; } $call->save(); return $this->serializeSession($call); } public function decline(Request $request, CallSession $call) { abort_unless($call->callee_id === $request->user()->id, 403, 'Only the callee can decline this call.'); $call->status = 'declined'; $call->ended_at = now(); $call->save(); return $this->serializeSession($call); } public function end(Request $request, CallSession $call) { $this->authorizeParticipant($call, $request->user()->id); $call->status = 'ended'; $call->ended_at = now(); $call->save(); return $this->serializeSession($call); } } ` ### File: backend\app\Http\Controllers\ChatController.php `php user(); $supportsPresence = $this->supportsPresenceColumns(); $userSelect = ['id', 'name', 'email', 'created_at']; if ($supportsPresence) { $userSelect[] = 'is_online'; $userSelect[] = 'last_seen_at'; } $users = User::where('id', '!=', $authUser->id) ->select($userSelect) ->orderBy('name', 'asc') ->get(); if ($supportsPresence) { $presenceCutoff = now()->subSeconds(45); $users->transform(function (User $user) use ($presenceCutoff) { $user->is_online = (bool) $user->is_online && $user->last_seen_at !== null && $user->last_seen_at->greaterThan($presenceCutoff); return $user; }); } else { $users->transform(function (User $user) { $user->is_online = false; $user->last_seen_at = null; return $user; }); } $selectedUserId = (int) $request->query('selected_user_id', 0); $selectedUser = $selectedUserId > 0 ? $users->firstWhere('id', $selectedUserId) : null; if (!$selectedUser) { $selectedUser = $users->sortBy([ ['is_online', 'desc'], ['last_seen_at', 'desc'], ['name', 'asc'], ])->first(); } $messages = collect(); if ($selectedUser) { $this->markConversationAsRead($authUser->id, $selectedUser->id); $messages = $this->conversationMessages($authUser->id, $selectedUser->id); } return response()->json([ 'user' => $authUser, 'users' => $users->values(), 'selected_user_id' => $selectedUser?->id, 'messages' => $messages, ]); } // Get all messages between authenticated user and another user public function getMessages(ConversationUserRequest $request, User $user) { $authUserId = $request->user()->id; $otherUserId = $user->id; $this->markConversationAsRead($authUserId, $otherUserId); return response()->json($this->conversationMessages($authUserId, $otherUserId)); } // Send a message public function sendMessage(StoreMessageRequest $request) { $payload = [ 'sender_id' => $request->user()->id, 'receiver_id' => $request->receiver_id, 'type' => $request->input('type', 'text'), 'content' => $request->content, 'media_url' => $this->storeMediaDataUrl($request->media_url), ]; if (Schema::hasColumn('messages', 'duration_seconds')) { $payload['duration_seconds'] = $request->duration_seconds; } $message = Message::create($payload); $message->load(['sender:id,name', 'receiver:id,name']); $message = $this->normalizeMessageMediaUrl($message); return response()->json([ 'message' => 'Message sent successfully', 'data' => $message, ], 201); } public function serveMedia(string $filename): BinaryFileResponse { $safeFilename = basename($filename); $absolutePath = public_path(self::LOCAL_MEDIA_DIRECTORY . DIRECTORY_SEPARATOR . $safeFilename); abort_unless( $safeFilename !== '' && File::exists($absolutePath) && str_starts_with(realpath($absolutePath) ?: '', realpath(public_path(self::LOCAL_MEDIA_DIRECTORY)) ?: ''), 404 ); $fallbackMime = File::mimeType($absolutePath) ?: 'application/octet-stream'; return response()->file($absolutePath, [ 'Content-Type' => $this->guessResponseMimeFromFilename($safeFilename, $fallbackMime), 'Cache-Control' => 'public, max-age=31536000, immutable', 'Accept-Ranges' => 'bytes', ]); } public function clearMessages(ConversationUserRequest $request, User $user) { $authUserId = $request->user()->id; Message::where(function ($query) use ($authUserId, $user) { $query->where('sender_id', $authUserId) ->where('receiver_id', $user->id); }) ->orWhere(function ($query) use ($authUserId, $user) { $query->where('sender_id', $user->id) ->where('receiver_id', $authUserId); }) ->delete(); return response()->json([ 'message' => 'Conversation cleared successfully', ]); } public function markDelivered(Request $request): JsonResponse { if (!$this->supportsMessageStatusColumns()) { return response()->json([ 'message' => 'Message status columns are not available in this database yet.', ]); } $timestamp = now(); Message::where('receiver_id', $request->user()->id) ->whereNull('delivered_at') ->update([ 'delivered_at' => $timestamp, 'updated_at' => $timestamp, ]); return response()->json([ 'message' => 'Pending messages marked as delivered.', ]); } public function markRead(ConversationUserRequest $request, User $user): JsonResponse { $this->markConversationAsRead($request->user()->id, $user->id); return response()->json([ 'message' => 'Conversation marked as read.', ]); } private function markConversationAsRead(int $authUserId, int $otherUserId): void { if (!$this->supportsMessageStatusColumns()) { return; } $timestamp = now(); Message::where('sender_id', $otherUserId) ->where('receiver_id', $authUserId) ->whereNull('read_at') ->update([ 'delivered_at' => $timestamp, 'read_at' => $timestamp, 'updated_at' => $timestamp, ]); } private function conversationMessages(int $authUserId, int $otherUserId) { $messages = Message::where(function ($query) use ($authUserId, $otherUserId) { $query->where(function ($q) use ($authUserId, $otherUserId) { $q->where('sender_id', $authUserId) ->where('receiver_id', $otherUserId); }) ->orWhere(function ($q) use ($authUserId, $otherUserId) { $q->where('sender_id', $otherUserId) ->where('receiver_id', $authUserId); }); }) ->with(['sender:id,name', 'receiver:id,name']) ->orderBy('created_at', 'asc') ->get(); $messages->transform(function (Message $message) { return $this->normalizeMessageMediaUrl($message); }); return $messages; } private function supportsMessageStatusColumns(): bool { static $supported = null; if ($supported === null) { $supported = Schema::hasColumn('messages', 'delivered_at') && Schema::hasColumn('messages', 'read_at'); } return $supported; } private function supportsPresenceColumns(): bool { static $supported = null; if ($supported === null) { $supported = Schema::hasColumn('users', 'is_online') && Schema::hasColumn('users', 'last_seen_at'); } return $supported; } private function normalizeMessageMediaUrl(Message $message): Message { if (!is_string($message->media_url) || trim($message->media_url) === '') { return $message; } $storedPath = $this->normalizeStoredMediaReference($message->media_url, $message->id); if ($storedPath !== $message->media_url) { $message->forceFill([ 'media_url' => $storedPath, ]); $message->saveQuietly(); } $publicUrl = $this->buildPublicMediaUrl($message->media_url); if ($publicUrl !== null) { $message->media_url = $publicUrl; } return $message; } private function storeMediaDataUrl(?string $mediaUrl, ?int $messageId = null): ?string { if (!is_string($mediaUrl) || !str_starts_with($mediaUrl, 'data:')) { return $mediaUrl; } $parsedMedia = $this->parseDataUrl($mediaUrl); if ($parsedMedia === null) { return $mediaUrl; } $directory = public_path(self::LOCAL_MEDIA_DIRECTORY); if (!File::isDirectory($directory)) { File::ensureDirectoryExists($directory); } $extension = $this->guessExtensionFromMime($parsedMedia['mime']); $filename = $messageId ? "message-{$messageId}.{$extension}" : 'message-' . Str::uuid() . ".{$extension}"; $absolutePath = $directory . DIRECTORY_SEPARATOR . $filename; if (!File::exists($absolutePath)) { File::put($absolutePath, $parsedMedia['data']); } return self::LOCAL_MEDIA_DIRECTORY . "/{$filename}"; } private function normalizeStoredMediaReference(?string $mediaUrl, ?int $messageId = null): ?string { if (!is_string($mediaUrl) || trim($mediaUrl) === '') { return $mediaUrl; } if (str_starts_with($mediaUrl, 'data:')) { return $this->storeMediaDataUrl($mediaUrl, $messageId); } $filename = $this->extractStoredMediaFilename($mediaUrl); if ($filename === null) { return $mediaUrl; } return self::LOCAL_MEDIA_DIRECTORY . "/{$filename}"; } private function buildPublicMediaUrl(?string $mediaUrl): ?string { $filename = $this->extractStoredMediaFilename($mediaUrl); if ($filename === null) { return null; } return '/api/media/' . rawurlencode($filename); } private function extractStoredMediaFilename(?string $mediaUrl): ?string { if (!is_string($mediaUrl) || trim($mediaUrl) === '') { return null; } $path = $mediaUrl; if (filter_var($mediaUrl, FILTER_VALIDATE_URL)) { $path = parse_url($mediaUrl, PHP_URL_PATH) ?: ''; } $normalizedPath = str_replace('\\', '/', trim($path)); $marker = '/' . self::LOCAL_MEDIA_DIRECTORY . '/'; if (str_contains($normalizedPath, $marker)) { return basename(substr($normalizedPath, strpos($normalizedPath, $marker) + strlen($marker))); } if (str_starts_with($normalizedPath, self::LOCAL_MEDIA_DIRECTORY . '/')) { return basename(substr($normalizedPath, strlen(self::LOCAL_MEDIA_DIRECTORY . '/'))); } return null; } private function parseDataUrl(string $mediaUrl): ?array { $commaPosition = strpos($mediaUrl, ','); if ($commaPosition === false) { return null; } $metadata = substr($mediaUrl, 5, $commaPosition - 5); $encoded = substr($mediaUrl, $commaPosition + 1); if (!str_contains($metadata, ';base64')) { return null; } $mime = trim(explode(';', $metadata)[0] ?? ''); $decoded = base64_decode($encoded, true); if ($mime === '' || $decoded === false) { return null; } return [ 'mime' => $mime, 'data' => $decoded, ]; } private function guessExtensionFromMime(string $mime): string { $normalizedMime = strtolower(trim(explode(';', $mime)[0] ?? '')); return match ($normalizedMime) { 'image/jpeg' => 'jpg', 'image/png' => 'png', 'image/webp' => 'webp', 'image/gif' => 'gif', 'audio/webm' => 'webm', 'audio/mp4', 'audio/x-m4a' => 'm4a', 'audio/mpeg' => 'mp3', 'audio/wav' => 'wav', 'audio/aac', 'audio/mp4a-latm' => 'aac', 'audio/ogg' => 'ogg', 'video/mp4' => 'mp4', 'application/pdf' => 'pdf', default => 'bin', }; } private function guessResponseMimeFromFilename(string $filename, string $fallbackMime): string { return match (strtolower(pathinfo($filename, PATHINFO_EXTENSION))) { 'jpg', 'jpeg' => 'image/jpeg', 'png' => 'image/png', 'gif' => 'image/gif', 'webp' => 'image/webp', 'pdf' => 'application/pdf', 'webm' => 'audio/webm', 'mp3' => 'audio/mpeg', 'wav' => 'audio/wav', 'ogg' => 'audio/ogg', 'm4a', 'mp4' => 'audio/mp4', 'aac' => 'audio/aac', default => $fallbackMime, }; } } ` ### File: backend\app\Http\Controllers\Controller.php `php supportsPresenceColumns(); if ($supportsPresence) { $select[] = 'is_online'; $select[] = 'last_seen_at'; } $users = User::where('id', '!=', $request->user()->id) ->select($select) ->orderBy('name', 'asc') ->get(); if (!$supportsPresence) { $users->transform(function (User $user) { $user->is_online = false; $user->last_seen_at = null; return $user; }); return response()->json($users); } $presenceCutoff = now()->subSeconds(45); $users->transform(function (User $user) use ($presenceCutoff) { $user->is_online = (bool) $user->is_online && $user->last_seen_at !== null && $user->last_seen_at->greaterThan($presenceCutoff); return $user; }); return response()->json($users); } public function heartbeat(Request $request) { if (!$this->supportsPresenceColumns()) { return response()->json([ 'ok' => false, 'message' => 'Presence columns are not available in this database yet.', ]); } $request->user()->forceFill([ 'is_online' => true, 'last_seen_at' => now(), ])->save(); return response()->json([ 'ok' => true, 'last_seen_at' => $request->user()->last_seen_at, ]); } private function supportsPresenceColumns(): bool { static $supported = null; if ($supported === null) { $supported = Schema::hasColumn('users', 'is_online') && Schema::hasColumn('users', 'last_seen_at'); } return $supported; } } ` ### File: backend\app\Http\Middleware\Authenticate.php `php expectsJson()) { return null; } return null; } protected function unauthenticated($request, array $guards) { abort(response()->json([ 'message' => 'Unauthenticated. Please login.' ], 401)); } } ` ### File: backend\app\Http\Middleware\EncryptCookies.php `php check()) { return response()->json([ 'message' => 'Already authenticated.', ], 409); } } return $next($request); } } ` ### File: backend\app\Http\Middleware\TrimStrings.php `php merge([ 'email' => strtolower(trim((string) $this->input('email', ''))), 'password' => (string) $this->input('password', ''), ]); } public function authorize(): bool { return true; } public function rules(): array { return [ 'email' => 'required|email', 'password' => 'required|string', ]; } } ` ### File: backend\app\Http\Requests\Auth\RegisterRequest.php `php merge([ 'name' => trim((string) $this->input('name', '')), 'email' => strtolower(trim((string) $this->input('email', ''))), 'password' => (string) $this->input('password', ''), 'password_confirmation' => (string) $this->input('password_confirmation', ''), ]); } public function authorize(): bool { return true; } public function rules(): array { return [ 'name' => 'required|string|max:255', 'email' => 'required|email|unique:users,email', 'password' => 'required|string|min:6|confirmed', ]; } } ` ### File: backend\app\Http\Requests\Calls\AnswerCallRequest.php `php 'required|string', ]; } } ` ### File: backend\app\Http\Requests\Calls\CandidateCallRequest.php `php 'required|array', ]; } } ` ### File: backend\app\Http\Requests\Calls\OfferCallRequest.php `php 'required|string', ]; } } ` ### File: backend\app\Http\Requests\Calls\StoreCallRequest.php `php 'required|exists:users,id|different:' . $this->user()->id, 'type' => 'required|in:voice,video', ]; } } ` ### File: backend\app\Http\Requests\Chat\ConversationUserRequest.php `php merge([ 'conversation_user_id' => $this->route('user')?->id, ]); } public function rules(): array { return [ 'conversation_user_id' => 'required|exists:users,id|different:' . $this->user()->id, ]; } } ` ### File: backend\app\Http\Requests\Chat\StoreMessageRequest.php `php 'required|exists:users,id|different:' . $this->user()->id, 'type' => 'nullable|in:text,gif,sticker,image,file,audio,voice', 'content' => 'required_without:media_url|nullable|string|max:5000', 'media_url' => 'required_without:content|nullable|string|max:500000', 'duration_seconds' => 'nullable|integer|min:1|max:3600', ]; } } ` ### File: backend\app\Models\CallSession.php `php 'array', 'callee_candidates' => 'array', 'started_at' => 'datetime', 'ended_at' => 'datetime', ]; public function caller() { return $this->belongsTo(User::class, 'caller_id'); } public function callee() { return $this->belongsTo(User::class, 'callee_id'); } } ` ### File: backend\app\Models\Message.php `php 'datetime', 'updated_at' => 'datetime', 'delivered_at' => 'datetime', 'read_at' => 'datetime', ]; // Sender relationship public function sender() { return $this->belongsTo(User::class, 'sender_id'); } // Receiver relationship public function receiver() { return $this->belongsTo(User::class, 'receiver_id'); } } ` ### File: backend\app\Models\User.php `php 'datetime', 'password' => 'hashed', 'is_online' => 'boolean', 'last_seen_at' => 'datetime', ]; // Messages sent by this user public function sentMessages() { return $this->hasMany(Message::class, 'sender_id'); } // Messages received by this user public function receivedMessages() { return $this->hasMany(Message::class, 'receiver_id'); } } ` ### File: backend\app\Providers\AppServiceProvider.php `php by($request->user()?->id ?: $request->ip()); }); $this->routes(function () { Route::middleware('api') ->prefix('api') ->group(base_path('routes/api.php')); Route::middleware('web') ->group(base_path('routes/web.php')); }); } } ` ### File: backend\bootstrap\app.php `php singleton( Illuminate\Contracts\Http\Kernel::class, App\Http\Kernel::class ); $app->singleton( Illuminate\Contracts\Console\Kernel::class, App\Console\Kernel::class ); $app->singleton( Illuminate\Contracts\Debug\ExceptionHandler::class, App\Exceptions\Handler::class ); return $app; ` ### File: backend\bootstrap\cache\packages.php `php array ( 'providers' => array ( 0 => 'Laravel\\Sanctum\\SanctumServiceProvider', ), ), 'nesbot/carbon' => array ( 'providers' => array ( 0 => 'Carbon\\Laravel\\ServiceProvider', ), ), 'nunomaduro/termwind' => array ( 'providers' => array ( 0 => 'Termwind\\Laravel\\TermwindServiceProvider', ), ), ); ` ### File: backend\bootstrap\cache\services.php `php array ( 0 => 'Illuminate\\Auth\\AuthServiceProvider', 1 => 'Illuminate\\Broadcasting\\BroadcastServiceProvider', 2 => 'Illuminate\\Bus\\BusServiceProvider', 3 => 'Illuminate\\Cache\\CacheServiceProvider', 4 => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 5 => 'Illuminate\\Cookie\\CookieServiceProvider', 6 => 'Illuminate\\Database\\DatabaseServiceProvider', 7 => 'Illuminate\\Encryption\\EncryptionServiceProvider', 8 => 'Illuminate\\Filesystem\\FilesystemServiceProvider', 9 => 'Illuminate\\Foundation\\Providers\\FoundationServiceProvider', 10 => 'Illuminate\\Hashing\\HashServiceProvider', 11 => 'Illuminate\\Mail\\MailServiceProvider', 12 => 'Illuminate\\Notifications\\NotificationServiceProvider', 13 => 'Illuminate\\Pagination\\PaginationServiceProvider', 14 => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider', 15 => 'Illuminate\\Pipeline\\PipelineServiceProvider', 16 => 'Illuminate\\Queue\\QueueServiceProvider', 17 => 'Illuminate\\Redis\\RedisServiceProvider', 18 => 'Illuminate\\Session\\SessionServiceProvider', 19 => 'Illuminate\\Translation\\TranslationServiceProvider', 20 => 'Illuminate\\Validation\\ValidationServiceProvider', 21 => 'Illuminate\\View\\ViewServiceProvider', 22 => 'Laravel\\Sanctum\\SanctumServiceProvider', 23 => 'Carbon\\Laravel\\ServiceProvider', 24 => 'Termwind\\Laravel\\TermwindServiceProvider', 25 => 'Laravel\\Sanctum\\SanctumServiceProvider', 26 => 'App\\Providers\\AppServiceProvider', 27 => 'App\\Providers\\RouteServiceProvider', ), 'eager' => array ( 0 => 'Illuminate\\Auth\\AuthServiceProvider', 1 => 'Illuminate\\Cookie\\CookieServiceProvider', 2 => 'Illuminate\\Database\\DatabaseServiceProvider', 3 => 'Illuminate\\Encryption\\EncryptionServiceProvider', 4 => 'Illuminate\\Filesystem\\FilesystemServiceProvider', 5 => 'Illuminate\\Foundation\\Providers\\FoundationServiceProvider', 6 => 'Illuminate\\Notifications\\NotificationServiceProvider', 7 => 'Illuminate\\Pagination\\PaginationServiceProvider', 8 => 'Illuminate\\Session\\SessionServiceProvider', 9 => 'Illuminate\\View\\ViewServiceProvider', 10 => 'Laravel\\Sanctum\\SanctumServiceProvider', 11 => 'Carbon\\Laravel\\ServiceProvider', 12 => 'Termwind\\Laravel\\TermwindServiceProvider', 13 => 'Laravel\\Sanctum\\SanctumServiceProvider', 14 => 'App\\Providers\\AppServiceProvider', 15 => 'App\\Providers\\RouteServiceProvider', ), 'deferred' => array ( 'Illuminate\\Broadcasting\\BroadcastManager' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider', 'Illuminate\\Contracts\\Broadcasting\\Factory' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider', 'Illuminate\\Contracts\\Broadcasting\\Broadcaster' => 'Illuminate\\Broadcasting\\BroadcastServiceProvider', 'Illuminate\\Bus\\Dispatcher' => 'Illuminate\\Bus\\BusServiceProvider', 'Illuminate\\Contracts\\Bus\\Dispatcher' => 'Illuminate\\Bus\\BusServiceProvider', 'Illuminate\\Contracts\\Bus\\QueueingDispatcher' => 'Illuminate\\Bus\\BusServiceProvider', 'Illuminate\\Bus\\BatchRepository' => 'Illuminate\\Bus\\BusServiceProvider', 'Illuminate\\Bus\\DatabaseBatchRepository' => 'Illuminate\\Bus\\BusServiceProvider', 'cache' => 'Illuminate\\Cache\\CacheServiceProvider', 'cache.store' => 'Illuminate\\Cache\\CacheServiceProvider', 'cache.psr6' => 'Illuminate\\Cache\\CacheServiceProvider', 'memcached.connector' => 'Illuminate\\Cache\\CacheServiceProvider', 'Illuminate\\Cache\\RateLimiter' => 'Illuminate\\Cache\\CacheServiceProvider', 'Illuminate\\Foundation\\Console\\AboutCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Cache\\Console\\ClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Cache\\Console\\ForgetCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ClearCompiledCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Auth\\Console\\ClearResetsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ConfigCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ConfigClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ConfigShowCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\DbCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\MonitorCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\PruneCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\ShowCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\TableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\WipeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\DownCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\EnvironmentCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\EnvironmentDecryptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\EnvironmentEncryptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\EventCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\EventClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\EventListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\KeyGenerateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\OptimizeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\OptimizeClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\PackageDiscoverCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Cache\\Console\\PruneStaleTagsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Queue\\Console\\ClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Queue\\Console\\ListFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Queue\\Console\\FlushFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Queue\\Console\\ForgetFailedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Queue\\Console\\ListenCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Queue\\Console\\MonitorCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Queue\\Console\\PruneBatchesCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Queue\\Console\\PruneFailedJobsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Queue\\Console\\RestartCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Queue\\Console\\RetryCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Queue\\Console\\RetryBatchCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Queue\\Console\\WorkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\RouteCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\RouteClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\RouteListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\DumpCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\Seeds\\SeedCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Console\\Scheduling\\ScheduleFinishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Console\\Scheduling\\ScheduleListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Console\\Scheduling\\ScheduleRunCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Console\\Scheduling\\ScheduleClearCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Console\\Scheduling\\ScheduleTestCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Console\\Scheduling\\ScheduleWorkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Console\\Scheduling\\ScheduleInterruptCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\ShowModelCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\StorageLinkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\StorageUnlinkCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\UpCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ViewCacheCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ViewClearCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Cache\\Console\\CacheTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\CastMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ChannelListCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ChannelMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ComponentMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ConsoleMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Routing\\Console\\ControllerMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\DocsCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\EventGenerateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\EventMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ExceptionMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\Factories\\FactoryMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\JobMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\LangPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ListenerMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\MailMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Routing\\Console\\MiddlewareMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ModelMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\NotificationMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Notifications\\Console\\NotificationTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ObserverMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\PolicyMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ProviderMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Queue\\Console\\FailedTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Queue\\Console\\TableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Queue\\Console\\BatchesTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\RequestMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ResourceMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\RuleMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ScopeMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\Seeds\\SeederMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Session\\Console\\SessionTableCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ServeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\StubPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\TestMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\VendorPublishCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Foundation\\Console\\ViewMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'migrator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'migration.repository' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'migration.creator' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\Migrations\\MigrateCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\Migrations\\FreshCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\Migrations\\InstallCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\Migrations\\RefreshCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\Migrations\\ResetCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\Migrations\\RollbackCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\Migrations\\StatusCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'Illuminate\\Database\\Console\\Migrations\\MigrateMakeCommand' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'composer' => 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider', 'hash' => 'Illuminate\\Hashing\\HashServiceProvider', 'hash.driver' => 'Illuminate\\Hashing\\HashServiceProvider', 'mail.manager' => 'Illuminate\\Mail\\MailServiceProvider', 'mailer' => 'Illuminate\\Mail\\MailServiceProvider', 'Illuminate\\Mail\\Markdown' => 'Illuminate\\Mail\\MailServiceProvider', 'auth.password' => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider', 'auth.password.broker' => 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider', 'Illuminate\\Contracts\\Pipeline\\Hub' => 'Illuminate\\Pipeline\\PipelineServiceProvider', 'pipeline' => 'Illuminate\\Pipeline\\PipelineServiceProvider', 'queue' => 'Illuminate\\Queue\\QueueServiceProvider', 'queue.connection' => 'Illuminate\\Queue\\QueueServiceProvider', 'queue.failer' => 'Illuminate\\Queue\\QueueServiceProvider', 'queue.listener' => 'Illuminate\\Queue\\QueueServiceProvider', 'queue.worker' => 'Illuminate\\Queue\\QueueServiceProvider', 'redis' => 'Illuminate\\Redis\\RedisServiceProvider', 'redis.connection' => 'Illuminate\\Redis\\RedisServiceProvider', 'translator' => 'Illuminate\\Translation\\TranslationServiceProvider', 'translation.loader' => 'Illuminate\\Translation\\TranslationServiceProvider', 'validator' => 'Illuminate\\Validation\\ValidationServiceProvider', 'validation.presence' => 'Illuminate\\Validation\\ValidationServiceProvider', 'Illuminate\\Contracts\\Validation\\UncompromisedVerifier' => 'Illuminate\\Validation\\ValidationServiceProvider', ), 'when' => array ( 'Illuminate\\Broadcasting\\BroadcastServiceProvider' => array ( ), 'Illuminate\\Bus\\BusServiceProvider' => array ( ), 'Illuminate\\Cache\\CacheServiceProvider' => array ( ), 'Illuminate\\Foundation\\Providers\\ConsoleSupportServiceProvider' => array ( ), 'Illuminate\\Hashing\\HashServiceProvider' => array ( ), 'Illuminate\\Mail\\MailServiceProvider' => array ( ), 'Illuminate\\Auth\\Passwords\\PasswordResetServiceProvider' => array ( ), 'Illuminate\\Pipeline\\PipelineServiceProvider' => array ( ), 'Illuminate\\Queue\\QueueServiceProvider' => array ( ), 'Illuminate\\Redis\\RedisServiceProvider' => array ( ), 'Illuminate\\Translation\\TranslationServiceProvider' => array ( ), 'Illuminate\\Validation\\ValidationServiceProvider' => array ( ), ), ); ` ### File: backend\config\app.php `php env('APP_NAME', 'Laravel'), 'env' => env('APP_ENV', 'production'), 'debug' => (bool) env('APP_DEBUG', false), 'url' => env('APP_URL', 'http://localhost'), 'asset_url' => env('ASSET_URL'), 'timezone' => 'UTC', 'locale' => 'en', 'fallback_locale' => 'en', 'faker_locale' => 'en_US', 'cipher' => 'AES-256-CBC', 'key' => env('APP_KEY'), 'maintenance' => [ 'driver' => 'file', ], 'providers' => [ Illuminate\Auth\AuthServiceProvider::class, Illuminate\Broadcasting\BroadcastServiceProvider::class, Illuminate\Bus\BusServiceProvider::class, Illuminate\Cache\CacheServiceProvider::class, Illuminate\Foundation\Providers\ConsoleSupportServiceProvider::class, Illuminate\Cookie\CookieServiceProvider::class, Illuminate\Database\DatabaseServiceProvider::class, Illuminate\Encryption\EncryptionServiceProvider::class, Illuminate\Filesystem\FilesystemServiceProvider::class, Illuminate\Foundation\Providers\FoundationServiceProvider::class, Illuminate\Hashing\HashServiceProvider::class, Illuminate\Mail\MailServiceProvider::class, Illuminate\Notifications\NotificationServiceProvider::class, Illuminate\Pagination\PaginationServiceProvider::class, Illuminate\Auth\Passwords\PasswordResetServiceProvider::class, Illuminate\Pipeline\PipelineServiceProvider::class, Illuminate\Queue\QueueServiceProvider::class, Illuminate\Redis\RedisServiceProvider::class, Illuminate\Session\SessionServiceProvider::class, Illuminate\Translation\TranslationServiceProvider::class, Illuminate\Validation\ValidationServiceProvider::class, Illuminate\View\ViewServiceProvider::class, Laravel\Sanctum\SanctumServiceProvider::class, App\Providers\AppServiceProvider::class, App\Providers\RouteServiceProvider::class, ], 'aliases' => Illuminate\Support\Facades\Facade::defaultAliases()->merge([ ])->toArray(), ]; ` ### File: backend\config\auth.php `php [ 'guard' => env('AUTH_GUARD', 'web'), 'passwords' => env('AUTH_PASSWORD_BROKER', 'users'), ], 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'sanctum' => [ 'driver' => 'sanctum', 'provider' => 'users', ], ], 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\Models\User::class, ], ], 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => env('AUTH_PASSWORD_RESET_TOKEN_TABLE', 'password_reset_tokens'), 'expire' => 60, 'throttle' => 60, ], ], 'password_timeout' => env('AUTH_PASSWORD_TIMEOUT', 10800), ]; ` ### File: backend\config\cache.php `php env('CACHE_DRIVER', 'file'), 'stores' => [ 'array' => [ 'driver' => 'array', 'serialize' => false, ], 'file' => [ 'driver' => 'file', 'path' => storage_path('framework/cache/data'), ], ], 'prefix' => env('CACHE_PREFIX', str(env('APP_NAME', 'laravel'))->slug('_').'_cache_'), ]; ` ### File: backend\config\cors.php `php ['api/*', 'sanctum/csrf-cookie'], 'allowed_methods' => ['*'], 'allowed_origins' => ['http://localhost:5173', 'http://127.0.0.1:5173'], 'allowed_origins_patterns' => [], 'allowed_headers' => ['*'], 'exposed_headers' => [], 'max_age' => 0, 'supports_credentials' => true, ]; ` ### File: backend\config\database.php `php env('DB_CONNECTION', 'mysql'), 'connections' => [ 'mysql' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::ATTR_TIMEOUT => (int) env('DB_CONNECT_TIMEOUT', 5), ]) : [], ], ], 'migrations' => 'migrations', 'redis' => [ 'client' => env('REDIS_CLIENT', 'phpredis'), 'options' => [ 'cluster' => env('REDIS_CLUSTER', 'redis'), 'prefix' => env('REDIS_PREFIX', str(env('APP_NAME', 'laravel'))->slug('_').'_database_'), ], 'default' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'username' => env('REDIS_USERNAME'), 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_DB', '0'), ], 'cache' => [ 'url' => env('REDIS_URL'), 'host' => env('REDIS_HOST', '127.0.0.1'), 'username' => env('REDIS_USERNAME'), 'password' => env('REDIS_PASSWORD'), 'port' => env('REDIS_PORT', '6379'), 'database' => env('REDIS_CACHE_DB', '1'), ], ], ]; ` ### File: backend\config\filesystems.php `php env('FILESYSTEM_DISK', 'local'), 'disks' => [ 'local' => [ 'driver' => 'local', 'root' => storage_path('app'), 'throw' => false, ], 'public' => [ 'driver' => 'local', 'root' => storage_path('app/public'), 'url' => env('APP_URL').'/storage', 'visibility' => 'public', 'throw' => false, ], ], 'links' => [ public_path('storage') => storage_path('app/public'), ], ]; ` ### File: backend\config\logging.php `php env('LOG_CHANNEL', 'stack'), 'deprecations' => [ 'channel' => env('LOG_DEPRECATIONS_CHANNEL', 'null'), 'trace' => env('LOG_DEPRECATIONS_TRACE', false), ], 'channels' => [ 'stack' => [ 'driver' => 'stack', 'channels' => explode(',', (string) env('LOG_STACK', 'single')), 'ignore_exceptions' => false, ], 'single' => [ 'driver' => 'single', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), ], 'daily' => [ 'driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 14, ], 'slack' => [ 'driver' => 'slack', 'url' => env('LOG_SLACK_WEBHOOK_URL'), 'username' => 'Laravel Log', 'emoji' => ':boom:', 'level' => env('LOG_LEVEL', 'critical'), ], 'papertrail' => [ 'driver' => 'monolog', 'level' => env('LOG_LEVEL', 'debug'), 'handler' => env('LOG_PAPERTRAIL_HANDLER', SyslogUdpHandler::class), 'handler_with' => [ 'host' => env('PAPERTRAIL_URL'), 'port' => env('PAPERTRAIL_PORT'), ], ], 'stderr' => [ 'driver' => 'monolog', 'level' => env('LOG_LEVEL', 'debug'), 'handler' => StreamHandler::class, 'with' => [ 'stream' => 'php://stderr', ], ], 'syslog' => [ 'driver' => 'syslog', 'level' => env('LOG_LEVEL', 'debug'), ], 'errorlog' => [ 'driver' => 'errorlog', 'level' => env('LOG_LEVEL', 'debug'), ], 'null' => [ 'driver' => 'monolog', 'handler' => NullHandler::class, ], 'emergency' => [ 'path' => storage_path('logs/laravel.log'), ], ], ]; ` ### File: backend\config\queue.php `php env('QUEUE_CONNECTION', 'sync'), 'connections' => [ 'sync' => [ 'driver' => 'sync', ], 'database' => [ 'driver' => 'database', 'table' => 'jobs', 'queue' => 'default', 'retry_after' => 90, 'after_commit' => false, ], ], 'failed' => [ 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), 'database' => env('DB_CONNECTION', 'sqlite'), 'table' => 'failed_jobs', ], ]; ` ### File: backend\config\sanctum.php `php explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( '%s%s', 'localhost,localhost:3000,localhost:5173,127.0.0.1,127.0.0.1:8000,::1', Sanctum::currentApplicationUrlWithPort() ))), 'guard' => ['web'], 'expiration' => null, 'token_prefix' => env('SANCTUM_TOKEN_PREFIX', ''), 'middleware' => [ 'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class, 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, 'validate_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, ], ]; ` ### File: backend\config\services.php `php env('SESSION_DRIVER', 'file'), 'lifetime' => env('SESSION_LIFETIME', 120), 'expire_on_close' => false, 'encrypt' => false, 'files' => storage_path('framework/sessions'), 'connection' => env('SESSION_CONNECTION'), 'table' => 'sessions', 'store' => env('SESSION_STORE'), 'lottery' => [2, 100], 'cookie' => env( 'SESSION_COOKIE', str(env('APP_NAME', 'laravel'))->slug('_').'_session' ), 'path' => '/', 'domain' => env('SESSION_DOMAIN'), 'secure' => env('SESSION_SECURE_COOKIE'), 'http_only' => true, 'same_site' => env('SESSION_SAME_SITE', 'lax'), ]; ` ### File: backend\config\view.php `php [ resource_path('views'), ], 'compiled' => env( 'VIEW_COMPILED_PATH', realpath(storage_path('framework/views')) ?: storage_path('framework/views') ), ]; ` ### File: backend\database\migrations\0001_create_users_table.php `php id(); $table->string('name'); $table->string('email')->unique(); $table->timestamp('email_verified_at')->nullable(); $table->string('password'); $table->rememberToken(); $table->timestamps(); }); } public function down(): void { Schema::dropIfExists('users'); } }; ` ### File: backend\database\migrations\0002_create_messages_table.php `php id(); $table->foreignId('sender_id') ->constrained('users') ->onDelete('cascade'); $table->foreignId('receiver_id') ->constrained('users') ->onDelete('cascade'); $table->text('content'); $table->timestamps(); // Index for faster query performance $table->index(['sender_id', 'receiver_id']); }); } public function down(): void { Schema::dropIfExists('messages'); } }; ` ### File: backend\database\migrations\2026_03_28_000003_add_type_and_media_url_to_messages_table.php `php string('type')->default('text')->after('receiver_id'); $table->text('media_url')->nullable()->after('content'); }); } public function down(): void { Schema::table('messages', function (Blueprint $table) { $table->dropColumn(['type', 'media_url']); }); } }; ` ### File: backend\database\migrations\2026_03_28_000004_create_call_sessions_table.php `php id(); $table->foreignId('caller_id')->constrained('users')->cascadeOnDelete(); $table->foreignId('callee_id')->constrained('users')->cascadeOnDelete(); $table->string('type'); $table->string('status')->default('ringing'); $table->longText('offer_sdp')->nullable(); $table->longText('answer_sdp')->nullable(); $table->json('caller_candidates')->nullable(); $table->json('callee_candidates')->nullable(); $table->timestamp('started_at')->nullable(); $table->timestamp('ended_at')->nullable(); $table->timestamps(); $table->index(['caller_id', 'callee_id', 'status']); }); } public function down(): void { Schema::dropIfExists('call_sessions'); } }; ` ### File: backend\database\migrations\2026_04_02_000001_add_message_status_and_online_fields.php `php enum('status', ['sent', 'delivered', 'read'])->default('sent')->after('media_url'); $table->timestamp('delivered_at')->nullable()->after('status'); $table->timestamp('read_at')->nullable()->after('delivered_at'); }); // Add online status to users table Schema::table('users', function (Blueprint $table) { $table->boolean('is_online')->default(false)->after('password'); $table->timestamp('last_seen_at')->nullable()->after('is_online'); }); } public function down(): void { Schema::table('messages', function (Blueprint $table) { $table->dropColumn(['status', 'delivered_at', 'read_at']); }); Schema::table('users', function (Blueprint $table) { $table->dropColumn(['is_online', 'last_seen_at']); }); } }; ` ### File: backend\database\migrations\2026_04_02_000005_add_presence_to_users_table.php `php boolean('is_online')->default(false)->after('password'); $table->timestamp('last_seen_at')->nullable()->after('is_online'); }); } public function down(): void { Schema::table('users', function (Blueprint $table) { $table->dropColumn(['is_online', 'last_seen_at']); }); } }; ` ### File: backend\database\migrations\2026_04_02_000006_add_message_status_fields.php `php unsignedInteger('duration_seconds')->nullable()->after('media_url'); $table->timestamp('delivered_at')->nullable()->after('duration_seconds'); $table->timestamp('read_at')->nullable()->after('delivered_at'); }); } public function down(): void { Schema::table('messages', function (Blueprint $table) { $table->dropColumn(['duration_seconds', 'delivered_at', 'read_at']); }); } }; ` ### File: backend\database\migrations\2026_05_01_000001_create_personal_access_tokens_table.php `php id(); $table->morphs('tokenable'); $table->string('name'); $table->string('token', 64)->unique(); $table->text('abilities')->nullable(); $table->timestamp('last_used_at')->nullable(); $table->timestamp('expires_at')->nullable(); $table->timestamps(); }); } public function down(): void { Schema::dropIfExists('personal_access_tokens'); } }; ` ### File: backend\database\seeders\DatabaseSeeder.php `php 'Alice Johnson', 'email' => 'alice@example.com'], ['name' => 'Bob Smith', 'email' => 'bob@example.com'], ['name' => 'Charlie Brown', 'email' => 'charlie@example.com'], ['name' => 'Diana Prince', 'email' => 'diana@example.com'], ]; foreach ($users as $userData) { User::firstOrCreate( ['email' => $userData['email']], [ 'name' => $userData['name'], 'password' => Hash::make('password123'), ] ); } } } ` ### File: backend\public\index.php `php make(Kernel::class); $response = $kernel->handle( $request = Request::capture() )->send(); $kernel->terminate($request, $response); ` ### File: backend\realtime\call_signal_server.php `php make(Kernel::class)->bootstrap(); const DEFAULT_SIGNAL_HOST = '127.0.0.1'; const DEFAULT_SIGNAL_PORT = 8081; $host = $argv[1] ?? DEFAULT_SIGNAL_HOST; $port = (int) ($argv[2] ?? DEFAULT_SIGNAL_PORT); $server = stream_socket_server( sprintf('tcp://%s:%d', $host, $port), $errorCode, $errorMessage ); if ($server === false) { fwrite(STDERR, sprintf("Unable to start signaling server: [%d] %s\n", $errorCode, $errorMessage)); exit(1); } stream_set_blocking($server, false); $clients = []; $userSockets = []; fwrite(STDOUT, sprintf("Call signaling WebSocket listening on ws://%s:%d\n", $host, $port)); while (true) { $readSockets = [$server]; foreach ($clients as $client) { $readSockets[] = $client['socket']; } $writeSockets = null; $exceptSockets = null; if (@stream_select($readSockets, $writeSockets, $exceptSockets, null) === false) { continue; } foreach ($readSockets as $socket) { if ($socket === $server) { $connection = @stream_socket_accept($server, 0); if ($connection === false) { continue; } stream_set_blocking($connection, false); $clients[(int) $connection] = [ 'socket' => $connection, 'buffer' => '', 'handshake_complete' => false, 'user_id' => null, ]; continue; } $clientId = (int) $socket; $chunk = @fread($socket, 8192); if ($chunk === '' || $chunk === false) { if (feof($socket)) { disconnectClient($clientId, $clients, $userSockets); } continue; } $clients[$clientId]['buffer'] .= $chunk; if ($clients[$clientId]['handshake_complete'] === false) { if (str_contains($clients[$clientId]['buffer'], "\r\n\r\n")) { completeHandshake($clients[$clientId]); } continue; } processClientFrames($clientId, $clients, $userSockets); } } function completeHandshake(array &$client): void { if (!preg_match("/Sec-WebSocket-Key: (.*)\r\n/i", $client['buffer'], $matches)) { fclose($client['socket']); return; } $key = trim($matches[1]); $acceptKey = base64_encode( sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true) ); $response = implode("\r\n", [ 'HTTP/1.1 101 Switching Protocols', 'Upgrade: websocket', 'Connection: Upgrade', 'Sec-WebSocket-Accept: ' . $acceptKey, '', '', ]); fwrite($client['socket'], $response); $client['handshake_complete'] = true; $client['buffer'] = ''; } function processClientFrames(int $clientId, array &$clients, array &$userSockets): void { while (true) { $frame = extractFrame($clients[$clientId]['buffer']); if ($frame === null) { return; } if ($frame['opcode'] === 0x8) { disconnectClient($clientId, $clients, $userSockets); return; } if ($frame['opcode'] === 0x9) { sendFrame($clients[$clientId]['socket'], $frame['payload'], 0xA); continue; } if ($frame['opcode'] !== 0x1) { continue; } $payload = json_decode($frame['payload'], true); if (!is_array($payload)) { sendJson($clients[$clientId]['socket'], [ 'type' => 'error', 'message' => 'Invalid signaling payload.', ]); continue; } handleSignalMessage($clientId, $payload, $clients, $userSockets); } } function handleSignalMessage(int $clientId, array $payload, array &$clients, array &$userSockets): void { $type = $payload['type'] ?? ''; if ($type === 'auth') { $token = is_string($payload['token'] ?? null) ? trim($payload['token']) : ''; $user = authenticateToken($token); if (!$user) { sendJson($clients[$clientId]['socket'], [ 'type' => 'auth.error', 'message' => 'Invalid token.', ]); disconnectClient($clientId, $clients, $userSockets); return; } $clients[$clientId]['user_id'] = $user['id']; $userSockets[$user['id']][$clientId] = true; sendJson($clients[$clientId]['socket'], [ 'type' => 'auth.ok', 'user' => $user, ]); return; } if (!$clients[$clientId]['user_id']) { sendJson($clients[$clientId]['socket'], [ 'type' => 'auth.error', 'message' => 'Authenticate before signaling.', ]); disconnectClient($clientId, $clients, $userSockets); return; } $toUserId = (int) ($payload['toUserId'] ?? 0); if ($toUserId < 1 || empty($userSockets[$toUserId])) { return; } $relayPayload = $payload; unset($relayPayload['toUserId'], $relayPayload['token']); $relayPayload['fromUserId'] = $clients[$clientId]['user_id']; $relayPayload['serverTime'] = now()->toIso8601String(); foreach (array_keys($userSockets[$toUserId]) as $targetClientId) { if (!isset($clients[$targetClientId])) { continue; } sendJson($clients[$targetClientId]['socket'], $relayPayload); } } function authenticateToken(string $plainTextToken): ?array { if ($plainTextToken === '') { return null; } $token = PersonalAccessToken::findToken($plainTextToken); if (!$token || !$token->tokenable) { return null; } $user = $token->tokenable->fresh(['id', 'name', 'email']); if (!$user) { return null; } return [ 'id' => $user->id, 'name' => $user->name, 'email' => $user->email, ]; } function extractFrame(string &$buffer): ?array { $bufferLength = strlen($buffer); if ($bufferLength < 2) { return null; } $firstByte = ord($buffer[0]); $secondByte = ord($buffer[1]); $opcode = $firstByte & 0x0F; $masked = ($secondByte & 0x80) === 0x80; $payloadLength = $secondByte & 0x7F; $offset = 2; if ($payloadLength === 126) { if ($bufferLength < 4) { return null; } $payloadLength = unpack('n', substr($buffer, 2, 2))[1]; $offset = 4; } elseif ($payloadLength === 127) { if ($bufferLength < 10) { return null; } $extended = unpack('N2', substr($buffer, 2, 8)); $payloadLength = ($extended[1] << 32) | $extended[2]; $offset = 10; } $maskKey = ''; if ($masked) { if ($bufferLength < $offset + 4) { return null; } $maskKey = substr($buffer, $offset, 4); $offset += 4; } if ($bufferLength < $offset + $payloadLength) { return null; } $payload = substr($buffer, $offset, $payloadLength); $buffer = substr($buffer, $offset + $payloadLength); if ($masked) { $payload = unmaskPayload($payload, $maskKey); } return [ 'opcode' => $opcode, 'payload' => $payload, ]; } function unmaskPayload(string $payload, string $maskKey): string { $decoded = ''; $payloadLength = strlen($payload); for ($index = 0; $index < $payloadLength; $index++) { $decoded .= $payload[$index] ^ $maskKey[$index % 4]; } return $decoded; } function sendJson($socket, array $payload): void { sendFrame($socket, json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } function sendFrame($socket, string $payload, int $opcode = 0x1): void { $payloadLength = strlen($payload); $frame = chr(0x80 | ($opcode & 0x0F)); if ($payloadLength < 126) { $frame .= chr($payloadLength); } elseif ($payloadLength <= 65535) { $frame .= chr(126) . pack('n', $payloadLength); } else { $frame .= chr(127) . pack('NN', 0, $payloadLength); } $frame .= $payload; @fwrite($socket, $frame); } function disconnectClient(int $clientId, array &$clients, array &$userSockets): void { if (!isset($clients[$clientId])) { return; } $userId = $clients[$clientId]['user_id']; if ($userId && isset($userSockets[$userId][$clientId])) { unset($userSockets[$userId][$clientId]); if (empty($userSockets[$userId])) { unset($userSockets[$userId]); } } @fclose($clients[$clientId]['socket']); unset($clients[$clientId]); } ` ### File: backend\routes\api.php `php where('filename', '.*'); // Protected Routes (Sanctum auth required) Route::middleware('auth:sanctum')->group(function () { // Auth Route::post('/logout', [AuthController::class, 'logout']); Route::get('/me', [AuthController::class, 'me']); // Users Route::get('/users', [UserController::class, 'index']); Route::post('/presence/heartbeat', [UserController::class, 'heartbeat']); // Chat Route::get('/chat/bootstrap', [ChatController::class, 'bootstrap']); Route::get('/messages/{user}', [ChatController::class, 'getMessages']); Route::post('/messages', [ChatController::class, 'sendMessage']); Route::post('/send-message', [ChatController::class, 'sendMessage']); Route::post('/messages/delivered', [ChatController::class, 'markDelivered']); Route::post('/messages/{user}/read', [ChatController::class, 'markRead']); Route::delete('/messages/{user}', [ChatController::class, 'clearMessages']); // Calls Route::get('/calls/current', [CallController::class, 'current']); Route::post('/calls', [CallController::class, 'store']); Route::get('/calls/{call}', [CallController::class, 'show']); Route::post('/calls/{call}/offer', [CallController::class, 'offer']); Route::post('/calls/{call}/answer', [CallController::class, 'answer']); Route::post('/calls/{call}/candidate', [CallController::class, 'candidate']); Route::post('/calls/{call}/decline', [CallController::class, 'decline']); Route::post('/calls/{call}/end', [CallController::class, 'end']); }); ` ### File: backend\routes\console.php `php comment('Backend is booting normally.'); })->purpose('Confirm Artisan commands are loaded'); ` ### File: backend\routes\web.php `php away(frontendUrl('/')); }); Route::get('/login', fn () => redirect()->away(frontendUrl('/login'))); Route::get('/register', fn () => redirect()->away(frontendUrl('/register'))); Route::get('/chat', fn () => redirect()->away(frontendUrl('/chat'))); ` ### File: database\schema.sql `sql -- Chat App Database Schema -- Run this if you prefer manual DB setup instead of Laravel migrations CREATE DATABASE IF NOT EXISTS chat_app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE chat_app; -- Users Table CREATE TABLE IF NOT EXISTS users ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL UNIQUE, email_verified_at TIMESTAMP NULL DEFAULT NULL, password VARCHAR(255) NOT NULL, is_online TINYINT(1) NOT NULL DEFAULT 0, last_seen_at TIMESTAMP NULL DEFAULT NULL, remember_token VARCHAR(100) NULL, created_at TIMESTAMP NULL DEFAULT NULL, updated_at TIMESTAMP NULL DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Messages Table CREATE TABLE IF NOT EXISTS messages ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, sender_id BIGINT UNSIGNED NOT NULL, receiver_id BIGINT UNSIGNED NOT NULL, type VARCHAR(255) NOT NULL DEFAULT 'text', content TEXT NOT NULL, media_url LONGTEXT NULL, duration_seconds INT UNSIGNED NULL, delivered_at TIMESTAMP NULL DEFAULT NULL, read_at TIMESTAMP NULL DEFAULT NULL, created_at TIMESTAMP NULL DEFAULT NULL, updated_at TIMESTAMP NULL DEFAULT NULL, CONSTRAINT fk_sender FOREIGN KEY (sender_id) REFERENCES users(id) ON DELETE CASCADE, CONSTRAINT fk_receiver FOREIGN KEY (receiver_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_conversation (sender_id, receiver_id), INDEX idx_created_at (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Call Sessions Table CREATE TABLE IF NOT EXISTS call_sessions ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, caller_id BIGINT UNSIGNED NOT NULL, callee_id BIGINT UNSIGNED NOT NULL, type VARCHAR(255) NOT NULL, status VARCHAR(255) NOT NULL DEFAULT 'ringing', offer_sdp LONGTEXT NULL, answer_sdp LONGTEXT NULL, caller_candidates JSON NULL, callee_candidates JSON NULL, started_at TIMESTAMP NULL DEFAULT NULL, ended_at TIMESTAMP NULL DEFAULT NULL, created_at TIMESTAMP NULL DEFAULT NULL, updated_at TIMESTAMP NULL DEFAULT NULL, CONSTRAINT fk_call_caller FOREIGN KEY (caller_id) REFERENCES users(id) ON DELETE CASCADE, CONSTRAINT fk_call_callee FOREIGN KEY (callee_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_call_status (caller_id, callee_id, status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Personal Access Tokens (Laravel Sanctum) CREATE TABLE IF NOT EXISTS personal_access_tokens ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, tokenable_type VARCHAR(255) NOT NULL, tokenable_id BIGINT UNSIGNED NOT NULL, name VARCHAR(255) NOT NULL, token VARCHAR(64) NOT NULL UNIQUE, abilities TEXT NULL, last_used_at TIMESTAMP NULL DEFAULT NULL, expires_at TIMESTAMP NULL DEFAULT NULL, created_at TIMESTAMP NULL DEFAULT NULL, updated_at TIMESTAMP NULL DEFAULT NULL, INDEX idx_tokenable (tokenable_type, tokenable_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ` ### File: frontend\package-lock.json `json { "name": "chat-app-frontend", "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "chat-app-frontend", "version": "1.0.0", "devDependencies": { "autoprefixer": "^10.4.16", "postcss": "^8.4.31", "tailwindcss": "^3.3.5", "vite": "^5.0.0" } }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", "dev": true, "license": "MIT", "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", "cpu": [ "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "aix" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/android-arm": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/android-arm64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/android-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "android" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/darwin-arm64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/darwin-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/freebsd-arm64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/freebsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "freebsd" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/linux-arm": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/linux-arm64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/linux-ia32": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/linux-loong64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", "cpu": [ "loong64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/linux-mips64el": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/linux-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", "cpu": [ "ppc64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/linux-riscv64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/linux-s390x": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/linux-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/netbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "netbsd" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/openbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "openbsd" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/sunos-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", "cpu": [ "x64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "sunos" ], "engines": { "node": ">=12" } }, "node_modules/@esbuild/win32-arm64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",

About

Let's Chat is a full-stack real-time chat application designed for seamless and interactive communication. This project enables users to send instant messages, create chat rooms, and experience a smooth, modern UI with real-time updates powered by efficient backend communication.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors