Skip to content

Latest commit

 

History

History
347 lines (268 loc) · 14.4 KB

File metadata and controls

347 lines (268 loc) · 14.4 KB

Marina Boat Tracking and Reservation System

A dual-sided mobile application for managing marina operations, built with React Native (Expo) and Supabase.


Overview

The Marina app serves two types of users:

  • Customers — boat owners who schedule put-ins, manage reservations, track their boat's status, and communicate with marina staff
  • Workers / Admins — marina staff who manage the slip board, approve boats, track daily operations, message customers, and (admins) generate invite codes

The system manages a fleet of 20 boats across 6 physical boat slips, with business logic that automatically handles slip assignment, overflow reservations, and end-of-day return enforcement.


Tech Stack

Layer Technology
Mobile (iOS + Android) React Native via Expo (SDK 54)
Routing Expo Router (file-based)
Backend & Database Supabase (Postgres)
Authentication Supabase Auth
Real-time sync Supabase Realtime (postgres_changes)
Scheduled jobs pg_cron (via Supabase)
Push notifications Expo Notifications + Supabase Edge Functions
Maps @rnmapbox/maps (requires dev build)
Session storage AsyncStorage

Project Structure

marina-app/
├── app/
│   ├── (auth)/
│   │   ├── login.tsx               # Shared login screen
│   │   ├── register.tsx            # Customer self-registration
│   │   └── worker-register.tsx     # Worker/admin invite-code registration
│   ├── (customer)/
│   │   ├── _layout.tsx
│   │   ├── index.tsx               # Customer dashboard
│   │   ├── boats.tsx               # My boats + add boat
│   │   ├── reservations.tsx        # Make a reservation
│   │   ├── my-reservations.tsx     # View + cancel reservations
│   │   ├── notifications.tsx       # Inbox + system notifications
│   │   └── map.tsx                 # Boat location (stubbed in Expo Go)
│   ├── (worker)/
│   │   ├── _layout.tsx
│   │   ├── index.tsx               # Worker dashboard
│   │   ├── boats.tsx               # Boat approvals + archive/delete
│   │   ├── schedule.tsx            # Slip board + daily schedule
│   │   ├── messages.tsx            # Worker message inbox
│   │   ├── chat/[customerId].tsx   # Threaded conversation with a customer
│   │   ├── invite-codes.tsx        # Admin invite code management
│   │   └── map.tsx                 # Marina map (stubbed in Expo Go)
│   └── _layout.tsx                 # Root layout + auth gate
├── src/
│   ├── lib/
│   │   ├── supabase.ts             # Supabase client
│   │   ├── boatService.ts          # Boat CRUD + location pinning
│   │   ├── inviteCodeService.ts    # Invite code generation
│   │   ├── notifications.ts        # Push token registration
│   │   ├── notificationService.ts  # Push notification sending
│   │   ├── mapbox.ts               # Mapbox init (dev build only)
│   │   └── constants.ts            # Marina coordinates + slip locations
│   ├── services/
│   │   ├── reservationService.ts   # Reservation RPC calls
│   │   └── workerService.ts        # Worker schedule + status updates (canonical file)
│   ├── context/
│   │   └── AuthContext.tsx         # Session, profile, role state + periodic role re-check
│   ├── hooks/
│   │   └── useAuth.ts
│   └── types/
│       └── database.ts
└── supabase/
    ├── functions/
    │   └── send-push-notification/ # Edge function for push delivery
    │       └── index.ts
    └── migrations/
        ├── 001_initial_schema.sql
        └── 002_customer_reservation_rpc.sql

Database Schema

Tables

Table Description
profiles Extends Supabase auth users with full name, phone, role, and push token
invite_codes One-time codes for worker/admin registration, with grants_role
boats Customer boats with approval status and archive flag
slips The 6 physical boat slips with availability status
reservations All bookings with slip assignment, return alert flag, and worker notes
boat_locations Worker-pinned GPS coordinates for boat tracking
notifications Push notification queue and history
messages Worker to customer direct messages (threaded)

Enums

user_role:           customer | worker | admin
boat_status:         in_water | on_land | maintenance
reservation_status:  pending | confirmed | in_water | returned | overdue | cancelled
notification_type:   reservation_confirmed | return_reminder | return_overdue |
                     slip_assigned | slip_released | worker_message | location_updated

Core Business Logic

Slip Assignment Rules

The marina has 6 physical slips reserved exclusively for boats staying past 5:00 PM. Early returners do not need a slip — the forklift operator handles them when they return.

Customer books with return before 5:00 PM
  -> No slip assigned
  -> Forklift pulls boat on return

Customer books with return at or after 5:00 PM
  -> System checks: how many slips are held by other late stayers?
  -> If a slip is available -> assigned automatically
  -> If all 6 slips are taken by late stayers -> overflow reservation
     -> needs_return_alert = true
     -> Customer must return by 4:45 PM

The RPC public.create_customer_reservation uses row-level locking (FOR UPDATE SKIP LOCKED) to prevent race conditions during concurrent bookings, and includes optimistic-concurrency checks so a status or slip change is rejected (with a clear message) if the underlying row changed since it was last loaded.

Time Slot Blocking

  • Time slots are in 15-minute increments from 9:00 AM to 5:00 PM
  • One boat per time slot (the forklift handles one boat at a time)
  • A reservation blocks both its put-in time slot and its return time slot
  • Taken slots are hidden from the time picker for other customers
  • The RPC rejects bookings for already-taken slots as a server-side safeguard

Overdue Auto-Trigger

A pg_cron job runs every 15 minutes and marks no-slip in_water reservations as overdue if their expected return has passed or it's past 4:45 PM. Slip boats are never marked overdue (they stay overnight by design).

4:45 PM Alert

A pg_cron job runs at exactly 4:45 PM daily and notifies all in_water no-slip customers to return immediately.


Authentication & Roles

Customer Registration

Self-service via the registration screen. Email confirmation disabled for development.

Worker / Admin Registration

Invite-code gated. The invite code carries a grants_role so a code can create either a worker or an admin. Codes are redeemed atomically via a row-locked database function to prevent two people redeeming the same code at once, and single-use codes expire after 7 days.

Admin Capabilities

Admins can generate, copy, and deactivate invite codes from inside the app (Invite Codes screen). To create the first admin, manually update a profile's role to admin in SQL.

Role-Based Routing

The root layout reads the user's role after login and routes:

  • customer -> /(customer)/
  • worker or admin -> /(worker)/

A periodic re-check keeps an already-logged-in session in sync if a role changes elsewhere (e.g. an admin promotes or demotes someone while they're still signed in).


Features Completed

Phase 1 — Foundation

  • Expo project with TypeScript and expo-router
  • Full Supabase schema with RLS on all tables
  • Role-based auth (customer self-register, worker/admin invite-code)
  • Automatic profile creation via database trigger
  • Role-based navigation gate
  • Login, customer registration, and worker registration screens

Phase 2 — Core Booking

  • Customer boat registration with name, make/model, and boat number
  • Worker boat approval and rejection flow with reason
  • Reservation system with correct 6-slip business logic
  • Calendar date picker with past-date blocking
  • Customer reservations list (today + upcoming)
  • Cancel reservation with confirmation (race-guarded against concurrent worker updates)
  • Multi-boat support per customer

Phase 3 — Worker Operations

  • Worker daily schedule with date navigation (all dates)
  • Slip board with 6 slips (available / occupied / overdue / unavailable)
  • Worker can mark slips unavailable and restore them
  • No-slip boats section with 4:45 PM urgent alert
  • Summary bar (slips used, no-slip boats, overdue)
  • Reservation action sheet with full boat/customer details
  • Mark boats in water / returned / overdue, with undo paths in both directions
  • Expandable "Returned (N)" section so returned boats stay reachable for undo
  • Slip reassignment
  • Overdue auto-trigger via pg_cron
  • Real-time sync between worker devices, including live refresh of an open action sheet
  • Boat archive (worker) / restore / permanent delete (admin)
  • Archived boats show as inactive to the customer

Phase 4 — Map & Location

  • Mapbox map screens written (worker pin-drop + customer view)
  • Slip and yard location markers defined
  • Requires a development build to run (stubbed with placeholder screens in Expo Go for now)

Phase 5 — Notifications & Messaging

  • Push token registration and saving
  • Edge function delivering push notifications to device (working)
  • Worker messages customer from action sheet, in a threaded conversation view
  • Customer inbox with unread badge
  • Customer can reply to worker messages
  • Worker message inbox with unread badge and reply
  • Customers can message staff once they have at least one approved boat
  • Reservation confirmed notification
  • Boat status update notifications (non-blocking)
  • 4:45 PM deadline alert scheduled
  • Keyboard-avoiding inputs on all message screens

Phase 6 — Polish & Edge Case Hardening

  • 15-minute time slots with forklift buffer blocking
  • Admin invite code generation (auto + custom codes, worker/admin roles)
  • Status flow undo paths (return -> undo, overdue -> back to in water)
  • Archive warning when a boat has upcoming reservations
  • Flag reservations for review when their boat is archived
  • Non-blocking notifications so push failures never crash core actions
  • Concurrency hardening: optimistic concurrency checks on status/slip changes, live action-sheet refresh via realtime, race guard on customer cancellations
  • Account/auth hardening: periodic role re-check while logged in, atomic invite-code redemption, an 8-second timeout safeguard for an auth cold-start hang
  • Boat & reservation integrity edge cases (customer with zero boats reaching the reservation screen, duplicate boat resubmission, reservation date/timezone edge cases)
  • Messaging edge cases (a demoted/deleted worker's existing conversations, a customer's last approved boat archived before staff reply)
  • Notification edge cases (a stale push token after reinstall or a new device)

Phases Remaining

Phase Feature
6 Remaining edge case sweeps (boat/reservation integrity, messaging, notifications — see above)
6 Development build to enable Mapbox
6 Test maps with real marina coordinates
6 Final QA
6 App Store and Play Store builds

Environment Setup

Prerequisites

  • Node.js 18+
  • Expo CLI and EAS CLI (npm install -g expo-cli eas-cli)
  • Supabase account
  • Expo account (expo.dev)
  • Docker Desktop (required for deploying edge functions)
  • Expo Go app on a physical device

Installation

git clone https://github.com/bqhorsfall/marina-app.git
cd marina-app
npm install --legacy-peer-deps

Environment Variables

Create a .env file in the project root (gitignored — never commit this file):

SUPABASE_URL=https://your-project.supabase.co
SUPABASE_ANON_KEY=your-anon-key-here
MAPBOX_SECRET_TOKEN=your-mapbox-secret-token-here

Reference these via process.env inside app.config.ts rather than hardcoding any key or token as a literal string in source.

Database Setup

  1. Run the migrations in the Supabase SQL editor
  2. Seed the slips and an invite code:
insert into public.slips (slip_number) values (1),(2),(3),(4),(5),(6);

insert into public.invite_codes (code, grants_role, expires_at)
values ('MARINA-WORKER-2025', 'worker', now() + interval '365 days');
  1. Schedule the cron jobs:
select cron.schedule('mark-overdue-reservations', '*/15 * * * *',
  'select public.mark_overdue_reservations()');

select cron.schedule('send-445-alerts', '45 16 * * *',
  'select public.send_445_alerts()');

Edge Function (requires Docker running)

supabase login
supabase link --project-ref your-project-ref
supabase functions deploy send-push-notification --no-verify-jwt

If the function serves stale code after a CLI deploy, edit it directly in the Supabase dashboard editor and save there — this has been the reliable fallback in this project.

Running the App

npx expo start --clear

Scan the QR code with Expo Go. Both devices must be on the same WiFi network.


Known Limitations

  • Map-based location tracking requires a development build (not available in Expo Go).
  • Email confirmation is disabled in development — enable before production.
  • Apple App Store distribution requires a $99/year Apple Developer account.

Security Notes

  • Never commit .env to version control.
  • Row-level security is enabled on all tables.
  • Invite codes are redeemed atomically (row-locked) and expire after 7 days.
  • Admin-only actions check role via the profiles table in RLS policies rather than a self-referencing policy on profiles itself, which avoids infinite recursion.
  • The service role key lives only in the edge function — never expose it to the client.
  • Never hardcode a secret token (Mapbox secret token, service role key, etc.) directly in a source file such as app.config.ts — reference it via an environment variable instead.
  • If a secret is ever accidentally committed, rotate it immediately in the provider's dashboard and remove it from git history (e.g. with git filter-repo) before pushing again — deleting it from the current file alone is not sufficient, since GitHub's push protection scans full commit history.

Built with React Native, Expo, and Supabase.