From 2bb58e041bc97304e58a932b125a0d48a3794808 Mon Sep 17 00:00:00 2001 From: root Date: Mon, 23 Feb 2026 19:27:47 +0000 Subject: [PATCH] Add Supabase integration for trading journal --- app/api/trading/trades/route.ts | 46 +++++++------- lib/supabase.ts | 66 ++++++++++++++++++++ package-lock.json | 105 ++++++++++++++++++++++++++++++++ package.json | 1 + supabase-setup.sql | 78 ++++++++++++++++++++++++ 5 files changed, 273 insertions(+), 23 deletions(-) create mode 100644 lib/supabase.ts create mode 100644 supabase-setup.sql diff --git a/app/api/trading/trades/route.ts b/app/api/trading/trades/route.ts index e5656df..266f639 100644 --- a/app/api/trading/trades/route.ts +++ b/app/api/trading/trades/route.ts @@ -1,39 +1,39 @@ import { NextRequest, NextResponse } from 'next/server' -import * as fs from 'fs' -import * as path from 'path' - -const dataFile = path.join(process.cwd(), 'trading-trades.json') +import { supabase } from '@/lib/supabase' export async function GET() { try { - let trades = [] - if (fs.existsSync(dataFile)) { - trades = JSON.parse(fs.readFileSync(dataFile, 'utf-8')) - } - return NextResponse.json({ trades }) + const { data: trades, error } = await supabase + .from('trades') + .select('*') + .order('opened_at', { ascending: false }) + + if (error) throw error + + return NextResponse.json({ trades: trades || [] }) } catch (error) { - return NextResponse.json({ trades: [] }) + console.error('Supabase error:', error) + return NextResponse.json({ trades: [], error: 'Failed to fetch' }, { status: 500 }) } } export async function POST(request: NextRequest) { try { const body = await request.json() - let trades = [] - if (fs.existsSync(dataFile)) { - trades = JSON.parse(fs.readFileSync(dataFile, 'utf-8')) - } - - trades.push({ - ...body, - id: Date.now().toString(), - openedAt: new Date().toISOString() - }) - - fs.writeFileSync(dataFile, JSON.stringify(trades, null, 2)) - return NextResponse.json({ success: true }) + const { data, error } = await supabase + .from('trades') + .insert([{ + ...body, + opened_at: new Date().toISOString(), + }]) + .select() + + if (error) throw error + + return NextResponse.json({ success: true, trade: data?.[0] }) } catch (error) { + console.error('Supabase error:', error) return NextResponse.json({ error: 'Failed to save' }, { status: 500 }) } } diff --git a/lib/supabase.ts b/lib/supabase.ts new file mode 100644 index 0000000..21ee531 --- /dev/null +++ b/lib/supabase.ts @@ -0,0 +1,66 @@ +import { createClient } from '@supabase/supabase-js' + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || 'https://mgqefaxhfmgkoqeachgi.supabase.co' +const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || 'sb_publishable_T2pJmKz7HIFy4WuT-6eknw_kYckbItP' + +export const supabase = createClient(supabaseUrl, supabaseAnonKey) + +// Database types +export interface Database { + public: { + Tables: { + trades: { + Row: { + id: string + user_id: string + pair: string + direction: 'long' | 'short' + entry_price: number + exit_price?: number + status: 'open' | 'closed' + is_demo: boolean + trader_style: string + setup: string + timeframe: string + pnl?: number + pnl_percent?: number + opened_at: string + closed_at?: string + created_at: string + } + Insert: Omit + Update: Partial + } + tasks: { + Row: { + id: string + user_id: string + title: string + description?: string + status: 'pending' | 'in_progress' | 'completed' + priority: 'low' | 'medium' | 'high' + due_date?: string + created_at: string + } + Insert: Omit + Update: Partial + } + leads: { + Row: { + id: string + user_id: string + name: string + business_name?: string + phone?: string + email?: string + source?: string + status: 'new' | 'contacted' | 'qualified' | 'won' | 'lost' + notes?: string + created_at: string + } + Insert: Omit + Update: Partial + } + } + } +} diff --git a/package-lock.json b/package-lock.json index 04fdc33..bca1192 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@google/genai": "^1.39.0", "@google/generative-ai": "^0.24.1", "@stripe/stripe-js": "^8.7.0", + "@supabase/supabase-js": "^2.97.0", "@vapi-ai/web": "^2.5.2", "chartjs-plugin-zoom": "^2.2.0", "framer-motion": "^12.23.12", @@ -968,6 +969,86 @@ "node": ">=12.16" } }, + "node_modules/@supabase/auth-js": { + "version": "2.97.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.97.0.tgz", + "integrity": "sha512-2Og/1lqp+AIavr8qS2X04aSl8RBY06y4LrtIAGxat06XoXYiDxKNQMQzWDAKm1EyZFZVRNH48DO5YvIZ7la5fQ==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.97.0", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.97.0.tgz", + "integrity": "sha512-fSaA0ZeBUS9hMgpGZt5shIZvfs3Mvx2ZdajQT4kv/whubqDBAp3GU5W8iIXy21MRvKmO2NpAj8/Q6y+ZkZyF/w==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.97.0", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.97.0.tgz", + "integrity": "sha512-g4Ps0eaxZZurvfv/KGoo2XPZNpyNtjth9aW8eho9LZWM0bUuBtxPZw3ZQ6ERSpEGogshR+XNgwlSPIwcuHCNww==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.97.0", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.97.0.tgz", + "integrity": "sha512-37Jw0NLaFP0CZd7qCan97D1zWutPrTSpgWxAw6Yok59JZoxp4IIKMrPeftJ3LZHmf+ILQOPy3i0pRDHM9FY36Q==", + "license": "MIT", + "dependencies": { + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.97.0", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.97.0.tgz", + "integrity": "sha512-9f6NniSBfuMxOWKwEFb+RjJzkfMdJUwv9oHuFJKfe/5VJR8cd90qw68m6Hn0ImGtwG37TUO+QHtoOechxRJ1Yg==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.97.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.97.0.tgz", + "integrity": "sha512-kTD91rZNO4LvRUHv4x3/4hNmsEd2ofkYhuba2VMUPRVef1RCmnHtm7rIws38Fg0yQnOSZOplQzafn0GSiy6GVg==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.97.0", + "@supabase/functions-js": "2.97.0", + "@supabase/postgrest-js": "2.97.0", + "@supabase/realtime-js": "2.97.0", + "@supabase/storage-js": "2.97.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@swc/helpers": { "version": "0.5.15", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", @@ -992,6 +1073,12 @@ "undici-types": "~7.16.0" } }, + "node_modules/@types/phoenix": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz", + "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==", + "license": "MIT" + }, "node_modules/@types/react": { "version": "19.2.10", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", @@ -1012,6 +1099,15 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@vapi-ai/web": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/@vapi-ai/web/-/web-2.5.2.tgz", @@ -1981,6 +2077,15 @@ "node": ">= 14" } }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", diff --git a/package.json b/package.json index 87f910d..387f560 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@google/genai": "^1.39.0", "@google/generative-ai": "^0.24.1", "@stripe/stripe-js": "^8.7.0", + "@supabase/supabase-js": "^2.97.0", "@vapi-ai/web": "^2.5.2", "chartjs-plugin-zoom": "^2.2.0", "framer-motion": "^12.23.12", diff --git a/supabase-setup.sql b/supabase-setup.sql new file mode 100644 index 0000000..cdc1149 --- /dev/null +++ b/supabase-setup.sql @@ -0,0 +1,78 @@ +-- Supabase Database Setup for SiteMente Mission Control +-- Run this in Supabase SQL Editor + +-- Enable UUID extension +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +-- Trading Journal Table +CREATE TABLE IF NOT EXISTS trades ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id TEXT DEFAULT 'default', + pair TEXT NOT NULL, + direction TEXT NOT NULL CHECK (direction IN ('long', 'short')), + entry_price NUMERIC, + exit_price NUMERIC, + status TEXT NOT NULL DEFAULT 'open' CHECK (status IN ('open', 'closed')), + is_demo BOOLEAN DEFAULT true, + trader_style TEXT, + setup TEXT, + timeframe TEXT, + pnl NUMERIC, + pnl_percent NUMERIC, + opened_at TIMESTAMPTZ DEFAULT NOW(), + closed_at TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Tasks Table +CREATE TABLE IF NOT EXISTS tasks ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id TEXT DEFAULT 'default', + title TEXT NOT NULL, + description TEXT, + status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'in_progress', 'completed')), + priority TEXT DEFAULT 'medium' CHECK (priority IN ('low', 'medium', 'high')), + due_date TIMESTAMPTZ, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Leads/CRM Table +CREATE TABLE IF NOT EXISTS leads ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + user_id TEXT DEFAULT 'default', + name TEXT NOT NULL, + business_name TEXT, + phone TEXT, + email TEXT, + source TEXT, + status TEXT NOT NULL DEFAULT 'new' CHECK (status IN ('new', 'contacted', 'qualified', 'won', 'lost')), + notes TEXT, + created_at TIMESTAMPTZ DEFAULT NOW() +); + +-- Enable RLS (Row Level Security) - optional for now +-- ALTER TABLE trades ENABLE ROW LEVEL SECURITY; +-- ALTER TABLE tasks ENABLE ROW LEVEL SECURITY; +-- ALTER TABLE leads ENABLE ROW LEVEL SECURITY; + +-- Create indexes for better performance +CREATE INDEX IF NOT EXISTS idx_trades_user ON trades(user_id); +CREATE INDEX IF NOT EXISTS idx_trades_status ON trades(status); +CREATE INDEX IF NOT EXISTS idx_tasks_user ON tasks(user_id); +CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status); +CREATE INDEX IF NOT EXISTS idx_leads_user ON leads(user_id); +CREATE INDEX IF NOT EXISTS idx_leads_status ON leads(status); + +-- Insert some sample data +INSERT INTO trades (pair, direction, entry_price, status, is_demo, trader_style, setup, timeframe) VALUES +('BTC/USD', 'long', 67500, 'open', true, 'thoth', 'Weekly structure break', '4H'), +('ETH/USD', 'long', 3200, 'open', true, 'dopetrades', 'Double bottom', '1H'); + +INSERT INTO tasks (title, description, status, priority) VALUES +('Fix Vapi integration', 'Get voice working on SiteMente', 'in_progress', 'high'), +('Contact local businesses', 'Reach out to leads in Benalmádena', 'pending', 'high'), +('Set up Supabase', 'Migrate from JSON to Supabase DB', 'in_progress', 'medium'); + +INSERT INTO leads (name, business_name, phone, status, source) VALUES +('Juan', 'Restaurante La Nina', '+34 952 449 193', 'new', 'cold_call'), +('Maria', 'Clínica Dental Málaga', '+34 951 123 456', 'contacted', 'website');