Skip to content

Latest commit

Β 

History

History
807 lines (661 loc) Β· 19.3 KB

File metadata and controls

807 lines (661 loc) Β· 19.3 KB

Supabase + Vercel Migration Plan

City Service Provider Application

πŸ“‹ Overview

This document outlines the complete migration strategy from Spring Boot + PostgreSQL to Supabase + Vercel while maintaining all existing functionality.

🎯 Migration Objectives

  • Backend: Migrate from Spring Boot to Supabase Edge Functions
  • Database: Migrate from local PostgreSQL to Supabase PostgreSQL
  • Authentication: Replace JWT with Supabase Auth
  • Frontend: Update React app to use Supabase client
  • Deployment: Deploy to Vercel with optimized configuration
  • Performance: Maintain or improve current performance
  • Data Integrity: Ensure zero data loss during migration

πŸ—οΈ Current Architecture Analysis

Backend (Spring Boot)

  • Framework: Spring Boot 3.x with Java
  • Database: PostgreSQL with JPA/Hibernate
  • Authentication: JWT-based with custom implementation
  • API Endpoints: RESTful APIs for auth, providers, services, categories, admin
  • File Upload: Spring Boot multipart file handling
  • Security: Spring Security with CORS configuration

Frontend (React)

  • Framework: React 18 with Material UI
  • State Management: React hooks and context
  • API Client: Axios with interceptors
  • Authentication: JWT token storage and refresh
  • Routing: React Router DOM

Database Schema

-- Core Tables
- users (21 records)
- roles (3 records)
- service_providers (20 records)
- service_categories (10 records)
- services (40 records)
- bookings (3 records)
- reviews (2 records)

🎯 Target Architecture

Supabase Backend

  • Edge Functions: TypeScript/Deno functions for business logic
  • Database: Supabase PostgreSQL with Row Level Security
  • Authentication: Supabase Auth with social providers
  • Storage: Supabase Storage for file uploads
  • Real-time: Supabase subscriptions for live updates

Vercel Frontend

  • Deployment: Vercel with automatic deployments
  • Environment: Optimized build configuration
  • CDN: Global edge network for performance
  • Analytics: Vercel Analytics integration

πŸ“Š Migration Timeline

Phase 1: Foundation Setup (Days 1-2)

  • Create Supabase project
  • Set up database schema
  • Configure authentication
  • Set up development environment

Phase 2: Database Migration (Days 3-4)

  • Export existing data
  • Create Supabase tables
  • Import data with transformations
  • Set up Row Level Security policies

Phase 3: Backend Migration (Days 5-7)

  • Create Edge Functions
  • Implement business logic
  • Set up file storage
  • Configure CORS and security

Phase 4: Frontend Updates (Days 8-9)

  • Install Supabase client
  • Update authentication flow
  • Migrate API calls
  • Implement real-time features

Phase 5: Deployment & Testing (Days 10-11)

  • Configure Vercel deployment
  • Set up environment variables
  • Comprehensive testing
  • Performance optimization

Phase 6: Documentation & Handover (Day 12)

  • Update documentation
  • Create deployment guides
  • Final validation
  • Go-live preparation

πŸ—„οΈ Database Migration Strategy

1. Schema Conversion

Users Table

-- Supabase users table (extends auth.users)
CREATE TABLE public.user_profiles (
  id UUID REFERENCES auth.users(id) PRIMARY KEY,
  username TEXT UNIQUE NOT NULL,
  first_name TEXT NOT NULL,
  last_name TEXT NOT NULL,
  phone_number TEXT,
  address TEXT,
  profile_image_url TEXT,
  date_of_birth TIMESTAMPTZ,
  gender TEXT CHECK (gender IN ('MALE', 'FEMALE', 'OTHER', 'PREFER_NOT_TO_SAY')),
  is_active BOOLEAN DEFAULT true,
  is_verified BOOLEAN DEFAULT false,
  email_verified BOOLEAN DEFAULT false,
  phone_verified BOOLEAN DEFAULT false,
  last_login_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

Roles Integration

-- Roles as enum or separate table
CREATE TYPE user_role AS ENUM ('USER', 'PROVIDER', 'ADMIN');

ALTER TABLE public.user_profiles 
ADD COLUMN role user_role DEFAULT 'USER';

Service Providers

CREATE TABLE public.service_providers (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id UUID REFERENCES public.user_profiles(id) UNIQUE NOT NULL,
  business_name TEXT,
  description TEXT,
  years_of_experience INTEGER,
  specializations TEXT,
  service_area TEXT,
  average_rating DECIMAL(3,2) DEFAULT 0.0,
  total_reviews INTEGER DEFAULT 0,
  is_verified BOOLEAN DEFAULT false,
  is_available BOOLEAN DEFAULT true,
  profile_image_url TEXT,
  license_number TEXT,
  insurance_details TEXT,
  website_url TEXT,
  social_media_links JSONB,
  emergency_contact TEXT,
  business_hours JSONB,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

2. Data Migration Script

-- Migration script to transform Spring Boot data to Supabase format
-- This will be executed after schema creation

3. Row Level Security Policies

-- User profiles RLS
ALTER TABLE public.user_profiles ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can view own profile" ON public.user_profiles
  FOR SELECT USING (auth.uid() = id);

CREATE POLICY "Users can update own profile" ON public.user_profiles
  FOR UPDATE USING (auth.uid() = id);

-- Service providers RLS
ALTER TABLE public.service_providers ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Anyone can view active providers" ON public.service_providers
  FOR SELECT USING (is_available = true);

CREATE POLICY "Providers can update own profile" ON public.service_providers
  FOR UPDATE USING (auth.uid() = user_id);

πŸ”§ Edge Functions Implementation

1. Authentication Functions

// supabase/functions/auth-helpers/index.ts
import { createClient } from '@supabase/supabase-js'

export const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}

export async function getUserProfile(supabase: any, userId: string) {
  const { data, error } = await supabase
    .from('user_profiles')
    .select('*')
    .eq('id', userId)
    .single()
  
  if (error) throw error
  return data
}

2. Provider Management Functions

// supabase/functions/providers/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from '@supabase/supabase-js'

serve(async (req) => {
  if (req.method === 'OPTIONS') {
    return new Response('ok', { headers: corsHeaders })
  }

  try {
    const supabase = createClient(
      Deno.env.get('SUPABASE_URL') ?? '',
      Deno.env.get('SUPABASE_ANON_KEY') ?? ''
    )

    const { method, url } = req
    const urlPath = new URL(url).pathname

    switch (method) {
      case 'GET':
        if (urlPath.includes('/search')) {
          return await searchProviders(req, supabase)
        }
        return await getProviders(req, supabase)
      
      case 'POST':
        return await createProvider(req, supabase)
      
      case 'PUT':
        return await updateProvider(req, supabase)
      
      default:
        return new Response('Method not allowed', { status: 405 })
    }
  } catch (error) {
    return new Response(JSON.stringify({ error: error.message }), {
      status: 400,
      headers: { ...corsHeaders, 'Content-Type': 'application/json' }
    })
  }
})

3. Service Management Functions

// supabase/functions/services/index.ts
// Similar structure for service CRUD operations

πŸ” Authentication Migration

1. Supabase Auth Configuration

// lib/supabase.ts
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
  auth: {
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: true
  }
})

2. Auth Service Migration

// services/authService.ts
import { supabase } from '../lib/supabase'

export const authService = {
  async signUp(email: string, password: string, userData: any) {
    const { data, error } = await supabase.auth.signUp({
      email,
      password,
      options: {
        data: userData
      }
    })
    
    if (error) throw error
    
    // Create user profile
    if (data.user) {
      await supabase
        .from('user_profiles')
        .insert({
          id: data.user.id,
          ...userData
        })
    }
    
    return data
  },

  async signIn(email: string, password: string) {
    const { data, error } = await supabase.auth.signInWithPassword({
      email,
      password
    })
    
    if (error) throw error
    return data
  },

  async signOut() {
    const { error } = await supabase.auth.signOut()
    if (error) throw error
  },

  async getCurrentUser() {
    const { data: { user } } = await supabase.auth.getUser()
    
    if (user) {
      const { data: profile } = await supabase
        .from('user_profiles')
        .select('*')
        .eq('id', user.id)
        .single()
      
      return { ...user, profile }
    }
    
    return null
  }
}

βš›οΈ Frontend Migration

1. Package Updates

{
  "dependencies": {
    "@supabase/supabase-js": "^2.38.0",
    "@supabase/auth-helpers-react": "^0.4.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  }
}

2. Context Provider Setup

// contexts/AuthContext.tsx
import React, { createContext, useContext, useEffect, useState } from 'react'
import { User, Session } from '@supabase/supabase-js'
import { supabase } from '../lib/supabase'

interface AuthContextType {
  user: User | null
  session: Session | null
  loading: boolean
  signIn: (email: string, password: string) => Promise<any>
  signUp: (email: string, password: string, userData: any) => Promise<any>
  signOut: () => Promise<void>
}

const AuthContext = createContext<AuthContextType>({} as AuthContextType)

export const useAuth = () => {
  const context = useContext(AuthContext)
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider')
  }
  return context
}

export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
  const [user, setUser] = useState<User | null>(null)
  const [session, setSession] = useState<Session | null>(null)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    // Get initial session
    supabase.auth.getSession().then(({ data: { session } }) => {
      setSession(session)
      setUser(session?.user ?? null)
      setLoading(false)
    })

    // Listen for auth changes
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      async (event, session) => {
        setSession(session)
        setUser(session?.user ?? null)
        setLoading(false)
      }
    )

    return () => subscription.unsubscribe()
  }, [])

  const value = {
    user,
    session,
    loading,
    signIn: authService.signIn,
    signUp: authService.signUp,
    signOut: authService.signOut
  }

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  )
}

3. API Service Updates

// services/providerService.ts
import { supabase } from '../lib/supabase'

export const providerService = {
  async getProviders(filters = {}) {
    let query = supabase
      .from('service_providers')
      .select(`
        *,
        user_profiles(*),
        services(*)
      `)
      .eq('is_available', true)

    // Apply filters
    if (filters.category) {
      query = query.eq('category_id', filters.category)
    }

    const { data, error } = await query
    if (error) throw error
    return data
  },

  async getProvider(id: string) {
    const { data, error } = await supabase
      .from('service_providers')
      .select(`
        *,
        user_profiles(*),
        services(*),
        reviews(*)
      `)
      .eq('id', id)
      .single()

    if (error) throw error
    return data
  },

  async createProvider(providerData: any) {
    const { data, error } = await supabase
      .from('service_providers')
      .insert(providerData)
      .select()
      .single()

    if (error) throw error
    return data
  }
}

πŸš€ Vercel Deployment Configuration

1. Vercel Configuration

{
  "version": 2,
  "builds": [
    {
      "src": "package.json",
      "use": "@vercel/static-build",
      "config": {
        "distDir": "build"
      }
    }
  ],
  "routes": [
    {
      "src": "/static/(.*)",
      "headers": {
        "cache-control": "s-maxage=31536000,immutable"
      }
    },
    {
      "src": "/(.*)",
      "dest": "/index.html"
    }
  ],
  "env": {
    "REACT_APP_SUPABASE_URL": "@supabase_url",
    "REACT_APP_SUPABASE_ANON_KEY": "@supabase_anon_key"
  }
}

2. Environment Variables

# Vercel Environment Variables
REACT_APP_SUPABASE_URL=https://your-project.supabase.co
REACT_APP_SUPABASE_ANON_KEY=your-anon-key
REACT_APP_APP_VERSION=2.0.0
REACT_APP_ENVIRONMENT=production

3. Build Optimization

{
  "scripts": {
    "build": "react-scripts build",
    "build:analyze": "npm run build && npx bundle-analyzer build/static/js/*.js",
    "build:prod": "GENERATE_SOURCEMAP=false npm run build"
  }
}

πŸ“ File Structure (New)

City-Service/
β”œβ”€β”€ supabase/
β”‚   β”œβ”€β”€ functions/
β”‚   β”‚   β”œβ”€β”€ auth-helpers/
β”‚   β”‚   β”œβ”€β”€ providers/
β”‚   β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”œβ”€β”€ categories/
β”‚   β”‚   └── admin/
β”‚   β”œβ”€β”€ migrations/
β”‚   └── config.toml
β”œβ”€β”€ frontend/
β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”‚   └── supabase.ts
β”‚   β”‚   β”œβ”€β”€ contexts/
β”‚   β”‚   β”‚   └── AuthContext.tsx
β”‚   β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”‚   β”œβ”€β”€ authService.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ providerService.ts
β”‚   β”‚   β”‚   └── serviceService.ts
β”‚   β”‚   └── components/
β”‚   β”œβ”€β”€ vercel.json
β”‚   └── package.json
└── migration/
    β”œβ”€β”€ data-export.sql
    β”œβ”€β”€ schema-migration.sql
    └── data-import.sql

πŸ”„ Real-time Features

1. Live Provider Updates

// hooks/useRealtimeProviders.ts
import { useEffect, useState } from 'react'
import { supabase } from '../lib/supabase'

export const useRealtimeProviders = () => {
  const [providers, setProviders] = useState([])

  useEffect(() => {
    // Initial fetch
    fetchProviders()

    // Subscribe to changes
    const subscription = supabase
      .channel('providers')
      .on('postgres_changes', 
        { event: '*', schema: 'public', table: 'service_providers' },
        (payload) => {
          handleRealtimeUpdate(payload)
        }
      )
      .subscribe()

    return () => {
      subscription.unsubscribe()
    }
  }, [])

  const fetchProviders = async () => {
    const { data } = await supabase
      .from('service_providers')
      .select('*')
    setProviders(data || [])
  }

  const handleRealtimeUpdate = (payload) => {
    switch (payload.eventType) {
      case 'INSERT':
        setProviders(prev => [...prev, payload.new])
        break
      case 'UPDATE':
        setProviders(prev => prev.map(p => 
          p.id === payload.new.id ? payload.new : p
        ))
        break
      case 'DELETE':
        setProviders(prev => prev.filter(p => p.id !== payload.old.id))
        break
    }
  }

  return { providers }
}

πŸ§ͺ Testing Strategy

1. Migration Testing

  • Data integrity validation
  • API endpoint compatibility
  • Authentication flow testing
  • Performance benchmarking
  • Security audit

2. End-to-End Testing

// tests/e2e/auth.test.ts
import { test, expect } from '@playwright/test'

test('user can sign up and sign in', async ({ page }) => {
  await page.goto('/register')
  
  // Fill registration form
  await page.fill('[data-testid="email"]', 'test@example.com')
  await page.fill('[data-testid="password"]', 'password123')
  await page.click('[data-testid="register-button"]')
  
  // Verify redirect to dashboard
  await expect(page).toHaveURL('/dashboard')
})

πŸ“Š Performance Optimization

1. Database Optimization

  • Proper indexing on frequently queried columns
  • Connection pooling configuration
  • Query optimization with EXPLAIN ANALYZE

2. Frontend Optimization

  • Code splitting with React.lazy()
  • Image optimization with Vercel
  • Bundle size monitoring
  • CDN utilization

3. Caching Strategy

  • Supabase query caching
  • Browser caching headers
  • Service worker implementation

πŸ”’ Security Considerations

1. Row Level Security

  • Comprehensive RLS policies
  • User data isolation
  • Admin access controls

2. API Security

  • Rate limiting on Edge Functions
  • Input validation and sanitization
  • CORS configuration

3. Authentication Security

  • Multi-factor authentication setup
  • Session management
  • Password policies

πŸ“‹ Migration Checklist

Pre-Migration

  • Backup existing database
  • Set up Supabase project
  • Create development environment
  • Test data export/import process

During Migration

  • Execute database migration
  • Deploy Edge Functions
  • Update frontend code
  • Configure Vercel deployment
  • Update DNS settings

Post-Migration

  • Verify all functionality
  • Performance testing
  • Security audit
  • User acceptance testing
  • Documentation updates

🚨 Rollback Plan

Emergency Rollback

  1. DNS Revert: Point domain back to original server
  2. Database Restore: Restore from pre-migration backup
  3. Code Revert: Revert to previous Git commit
  4. Service Restart: Restart original Spring Boot application

Partial Rollback

  • Ability to rollback individual components
  • Feature flags for gradual migration
  • A/B testing capabilities

πŸ“ž Support & Maintenance

Monitoring

  • Supabase dashboard monitoring
  • Vercel analytics
  • Error tracking with Sentry
  • Performance monitoring

Maintenance Tasks

  • Regular database maintenance
  • Security updates
  • Performance optimization
  • Backup verification

πŸ’° Cost Analysis

Supabase Costs

  • Free Tier: Up to 500MB database, 2GB bandwidth
  • Pro Tier: $25/month for production use
  • Additional: Storage and bandwidth overages

Vercel Costs

  • Hobby: Free for personal projects
  • Pro: $20/month for team collaboration
  • Enterprise: Custom pricing

Migration Costs

  • Development time: ~12 days
  • Testing and validation: ~3 days
  • Documentation: ~1 day
  • Total Estimated: 16 development days

πŸ“š Resources & Documentation

Supabase Resources

Vercel Resources

Migration Tools


This migration plan provides a comprehensive roadmap for transitioning from Spring Boot + PostgreSQL to Supabase + Vercel while maintaining all existing functionality and improving scalability, performance, and developer experience.