Files

414 lines
16 KiB
PL/PgSQL
Raw Permalink Normal View History

-- ============================================================
-- AppForge — application schema (de-Supabased)
-- Target: plain PostgreSQL. No RLS, no auth.* schema.
-- User identity is owned by Better Auth ("user" table, text id).
-- All user references are TEXT referencing "user"(id).
-- This file is idempotent (safe to re-run).
-- ============================================================
-- gen_random_uuid() is built into PG13+ core; pgcrypto kept for safety.
CREATE EXTENSION IF NOT EXISTS pgcrypto;
-- ---------- Enums ----------
DO $$ BEGIN
CREATE TYPE public.app_role AS ENUM ('admin', 'moderator', 'user');
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
DO $$ BEGIN
CREATE TYPE public.payment_method AS ENUM ('paypal', 'crypto', 'bank_transfer', 'stripe');
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
DO $$ BEGIN
CREATE TYPE public.subscription_tier AS ENUM ('free', 'pro', 'enterprise');
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
DO $$ BEGIN
CREATE TYPE public.transaction_status AS ENUM ('pending', 'completed', 'failed', 'refunded');
EXCEPTION WHEN duplicate_object THEN NULL; END $$;
-- ---------- updated_at trigger fn ----------
CREATE OR REPLACE FUNCTION public.update_updated_at_column() RETURNS trigger
LANGUAGE plpgsql AS $$
BEGIN
NEW.updated_at = now();
RETURN NEW;
END;
$$;
-- ============================================================
-- Tables
-- ============================================================
CREATE TABLE IF NOT EXISTS public.profiles (
id text PRIMARY KEY,
email text,
display_name text,
avatar_url text,
company_name text,
stripe_customer_id text UNIQUE,
marketing_consent boolean DEFAULT false,
marketing_consent_date timestamptz,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.user_roles (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
user_id text NOT NULL,
role public.app_role NOT NULL,
created_at timestamptz DEFAULT now() NOT NULL,
CONSTRAINT user_roles_user_id_role_key UNIQUE (user_id, role)
);
CREATE TABLE IF NOT EXISTS public.subscription_plans (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
tier public.subscription_tier NOT NULL UNIQUE,
name text NOT NULL,
description text,
price_monthly numeric(10,2) DEFAULT 0 NOT NULL,
price_yearly numeric(10,2) DEFAULT 0 NOT NULL,
monthly_credits integer DEFAULT 0 NOT NULL,
features jsonb DEFAULT '[]'::jsonb NOT NULL,
is_active boolean DEFAULT true NOT NULL,
stripe_price_id text,
stripe_yearly_price_id text,
paypal_plan_id text,
paypal_yearly_plan_id text,
paypal_product_id text,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.user_credits (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
user_id text NOT NULL UNIQUE,
monthly_credits integer DEFAULT 0 NOT NULL,
bonus_credits integer DEFAULT 0 NOT NULL,
credits_reset_at timestamptz,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.user_subscriptions (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
user_id text NOT NULL UNIQUE,
plan_id uuid NOT NULL REFERENCES public.subscription_plans(id),
status text DEFAULT 'active'::text NOT NULL,
billing_cycle text DEFAULT 'monthly'::text NOT NULL,
current_period_start timestamptz DEFAULT now() NOT NULL,
current_period_end timestamptz NOT NULL,
cancel_at_period_end boolean DEFAULT false NOT NULL,
payment_method public.payment_method,
external_subscription_id text,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.app_projects (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
user_id text NOT NULL,
website_url text NOT NULL,
app_name text NOT NULL,
primary_color text DEFAULT '#22D3EE'::text,
accent_color text DEFAULT '#A855F7'::text,
navigation_style text DEFAULT 'bottom-nav'::text,
features text[] DEFAULT '{}'::text[],
app_category text,
description text,
icon_style text DEFAULT 'modern'::text,
splash_screen_style text DEFAULT 'centered-logo'::text,
build_status text DEFAULT 'draft'::text,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL,
CONSTRAINT app_projects_user_id_website_url_key UNIQUE (user_id, website_url)
);
CREATE TABLE IF NOT EXISTS public.app_builds (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
user_id text NOT NULL,
project_id uuid REFERENCES public.app_projects(id) ON DELETE SET NULL,
website_url text NOT NULL,
app_name text NOT NULL,
package_name text NOT NULL,
config jsonb DEFAULT '{}'::jsonb NOT NULL,
status text DEFAULT 'pending'::text NOT NULL,
progress integer DEFAULT 0 NOT NULL,
download_url text,
file_size_bytes bigint,
error_message text,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL,
CONSTRAINT app_builds_status_check CHECK (status = ANY (ARRAY['pending'::text,'building'::text,'complete'::text,'failed'::text]))
);
CREATE TABLE IF NOT EXISTS public.app_templates (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
user_id text NOT NULL,
name text NOT NULL,
description text,
config jsonb NOT NULL,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.automation_configs (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
project_id uuid NOT NULL REFERENCES public.app_projects(id) ON DELETE CASCADE,
user_id text NOT NULL,
workflow_type text NOT NULL,
is_enabled boolean DEFAULT true NOT NULL,
config jsonb DEFAULT '{}'::jsonb NOT NULL,
last_run_at timestamptz,
next_run_at timestamptz,
run_count integer DEFAULT 0 NOT NULL,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL,
CONSTRAINT automation_configs_project_id_workflow_type_key UNIQUE (project_id, workflow_type)
);
CREATE TABLE IF NOT EXISTS public.automation_logs (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
automation_id uuid NOT NULL REFERENCES public.automation_configs(id) ON DELETE CASCADE,
user_id text NOT NULL,
status text DEFAULT 'pending'::text NOT NULL,
message text,
metadata jsonb DEFAULT '{}'::jsonb,
started_at timestamptz DEFAULT now() NOT NULL,
completed_at timestamptz,
created_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.credit_packs (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
name text NOT NULL,
credits integer NOT NULL,
price numeric(10,2) NOT NULL,
description text,
is_active boolean DEFAULT true NOT NULL,
stripe_price_id text,
created_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.credit_usage_history (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
user_id text NOT NULL,
amount integer NOT NULL,
action_type text NOT NULL,
description text,
metadata jsonb DEFAULT '{}'::jsonb,
created_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.payment_transactions (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
user_id text NOT NULL,
amount numeric(10,2) NOT NULL,
currency text DEFAULT 'USD'::text NOT NULL,
payment_method public.payment_method NOT NULL,
status public.transaction_status DEFAULT 'pending'::public.transaction_status NOT NULL,
transaction_type text NOT NULL,
reference_id text,
external_transaction_id text,
paypal_order_id text,
paypal_subscription_id text,
metadata jsonb DEFAULT '{}'::jsonb,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.bank_transfer_requests (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
user_id text NOT NULL,
transaction_id uuid REFERENCES public.payment_transactions(id),
amount numeric(10,2) NOT NULL,
currency text DEFAULT 'USD'::text NOT NULL,
plan_id uuid REFERENCES public.subscription_plans(id),
credit_pack_id uuid REFERENCES public.credit_packs(id),
status text DEFAULT 'pending'::text NOT NULL,
proof_of_payment_url text,
admin_notes text,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.chat_messages (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
user_id text NOT NULL,
role text NOT NULL,
content text NOT NULL,
project_id uuid REFERENCES public.app_projects(id) ON DELETE CASCADE,
created_at timestamptz DEFAULT now() NOT NULL,
CONSTRAINT chat_messages_role_check CHECK (role = ANY (ARRAY['user'::text,'assistant'::text]))
);
CREATE TABLE IF NOT EXISTS public.consent_records (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
user_id text,
email text,
consent_type text NOT NULL,
consented boolean DEFAULT false NOT NULL,
ip_address text,
user_agent text,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.email_templates (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
name text NOT NULL UNIQUE,
subject text NOT NULL,
html_content text NOT NULL,
text_content text,
variables jsonb DEFAULT '[]'::jsonb,
is_active boolean DEFAULT true NOT NULL,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.invoices (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
invoice_number text NOT NULL UNIQUE,
user_id text NOT NULL,
amount numeric NOT NULL,
currency text DEFAULT 'USD'::text NOT NULL,
status text DEFAULT 'draft'::text NOT NULL,
due_date timestamptz,
paid_at timestamptz,
items jsonb DEFAULT '[]'::jsonb NOT NULL,
notes text,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.api_configurations (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
name text NOT NULL,
provider text NOT NULL,
api_key_masked text,
is_active boolean DEFAULT false NOT NULL,
rate_limit integer DEFAULT 1000,
usage_count integer DEFAULT 0,
last_used_at timestamptz,
config jsonb DEFAULT '{}'::jsonb NOT NULL,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.plugins (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
name text NOT NULL,
slug text NOT NULL UNIQUE,
type text NOT NULL,
description text,
config jsonb DEFAULT '{}'::jsonb,
is_active boolean DEFAULT false NOT NULL,
version text DEFAULT '1.0.0'::text,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.system_settings (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
key text NOT NULL UNIQUE,
value jsonb DEFAULT '{}'::jsonb NOT NULL,
category text DEFAULT 'general'::text NOT NULL,
description text,
updated_at timestamptz DEFAULT now() NOT NULL,
updated_by text
);
CREATE TABLE IF NOT EXISTS public.settings_audit_log (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
setting_id uuid REFERENCES public.system_settings(id) ON DELETE SET NULL,
setting_key text NOT NULL,
old_value jsonb,
new_value jsonb NOT NULL,
changed_by text,
changed_by_email text,
change_type text DEFAULT 'update'::text NOT NULL,
created_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.payment_gateway_configs (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
gateway text NOT NULL UNIQUE,
is_enabled boolean DEFAULT false,
is_test_mode boolean DEFAULT true,
sandbox_config jsonb DEFAULT '{}'::jsonb,
live_config jsonb DEFAULT '{}'::jsonb,
created_at timestamptz DEFAULT now() NOT NULL,
updated_at timestamptz DEFAULT now() NOT NULL
);
CREATE TABLE IF NOT EXISTS public.webhook_event_logs (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
gateway text NOT NULL,
event_type text NOT NULL,
event_id text,
status text NOT NULL DEFAULT 'received',
payload jsonb DEFAULT '{}'::jsonb,
response_status integer,
error_message text,
processing_time_ms integer,
created_at timestamptz DEFAULT now() NOT NULL
);
-- ---------- Indexes ----------
CREATE INDEX IF NOT EXISTS idx_automation_configs_project ON public.automation_configs (project_id);
CREATE INDEX IF NOT EXISTS idx_automation_configs_type ON public.automation_configs (workflow_type);
CREATE INDEX IF NOT EXISTS idx_automation_configs_user ON public.automation_configs (user_id);
CREATE INDEX IF NOT EXISTS idx_automation_logs_automation ON public.automation_logs (automation_id);
CREATE INDEX IF NOT EXISTS idx_automation_logs_status ON public.automation_logs (status);
CREATE INDEX IF NOT EXISTS idx_chat_messages_user_project ON public.chat_messages (user_id, project_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_consent_records_email ON public.consent_records (email);
CREATE INDEX IF NOT EXISTS idx_consent_records_user_id ON public.consent_records (user_id);
CREATE INDEX IF NOT EXISTS idx_credit_usage_action ON public.credit_usage_history (action_type);
CREATE INDEX IF NOT EXISTS idx_credit_usage_user_date ON public.credit_usage_history (user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_app_builds_user ON public.app_builds (user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_app_projects_user ON public.app_projects (user_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_settings_audit_log_created_at ON public.settings_audit_log (created_at DESC);
CREATE INDEX IF NOT EXISTS idx_settings_audit_log_setting_id ON public.settings_audit_log (setting_id);
CREATE INDEX IF NOT EXISTS idx_webhook_event_logs_gateway ON public.webhook_event_logs (gateway);
CREATE INDEX IF NOT EXISTS idx_webhook_event_logs_created_at ON public.webhook_event_logs (created_at DESC);
CREATE INDEX IF NOT EXISTS idx_webhook_event_logs_status ON public.webhook_event_logs (status);
-- ---------- updated_at triggers ----------
DO $$
DECLARE t text;
BEGIN
FOR t IN
SELECT unnest(ARRAY[
'api_configurations','app_builds','app_projects','app_templates','automation_configs',
'bank_transfer_requests','consent_records','email_templates','invoices','payment_transactions',
'plugins','profiles','subscription_plans','system_settings','user_credits','user_subscriptions',
'payment_gateway_configs'
])
LOOP
EXECUTE format('DROP TRIGGER IF EXISTS update_%1$s_updated_at ON public.%1$s;', t);
EXECUTE format('CREATE TRIGGER update_%1$s_updated_at BEFORE UPDATE ON public.%1$s FOR EACH ROW EXECUTE FUNCTION public.update_updated_at_column();', t);
END LOOP;
END $$;
-- ============================================================
-- Seed data
-- ============================================================
INSERT INTO public.subscription_plans (tier, name, description, price_monthly, price_yearly, monthly_credits, features, is_active)
VALUES
('free', 'Free', 'Get started building apps', 0, 0, 5,
'["5 credits / month","Community support","Standard build queue"]'::jsonb, true),
('pro', 'Pro', 'For serious builders', 19, 190, 100,
'["100 credits / month","Priority builds","Email support","Custom branding"]'::jsonb, true),
('enterprise', 'Enterprise', 'For teams and agencies', 99, 990, 1000,
'["1000 credits / month","Fastest builds","Dedicated support","Team seats","White-label"]'::jsonb, true)
ON CONFLICT (tier) DO NOTHING;
INSERT INTO public.system_settings (key, value, category, description)
VALUES
('credits_per_build', '1'::jsonb, 'builds', 'Number of credits consumed per app build'),
('default_signup_credits', '5'::jsonb, 'app', 'Number of free credits given to new users on signup')
ON CONFLICT (key) DO NOTHING;
INSERT INTO public.payment_gateway_configs (gateway, is_enabled, is_test_mode)
VALUES ('paypal', false, true), ('stripe', false, true), ('coinbase', false, true), ('bank_transfer', false, false)
ON CONFLICT (gateway) DO NOTHING;