[04]  Live Product  —  Travel Social Platform

Migo App

A collaborative travel platform that keeps trips, itineraries, traveller profiles, shared expenses and contextual AI assistance in one persistent context.

Product design, full-stack development, database architecture, AI integration, testing and deployment.

Live Product  —  [Scroll to explore]

Product DesignFull-Stack DevelopmentAI Integration

Category

Travel Social Platform

Status

Live Product

Stack

ExpoReact NativeTypeScriptSupabasePostgreSQL

Project index

04

Role — Product design, full-stack development, database architecture, AI integration, testing and deployment.

40%

Faster trip planning

Measured during workflow testing

5

Core product modules

Trips, Explore, Social, AI, Profile

6

System layers

UI · Auth · App · DB · Security · AI

41

Database migrations

Sequential, behind the current schema

The 40% is a self-measured, single-tester comparison: the same trip-planning task timed once through the old multi-tool workflow and once through Migo, on the same device. Not a controlled study or a multi-user benchmark — treat it as directional, not statistical.

[01]

Problem

Travel planning is commonly fragmented across messaging apps, notes, spreadsheets, maps, social media and separate AI tools, making trip information harder to organise, maintain and revisit.

[02]

Solution

Creates one persistent travel context for trips, itineraries, traveller profiles, discovery, travel records and contextual AI assistance.

Connects those workflows through shared authenticated user and trip data rather than isolated screens.

[03]

What I built

Product

  • Designed the core experience across Trips, Explore, Social, AI and Profile.
  • Built reusable responsive components and navigation flows.

Backend & data

  • Architected PostgreSQL-backed user, trip and itinerary data.
  • Implemented Supabase authentication and user-owned data access.
  • Connected authenticated accounts to relational records.
  • Implemented Row-Level Security for protected data.

AI

  • Integrated a contextual travel assistant into the planning workflow.
  • Structured AI requests around existing trip context rather than generic standalone chat.

Engineering

  • Connected planning, social, profile, AI and data through one shared architecture.
  • Tested authentication, database access, CRUD flows, responsive states and production deployment.
[04]

Architecture

Supabase Auth establishes user identity, while Row-Level Security keeps PostgreSQL records scoped to authenticated users.

AI assistance uses existing trip context instead of operating as a disconnected chatbot.

  1. Frontend

    Expo + React Native + TypeScript

    Responsive product screens, navigation and shared UI patterns.

  2. Authentication

    Supabase Auth

    User identity and session state create the boundary for personalised records.

  3. Application

    Trips · Itineraries · Social · Profiles

    Core modules share user and trip context instead of acting as separate screens.

  4. Database

    PostgreSQL / Supabase

    Relational data stores users, trips, itinerary records, profile data and activity.

  5. Security

    Row-Level Security

    Database policies protect user-scoped records beyond frontend checks.

  6. AI layer

    Contextual travel assistant

    AI assistance operates inside the planning workflow using existing travel context.

Migo PostgreSQL schema diagram showing profiles, trips, posts, notifications and related trip tables
Supabase / PostgreSQL schema across the core relational tables
[05]

Engineering decisions

Why relational data?

Migo's data is inherently relational — trips, itinerary items, trip members, shared expenses and social posts all reference each other through real foreign-key constraints. Postgres enforces those relationships at the database layer; a document store would have pushed that integrity work up into application code instead.

Why DB-level authorization?

Frontend checks only stop an honest client. Row-Level Security policies run inside Postgres itself, so a direct API call cannot read or write another user's trip data — the database is the actual access boundary, not the UI.

Why persistent context?

The problem being solved is fragmentation: planning scattered across messaging apps, notes, maps and a separate AI tool. An assistant that cannot see the trip would recreate that split inside the product. So AI assistance operates on existing travel context rather than as a disconnected chatbot, and the core modules share user and trip data instead of acting as separate screens.

[06]

Technical challenges

Row-Level Security recursion on trip membership

Problem

Trip data is shared with collaborators through a trip_members table, and the RLS policy on that table needed to check trip membership to decide what a user could see.

Cause

A self-referencing RLS policy: evaluating the policy on trip_members required querying trip_members again, which re-triggered the same policy and recursed.

Fix

Extracted the membership check into a standalone is_trip_member() SQL function marked SECURITY DEFINER, which runs with the function owner's privileges instead of re-triggering the calling policy. search_path is pinned to public so the function cannot be redirected to an attacker-controlled schema.

Why this fix

A SECURITY DEFINER function was the minimal fix that kept RLS as the real enforcement boundary — loosening the policy or moving the check into application code would have quietly reopened the access-control gap RLS exists to close.

supabase/migrations/020_fix_trip_member_rls_recursion.sql
create or replace function public.is_trip_member(p_trip_id uuid, p_user_id uuid)
returns boolean
language sql stable security definer
set search_path = public
as $$
  select exists (
    select 1 from public.trips t
    where t.id = p_trip_id and (t.user_id = p_user_id or t.created_by = p_user_id)
  ) or exists (
    select 1 from public.trip_members tm
    where tm.trip_id = p_trip_id and tm.user_id = p_user_id and tm.status = 'accepted'
  );
$$;

Duplicate writes from offline sync

Problem

Trip and itinerary edits made while offline are queued locally and replayed once connectivity returns. A retried or duplicated replay of that queue could insert the same record twice.

Cause

Network retries and queue replays have no inherent way to know whether a given write already succeeded on the server before the connection dropped.

Fix

Writes from the offline queue carry a client-generated idempotency key; the insert path checks for that key before committing, so a repeated replay is a no-op instead of a duplicate row.

Why this fix

Idempotency keys kept the client's retry logic simple — it can always just resend — without needing to track what already landed on the server.

Keeping shared-expense splits consistent

Problem

Trip expenses can be split across multiple members, edited, or removed — any of which could leave a group's balances inconsistent if a split total drifted from the original expense amount.

Cause

Splits and their parent expense are stored as separate rows; without a constraint tying them together, an edit to one side could drift out of sync with the other.

Fix

Added database-level constraints and validation so a shared expense's split amounts are checked against its total at write time, hardened in a dedicated migration alongside handling for legacy expense-group records.

Why this fix

Money math should not depend on every call site remembering to validate it — enforcing the constraint in Postgres means it holds regardless of which code path writes the row.

[07]

Current limits & what's next

Implemented

  • Row-Level Security on every user-scoped table, with SECURITY DEFINER helper functions where self-referencing checks required it
  • Shared-expense split integrity constraints
  • Offline-write idempotency for the sync queue
  • Grouped notification infrastructure
  • Trip invite links and collaborator roles
  • Trust and safety report/evidence tables for user-generated content
  • Edge cases handled as first-class states rather than afterthoughts: unauthenticated access, users with no trips, empty itineraries, incomplete profiles, failed database requests and AI request failures

Still outstanding

  • Automated test coverage — there is no test suite yet; that is the next investment before adding more surface area
  • Structured observability (error tracking, query performance) beyond Supabase's default logs
  • Rate limiting on write-heavy endpoints (expense splits, social posts)
  • A background job queue for work currently done inline (notification fan-out, AI requests)
  • Retry and backoff handling for AI and third-party calls beyond the current request path
  • Load testing before onboarding usage beyond a single-developer scale
  • A documented backup and recovery drill for the Postgres database
  • Product analytics to replace the single self-measured planning-time comparison with real usage data
[08]

Outcome

I designed and built Migo end to end — from product architecture and responsive UI through authentication, relational data, security, AI integration, testing and deployment.