Add Supabase integration for trading journal
This commit is contained in:
@@ -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({
|
||||
const { data, error } = await supabase
|
||||
.from('trades')
|
||||
.insert([{
|
||||
...body,
|
||||
id: Date.now().toString(),
|
||||
openedAt: new Date().toISOString()
|
||||
})
|
||||
opened_at: new Date().toISOString(),
|
||||
}])
|
||||
.select()
|
||||
|
||||
fs.writeFileSync(dataFile, JSON.stringify(trades, null, 2))
|
||||
return NextResponse.json({ success: true })
|
||||
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 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Database['public']['Tables']['trades']['Row'], 'id' | 'created_at'>
|
||||
Update: Partial<Database['public']['Tables']['trades']['Insert']>
|
||||
}
|
||||
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<Database['public']['Tables']['tasks']['Row'], 'id' | 'created_at'>
|
||||
Update: Partial<Database['public']['Tables']['tasks']['Insert']>
|
||||
}
|
||||
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<Database['public']['Tables']['leads']['Row'], 'id' | 'created_at'>
|
||||
Update: Partial<Database['public']['Tables']['leads']['Insert']>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+105
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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');
|
||||
Reference in New Issue
Block a user