Add Supabase integration for trading journal
This commit is contained in:
@@ -1,39 +1,39 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server'
|
import { NextRequest, NextResponse } from 'next/server'
|
||||||
import * as fs from 'fs'
|
import { supabase } from '@/lib/supabase'
|
||||||
import * as path from 'path'
|
|
||||||
|
|
||||||
const dataFile = path.join(process.cwd(), 'trading-trades.json')
|
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
let trades = []
|
const { data: trades, error } = await supabase
|
||||||
if (fs.existsSync(dataFile)) {
|
.from('trades')
|
||||||
trades = JSON.parse(fs.readFileSync(dataFile, 'utf-8'))
|
.select('*')
|
||||||
}
|
.order('opened_at', { ascending: false })
|
||||||
return NextResponse.json({ trades })
|
|
||||||
|
if (error) throw error
|
||||||
|
|
||||||
|
return NextResponse.json({ trades: trades || [] })
|
||||||
} catch (error) {
|
} 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) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const body = await request.json()
|
const body = await request.json()
|
||||||
let trades = []
|
|
||||||
|
|
||||||
if (fs.existsSync(dataFile)) {
|
const { data, error } = await supabase
|
||||||
trades = JSON.parse(fs.readFileSync(dataFile, 'utf-8'))
|
.from('trades')
|
||||||
}
|
.insert([{
|
||||||
|
...body,
|
||||||
|
opened_at: new Date().toISOString(),
|
||||||
|
}])
|
||||||
|
.select()
|
||||||
|
|
||||||
trades.push({
|
if (error) throw error
|
||||||
...body,
|
|
||||||
id: Date.now().toString(),
|
|
||||||
openedAt: new Date().toISOString()
|
|
||||||
})
|
|
||||||
|
|
||||||
fs.writeFileSync(dataFile, JSON.stringify(trades, null, 2))
|
return NextResponse.json({ success: true, trade: data?.[0] })
|
||||||
return NextResponse.json({ success: true })
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error('Supabase error:', error)
|
||||||
return NextResponse.json({ error: 'Failed to save' }, { status: 500 })
|
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/genai": "^1.39.0",
|
||||||
"@google/generative-ai": "^0.24.1",
|
"@google/generative-ai": "^0.24.1",
|
||||||
"@stripe/stripe-js": "^8.7.0",
|
"@stripe/stripe-js": "^8.7.0",
|
||||||
|
"@supabase/supabase-js": "^2.97.0",
|
||||||
"@vapi-ai/web": "^2.5.2",
|
"@vapi-ai/web": "^2.5.2",
|
||||||
"chartjs-plugin-zoom": "^2.2.0",
|
"chartjs-plugin-zoom": "^2.2.0",
|
||||||
"framer-motion": "^12.23.12",
|
"framer-motion": "^12.23.12",
|
||||||
@@ -968,6 +969,86 @@
|
|||||||
"node": ">=12.16"
|
"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": {
|
"node_modules/@swc/helpers": {
|
||||||
"version": "0.5.15",
|
"version": "0.5.15",
|
||||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
|
||||||
@@ -992,6 +1073,12 @@
|
|||||||
"undici-types": "~7.16.0"
|
"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": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.2.10",
|
"version": "19.2.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
|
||||||
@@ -1012,6 +1099,15 @@
|
|||||||
"@types/react": "^19.2.0"
|
"@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": {
|
"node_modules/@vapi-ai/web": {
|
||||||
"version": "2.5.2",
|
"version": "2.5.2",
|
||||||
"resolved": "https://registry.npmjs.org/@vapi-ai/web/-/web-2.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/@vapi-ai/web/-/web-2.5.2.tgz",
|
||||||
@@ -1981,6 +2077,15 @@
|
|||||||
"node": ">= 14"
|
"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": {
|
"node_modules/is-binary-path": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
"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/genai": "^1.39.0",
|
||||||
"@google/generative-ai": "^0.24.1",
|
"@google/generative-ai": "^0.24.1",
|
||||||
"@stripe/stripe-js": "^8.7.0",
|
"@stripe/stripe-js": "^8.7.0",
|
||||||
|
"@supabase/supabase-js": "^2.97.0",
|
||||||
"@vapi-ai/web": "^2.5.2",
|
"@vapi-ai/web": "^2.5.2",
|
||||||
"chartjs-plugin-zoom": "^2.2.0",
|
"chartjs-plugin-zoom": "^2.2.0",
|
||||||
"framer-motion": "^12.23.12",
|
"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