Initial commit: Vuelato - buscador de vuelos
Some checks failed
ci / ci (22, ubuntu-latest) (push) Has been cancelled
Some checks failed
ci / ci (22, ubuntu-latest) (push) Has been cancelled
Nuxt 4 + Supabase + Flightics API. Incluye búsqueda de vuelos, inspiraciones, watchlist, tracking de precios y mapa interactivo. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
151
supabase/migrations/00001_init.sql
Normal file
151
supabase/migrations/00001_init.sql
Normal file
@@ -0,0 +1,151 @@
|
||||
-- ============================================
|
||||
-- Vuelato: Schema completo
|
||||
-- Se ejecuta despues de que la imagen supabase/postgres
|
||||
-- haya creado los roles y el schema auth internamente.
|
||||
-- ============================================
|
||||
|
||||
-- Tablas de cache (publicas)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS airports (
|
||||
iata TEXT PRIMARY KEY,
|
||||
icao TEXT,
|
||||
name TEXT NOT NULL,
|
||||
lat DOUBLE PRECISION,
|
||||
lon DOUBLE PRECISION,
|
||||
city_id TEXT,
|
||||
city_name TEXT,
|
||||
country_code TEXT,
|
||||
country_name TEXT,
|
||||
region_slug TEXT,
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS countries (
|
||||
iso_code2 TEXT PRIMARY KEY,
|
||||
iso_code3 TEXT,
|
||||
name_eng TEXT,
|
||||
name_native TEXT,
|
||||
phone_prefix TEXT,
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS regions (
|
||||
slug TEXT PRIMARY KEY,
|
||||
name TEXT,
|
||||
name_localized TEXT,
|
||||
airport_codes TEXT[],
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS inspirations_cache (
|
||||
id SERIAL PRIMARY KEY,
|
||||
from_airport TEXT NOT NULL,
|
||||
to_airports TEXT[] NOT NULL,
|
||||
min_price NUMERIC(10,2),
|
||||
min_stops INTEGER,
|
||||
fetched_at TIMESTAMPTZ DEFAULT now(),
|
||||
UNIQUE(from_airport, to_airports)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS multi_city_cache (
|
||||
id SERIAL PRIMARY KEY,
|
||||
origin_codes TEXT[] NOT NULL,
|
||||
from_airport TEXT,
|
||||
stops TEXT[] NOT NULL,
|
||||
min_price NUMERIC(10,2),
|
||||
fetched_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Tablas de usuario (con RLS)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS profiles (
|
||||
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
home_airports TEXT[] DEFAULT '{}',
|
||||
default_adults INTEGER DEFAULT 1,
|
||||
default_children INTEGER DEFAULT 0,
|
||||
default_infants INTEGER DEFAULT 0,
|
||||
locale TEXT DEFAULT 'es',
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS watchlist (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
booking_token TEXT NOT NULL,
|
||||
route_summary TEXT NOT NULL,
|
||||
departure_code TEXT,
|
||||
arrival_code TEXT,
|
||||
departure_date TEXT,
|
||||
original_price NUMERIC(10,2) NOT NULL,
|
||||
current_price NUMERIC(10,2),
|
||||
price_status TEXT DEFAULT 'saved',
|
||||
passengers_adult INTEGER DEFAULT 1,
|
||||
passengers_child INTEGER DEFAULT 0,
|
||||
passengers_infant INTEGER DEFAULT 0,
|
||||
last_checked_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS recent_searches (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
search_params JSONB NOT NULL,
|
||||
route_summary TEXT,
|
||||
search_mode TEXT DEFAULT 'roundtrip',
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS price_alerts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
watchlist_id UUID REFERENCES watchlist(id) ON DELETE CASCADE,
|
||||
target_price NUMERIC(10,2),
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
last_notified_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Indices
|
||||
CREATE INDEX IF NOT EXISTS idx_airports_country ON airports(country_code);
|
||||
CREATE INDEX IF NOT EXISTS idx_airports_city ON airports(city_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_inspirations_from ON inspirations_cache(from_airport);
|
||||
CREATE INDEX IF NOT EXISTS idx_watchlist_user ON watchlist(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_recent_searches_user ON recent_searches(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_price_alerts_user ON price_alerts(user_id);
|
||||
|
||||
-- RLS
|
||||
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE watchlist ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE recent_searches ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE price_alerts ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Users see own profile" ON profiles
|
||||
FOR ALL USING (auth.uid() = id);
|
||||
CREATE POLICY "Users see own watchlist" ON watchlist
|
||||
FOR ALL USING (auth.uid() = user_id);
|
||||
CREATE POLICY "Users see own searches" ON recent_searches
|
||||
FOR ALL USING (auth.uid() = user_id);
|
||||
CREATE POLICY "Users see own alerts" ON price_alerts
|
||||
FOR ALL USING (auth.uid() = user_id);
|
||||
|
||||
-- Grants
|
||||
GRANT USAGE ON SCHEMA public TO anon, authenticated;
|
||||
GRANT SELECT ON airports, countries, regions, inspirations_cache, multi_city_cache
|
||||
TO anon, authenticated;
|
||||
GRANT ALL ON profiles, watchlist, recent_searches, price_alerts
|
||||
TO authenticated;
|
||||
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public
|
||||
TO anon, authenticated;
|
||||
|
||||
-- Trigger: crear profile automaticamente al registrarse
|
||||
CREATE OR REPLACE FUNCTION public.handle_new_user()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.profiles (id) VALUES (NEW.id);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
CREATE OR REPLACE TRIGGER on_auth_user_created
|
||||
AFTER INSERT ON auth.users
|
||||
FOR EACH ROW EXECUTE FUNCTION public.handle_new_user();
|
||||
12
supabase/migrations/00002_destination_images.sql
Normal file
12
supabase/migrations/00002_destination_images.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
-- Tabla cache para imagenes de destinos (Unsplash)
|
||||
CREATE TABLE IF NOT EXISTS destination_images (
|
||||
city_name TEXT PRIMARY KEY,
|
||||
image_url TEXT NOT NULL,
|
||||
thumb_url TEXT NOT NULL,
|
||||
photographer TEXT NOT NULL,
|
||||
photographer_url TEXT NOT NULL,
|
||||
cached_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
GRANT SELECT ON destination_images TO anon, authenticated;
|
||||
GRANT ALL ON destination_images TO service_role;
|
||||
101
supabase/migrations/00002_search_queue.sql
Normal file
101
supabase/migrations/00002_search_queue.sql
Normal file
@@ -0,0 +1,101 @@
|
||||
-- ============================================
|
||||
-- Vuelato: Sistema de cola de busquedas y seguimiento de precios
|
||||
-- Tablas: search_cache, tracked_searches, search_runs, price_snapshots
|
||||
-- ============================================
|
||||
|
||||
-- Cache de resultados de busqueda (publica, sin RLS)
|
||||
-- TTL gestionado por el worker (limpia >2h)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS search_cache (
|
||||
id SERIAL PRIMARY KEY,
|
||||
params_hash TEXT UNIQUE NOT NULL,
|
||||
search_params JSONB NOT NULL,
|
||||
trips JSONB NOT NULL DEFAULT '[]',
|
||||
cheapest_price NUMERIC(10,2),
|
||||
total_results INTEGER DEFAULT 0,
|
||||
fetched_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_search_cache_hash ON search_cache(params_hash);
|
||||
CREATE INDEX IF NOT EXISTS idx_search_cache_fetched ON search_cache(fetched_at);
|
||||
|
||||
-- Busquedas recurrentes configuradas por el usuario
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tracked_searches (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
search_params JSONB NOT NULL,
|
||||
route_summary TEXT NOT NULL,
|
||||
interval_hours INTEGER NOT NULL DEFAULT 24,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
next_run_at TIMESTAMPTZ DEFAULT now(),
|
||||
last_run_at TIMESTAMPTZ,
|
||||
run_count INTEGER DEFAULT 0,
|
||||
last_error TEXT,
|
||||
expires_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Log de cada ejecucion de una busqueda tracked
|
||||
|
||||
CREATE TABLE IF NOT EXISTS search_runs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tracked_search_id UUID NOT NULL REFERENCES tracked_searches(id) ON DELETE CASCADE,
|
||||
status TEXT NOT NULL DEFAULT 'pending',
|
||||
cheapest_price NUMERIC(10,2),
|
||||
total_trips_found INTEGER DEFAULT 0,
|
||||
top_trips JSONB,
|
||||
from_cache BOOLEAN DEFAULT false,
|
||||
error_message TEXT,
|
||||
started_at TIMESTAMPTZ,
|
||||
completed_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Snapshots de precio para graficos
|
||||
|
||||
CREATE TABLE IF NOT EXISTS price_snapshots (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
tracked_search_id UUID NOT NULL REFERENCES tracked_searches(id) ON DELETE CASCADE,
|
||||
search_run_id UUID NOT NULL REFERENCES search_runs(id) ON DELETE CASCADE,
|
||||
cheapest_price NUMERIC(10,2) NOT NULL,
|
||||
avg_price NUMERIC(10,2),
|
||||
median_price NUMERIC(10,2),
|
||||
total_results INTEGER DEFAULT 0,
|
||||
recorded_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
-- Indices
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_tracked_searches_user ON tracked_searches(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_tracked_searches_next_run ON tracked_searches(next_run_at) WHERE is_active = true;
|
||||
CREATE INDEX IF NOT EXISTS idx_search_runs_tracked ON search_runs(tracked_search_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_price_snapshots_tracked ON price_snapshots(tracked_search_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_price_snapshots_recorded ON price_snapshots(tracked_search_id, recorded_at);
|
||||
|
||||
-- RLS
|
||||
|
||||
ALTER TABLE tracked_searches ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE search_runs ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE price_snapshots ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
CREATE POLICY "Users see own tracked_searches" ON tracked_searches
|
||||
FOR ALL USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
CREATE POLICY "Users see own search_runs" ON search_runs
|
||||
FOR ALL USING (tracked_search_id IN (SELECT id FROM tracked_searches WHERE user_id = auth.uid()))
|
||||
WITH CHECK (tracked_search_id IN (SELECT id FROM tracked_searches WHERE user_id = auth.uid()));
|
||||
|
||||
CREATE POLICY "Users see own price_snapshots" ON price_snapshots
|
||||
FOR ALL USING (tracked_search_id IN (SELECT id FROM tracked_searches WHERE user_id = auth.uid()))
|
||||
WITH CHECK (tracked_search_id IN (SELECT id FROM tracked_searches WHERE user_id = auth.uid()));
|
||||
|
||||
-- Grants
|
||||
|
||||
GRANT SELECT ON search_cache TO anon, authenticated;
|
||||
GRANT ALL ON search_cache TO service_role;
|
||||
GRANT USAGE, SELECT ON SEQUENCE search_cache_id_seq TO service_role;
|
||||
|
||||
GRANT ALL ON tracked_searches, search_runs, price_snapshots TO authenticated;
|
||||
GRANT ALL ON tracked_searches, search_runs, price_snapshots TO service_role;
|
||||
1
supabase/migrations/00003_profile_preferences.sql
Normal file
1
supabase/migrations/00003_profile_preferences.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE profiles ADD COLUMN IF NOT EXISTS show_origin_time BOOLEAN DEFAULT false;
|
||||
14
supabase/migrations/00004_airlines.sql
Normal file
14
supabase/migrations/00004_airlines.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
-- Tabla de aerolineas (datos de Wikidata)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS airlines (
|
||||
iata TEXT PRIMARY KEY,
|
||||
icao TEXT,
|
||||
name TEXT NOT NULL,
|
||||
logo_url TEXT,
|
||||
website TEXT,
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_airlines_icao ON airlines(icao);
|
||||
|
||||
GRANT SELECT ON airlines TO anon, authenticated;
|
||||
5
supabase/migrations/00005_airline_booking_url.sql
Normal file
5
supabase/migrations/00005_airline_booking_url.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- Columnas de booking URL descubiertas automaticamente
|
||||
|
||||
ALTER TABLE airlines ADD COLUMN IF NOT EXISTS booking_url TEXT;
|
||||
ALTER TABLE airlines ADD COLUMN IF NOT EXISTS booking_url_template TEXT;
|
||||
ALTER TABLE airlines ADD COLUMN IF NOT EXISTS booking_url_discovered_at TIMESTAMPTZ;
|
||||
84
supabase/volumes/api/kong.yml
Normal file
84
supabase/volumes/api/kong.yml
Normal file
@@ -0,0 +1,84 @@
|
||||
_format_version: "1.1"
|
||||
|
||||
consumers:
|
||||
- username: anon
|
||||
keyauth_credentials:
|
||||
- key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
|
||||
- username: service_role
|
||||
keyauth_credentials:
|
||||
- key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU
|
||||
|
||||
acls:
|
||||
- consumer: anon
|
||||
group: anon
|
||||
- consumer: service_role
|
||||
group: admin
|
||||
|
||||
services:
|
||||
- name: auth-v1-open
|
||||
url: http://auth:9999/verify
|
||||
routes:
|
||||
- name: auth-v1-open
|
||||
strip_path: true
|
||||
paths:
|
||||
- /auth/v1/verify
|
||||
plugins:
|
||||
- name: cors
|
||||
|
||||
- name: auth-v1-open-callback
|
||||
url: http://auth:9999/callback
|
||||
routes:
|
||||
- name: auth-v1-open-callback
|
||||
strip_path: true
|
||||
paths:
|
||||
- /auth/v1/callback
|
||||
plugins:
|
||||
- name: cors
|
||||
|
||||
- name: auth-v1-open-authorize
|
||||
url: http://auth:9999/authorize
|
||||
routes:
|
||||
- name: auth-v1-open-authorize
|
||||
strip_path: true
|
||||
paths:
|
||||
- /auth/v1/authorize
|
||||
plugins:
|
||||
- name: cors
|
||||
|
||||
- name: auth-v1
|
||||
url: http://auth:9999/
|
||||
routes:
|
||||
- name: auth-v1-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /auth/v1/
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: key-auth
|
||||
config:
|
||||
hide_credentials: false
|
||||
- name: acl
|
||||
config:
|
||||
hide_groups_header: true
|
||||
allow:
|
||||
- admin
|
||||
- anon
|
||||
|
||||
- name: rest-v1
|
||||
url: http://rest:3000/
|
||||
routes:
|
||||
- name: rest-v1-all
|
||||
strip_path: true
|
||||
paths:
|
||||
- /rest/v1/
|
||||
plugins:
|
||||
- name: cors
|
||||
- name: key-auth
|
||||
config:
|
||||
hide_credentials: false
|
||||
- name: acl
|
||||
config:
|
||||
hide_groups_header: true
|
||||
allow:
|
||||
- admin
|
||||
- anon
|
||||
6
supabase/volumes/db/jwt.sql
Normal file
6
supabase/volumes/db/jwt.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
-- Configure JWT settings as database parameters
|
||||
\set jwt_secret `echo "$JWT_SECRET"`
|
||||
\set jwt_exp `echo "$JWT_EXP"`
|
||||
|
||||
ALTER DATABASE postgres SET "app.settings.jwt_secret" TO :'jwt_secret';
|
||||
ALTER DATABASE postgres SET "app.settings.jwt_exp" TO :'jwt_exp';
|
||||
6
supabase/volumes/db/roles.sql
Normal file
6
supabase/volumes/db/roles.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
-- Set passwords on pre-existing roles (created by supabase/postgres image)
|
||||
\set pgpass `echo "$POSTGRES_PASSWORD"`
|
||||
|
||||
ALTER USER authenticator WITH PASSWORD :'pgpass';
|
||||
ALTER USER supabase_auth_admin WITH PASSWORD :'pgpass';
|
||||
ALTER USER supabase_storage_admin WITH PASSWORD :'pgpass';
|
||||
Reference in New Issue
Block a user