All Work

Samara Studio Management System

Replaced Excel with a production-grade, real-time studio operations platform.

RoleSolo Developer (Full-Stack)
Year2026
Duration~2 months
TeamSolo
Status๐ŸŸก In Development
Operational Dashboard โ€” Real-time metrics, active sessions, and quick actions

At a Glance

10Core App Modules
39SQL Migration Files
3Conflict Validation Layers
40+PostgreSQL RPCs & Triggers

The Problem

Samara Studio โ€” a photography, videography, and content-production studio โ€” was running its entire operation on Excel spreadsheets. Staff manually tracked bookings, room occupancy, equipment loans, client balances, and daily cash closings across disconnected sheets. Double-bookings happened when two staff members booked the same room simultaneously without syncing. Equipment was overcommitted because there was no real-time visibility of active reservations. Financial drift crept in as partial payments and deposits were manually tracked. And daily closing required summing rows in a spreadsheet with no audit trail.

The Solution

A real-time, role-based web application that centralizes every studio operation. Booking conflict prevention happens at three layers simultaneously: client-side validation, server-side API guards, and PostgreSQL exclusion constraints with row-level locking โ€” making double-booking structurally impossible. All billing logic lives in PostgreSQL RPCs (`end_booking_session`, `confirm_booking`, `cancel_booking`), so the critical financial path is transactional, lock-aware, and database-enforced regardless of which client triggers it.

Architecture

Key Features

Screenshots

Code Highlight

Whole-Hour Ceiling Billing with Short-Session Guard in a PostgreSQL RPC
-- From: 035_minimum_hour_billing.sql โ€” end_booking_session()

-- 1. Fetch booking with exclusive row lock (prevents concurrent finalization)
SELECT * INTO v_booking
FROM bookings
WHERE id = p_booking_id AND deleted_at IS NULL
FOR UPDATE;

-- 2. Guard: reject sub-15-minute sessions
v_billing_origin   := COALESCE(v_booking.started_at, v_booking.start_time);
v_duration_minutes := EXTRACT(EPOCH FROM (v_end_time - v_billing_origin)) / 60.0;

IF v_duration_minutes < 15 THEN
    RAISE EXCEPTION
        'Session lasted less than 15 minutes (%.1f min). Use cancel_short_session instead.',
        v_duration_minutes;
END IF;

-- 3. Whole-hour ceiling billing, minimum 1 billable hour
v_duration_ms := EXTRACT(EPOCH FROM (v_end_time - v_billing_origin)) * 1000.0;
v_room_hours  := GREATEST(CEIL(v_duration_ms / 3600000.0), 1);
v_room_cost   := COALESCE(v_booking.room_hourly_rate, 0) * v_room_hours;

-- Equipment follows the same rule (non-camera items):
v_eq_hours := GREATEST(CEIL(v_duration_ms / 3600000.0), 1);
v_eq_total  := v_eq_hours * COALESCE(v_eq_row.unit_price, 0) * v_eq_row.quantity;

Challenges & Solutions

๐ŸŽฏ Race conditions on concurrent bookings โ€” two staff booking the same room within milliseconds of each other

The `confirm_booking` RPC acquires a `FOR UPDATE` lock on the target room's active bookings before inserting. PostgreSQL serializes concurrent callers at the row-lock level. A PostgreSQL exclusion constraint on the `bookings` table using the `tstzrange` type additionally prevents overlapping time ranges from co-existing โ€” even if a bug bypasses the application layer.

๐ŸŽฏ Billing accuracy across multiple pricing models โ€” hourly, flat-rate, and camera-percentage variants

Migrated all billing math into the `end_booking_session` PostgreSQL function (migration 035). The function reads `camera_price_type`, applies `CEILING(ms / 3_600_000, 1)` for hourly items and uses the flat value for flat-fee items โ€” all inside a single `BEGIN/COMMIT` block. No partial state is possible.

๐ŸŽฏ Schema evolution across 39 migrations with real data present from early development

Every change was written as an incremental, idempotent SQL migration using `CREATE OR REPLACE`, `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`, and `CREATE INDEX IF NOT EXISTS`. Destructive operations were avoided by design, giving a linear, auditable history of every schema decision.

Tech Stack

Next.js 16TypeScript 5Supabase (PostgreSQL)Row Level SecuritySupabase RealtimeZustandTanStack Query v5React Hook Form + ZodTailwind CSS v4FullCalendar v6@react-pdf/rendererRechartsPlaywright

What I Learned

Links

Interested in a similar solution?

Let's discuss how I can build something like this for your business.