Back to Projects

Case Study

Spot

Laravel 8 JSON backend for Spot (MyWork/GiltchFix/Spot—the repo path is often spoken as glitchfix/Spot): voice-room social product with a large `/api` surface including Agora mic orchestration, room membership and moderation, chat, friends or follows or blocks, gifts and coins or diamonds, VIP, leaderboards, and staff operator endpoints; separate `/api/user` auth routes; `apiLocale` plus Fruitcake CORS for mobile; Pusher, Kreait Firebase PHP, and Stripe in composer; localized Blade dashboard at `{lang}/dashboard` for admins.

Laravel 8REST APIPHPMySQLAgoraPusherStripe
Agora mic APIsLarge JSON surfaceLocalized adminCORS for mobile
Spot cover

Impact

A single authoritative backend mobile teams could integrate against for rooms, voice, chat, and economy while operations used the dashboard—reducing split-brain between “admin tools” and “app APIs.”

Problem

Voice-first communities need server-side truth for who is on mic, who may speak, and who is banned—if those rules live only in the client, moderation and monetization collapse the first time someone ships a patched APK.

Solution

Centralized Laravel controllers under the `Api` namespace expose hundreds of stateful endpoints: room lifecycle and Agora mic slots, chat history retrieval, social graph (friends, followers, blocks), virtual economy (gifts, coins, diamonds, charge levels), reporting, and parallel admin tools for suspensions and room operations. `RouteServiceProvider` mounts `routes/api.php`, `routes/user.php` under `api/user`, `routes/web.php` (minimal marketing plus Stripe wiring), and `routes/admin.php` for the localized dashboard.

Highlighted implementation

Route mounting (RouteServiceProvider)

PHP
$this->routes(function () {
    Route::prefix('api')
        ->middleware('api')
        ->namespace($this->namespace)
        ->group(base_path('routes/api.php'));

    Route::prefix('api/user')
        ->middleware('apiLocale')
        ->namespace($this->namespace)
        ->group(base_path('routes/user.php'));

    Route::middleware('web')
        ->namespace($this->namespace)
        ->group(base_path('routes/web.php'));

    Route::middleware('web')
        ->namespace($this->namespace)
        ->group(base_path('routes/admin.php'));
});

Testing & quality

PHPUnit 9 present in the repo for automated coverage where written; production confidence relied on staging mobile builds against representative room and mic sequences, plus manual admin checks on ban and suspend paths.

Architecture

API Structure

`routes/api.php` is grouped with `apiLocale` and namespaces `Api\…` controllers covering user profiles, rooms, Agora mic orchestration, chat, items, VIP, events, and admin maintenance actions. `routes/user.php` isolates login, signup, confirmCode, and logout for the mobile auth flow. `routes/admin.php` serves `{lang}/dashboard` with `admin:admin` and `locale` middleware for operator CRUD on users, rooms, bans, notifications, and related resources.

Data Flow

Mobile app → HTTPS JSON → Laravel validation and authorization → Eloquent or query builder writes to MySQL (rooms, members, messages, ledgers). Agora-related endpoints persist mic ownership and mute flags the SDK expects; Pusher or Firebase can fan out side effects depending on deployment configuration.

Backend Decisions

Kept voice and moderation flows on the server even when it meant verbose route tables; used explicit operator endpoints for bans and room kicks instead of implicit client-side toggles. CORS middleware targets known app origins; Stripe remains server-initiated via web routes for payment experiments without exposing secrets to clients.

Spot architecture diagram
High-level diagram of web dashboard, REST API, Laravel layers, and MySQL.

Challenges

Mic state and room membership must stay consistent under concurrent takes and admin mutes; large route files need discipline when adding endpoints so mobile versioning does not break silently; balancing Firebase or Pusher usage with Laravel queues depends on hosting constraints not visible from routes alone.

Technical Decisions

Laravel 8 monolith with class-style Stripe controller on web routes for payment tests; legacy route string targets remain alongside newer syntax where refactors stopped; Pusher and Kreait Firebase chosen from composer to support realtime and push without rewriting the core API.