Skip to content

Commit 2516bcb

Browse files
committed
feat: add Supabase backend infrastructure and setup documentation
1 parent 624163e commit 2516bcb

File tree

8 files changed

+327
-0
lines changed

8 files changed

+327
-0
lines changed

backend/SUPABASE_SETUP.md

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Supabase Setup Guide
2+
3+
This guide will help you set up your Supabase project for the Portfolio Tracker application.
4+
5+
## 1. Create a Supabase Project
6+
7+
1. Go to [https://supabase.com/](https://supabase.com/) and sign up or log in
8+
2. Click "New Project" and follow the steps to create a new project
9+
3. Choose a name for your project and set a secure database password
10+
4. Select a region closest to you
11+
5. Wait for your project to be created (this may take a few minutes)
12+
13+
## 2. Create Database Tables
14+
15+
1. In your Supabase dashboard, go to the "SQL Editor" section
16+
2. Create a new query
17+
3. Copy and paste the contents of the `supabase-schema.sql` file into the editor
18+
4. Click "Run" to execute the SQL and create the tables
19+
20+
## 3. Set Up API Access
21+
22+
1. In your Supabase dashboard, go to the "Settings" section
23+
2. Click on "API" in the sidebar
24+
3. Under "Project API keys", copy the "anon" public key
25+
4. Also copy the "URL" value from the "Project URL" section
26+
27+
## 4. Configure Environment Variables
28+
29+
Update your `.env` file with the Supabase URL and key:
30+
31+
```
32+
SUPABASE_URL=your_project_url
33+
SUPABASE_KEY=your_anon_key
34+
FINNHUB_API_KEY=your_finnhub_api_key
35+
PORT=5000
36+
```
37+
38+
## 5. Test the Connection
39+
40+
Run the initialization script to test your connection:
41+
42+
```bash
43+
npm run init-db
44+
```
45+
46+
If successful, you should see a message confirming the connection to Supabase.
47+
48+
## 6. Row Level Security (Optional but Recommended)
49+
50+
For production environments, you should set up Row Level Security (RLS) policies in Supabase:
51+
52+
1. Go to the "Authentication" section, then "Policies"
53+
2. For each table, create appropriate RLS policies based on your requirements
54+
55+
## 7. Additional Configuration
56+
57+
- Consider setting up Supabase Auth if you want to add user authentication
58+
- Set up automated backups for your database
59+
- Configure CORS settings if needed
60+
61+
For more information, refer to the [Supabase documentation](https://supabase.com/docs).

backend/add-user-id-migration.sql

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
-- Migration to add user_id columns for user-specific data isolation
2+
-- Run this in your Supabase SQL Editor
3+
4+
-- Add user_id column to stocks table
5+
ALTER TABLE stocks
6+
ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE;
7+
8+
-- Add user_id column to watchlists table
9+
ALTER TABLE watchlists
10+
ADD COLUMN IF NOT EXISTS user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE;
11+
12+
-- Create indexes for better performance
13+
CREATE INDEX IF NOT EXISTS idx_stocks_user_id ON stocks(user_id);
14+
CREATE INDEX IF NOT EXISTS idx_watchlists_user_id ON watchlists(user_id);
15+
16+
-- Add RLS (Row Level Security) policies
17+
-- Enable RLS on both tables
18+
ALTER TABLE stocks ENABLE ROW LEVEL SECURITY;
19+
ALTER TABLE watchlists ENABLE ROW LEVEL SECURITY;
20+
21+
-- Create policy for stocks table - users can only see their own stocks
22+
CREATE POLICY "Users can view own stocks" ON stocks
23+
FOR SELECT USING (auth.uid() = user_id);
24+
25+
CREATE POLICY "Users can insert own stocks" ON stocks
26+
FOR INSERT WITH CHECK (auth.uid() = user_id);
27+
28+
CREATE POLICY "Users can update own stocks" ON stocks
29+
FOR UPDATE USING (auth.uid() = user_id);
30+
31+
CREATE POLICY "Users can delete own stocks" ON stocks
32+
FOR DELETE USING (auth.uid() = user_id);
33+
34+
-- Create policy for watchlists table - users can only see their own watchlist items
35+
CREATE POLICY "Users can view own watchlist" ON watchlists
36+
FOR SELECT USING (auth.uid() = user_id);
37+
38+
CREATE POLICY "Users can insert own watchlist items" ON watchlists
39+
FOR INSERT WITH CHECK (auth.uid() = user_id);
40+
41+
CREATE POLICY "Users can update own watchlist items" ON watchlists
42+
FOR UPDATE USING (auth.uid() = user_id);
43+
44+
CREATE POLICY "Users can delete own watchlist items" ON watchlists
45+
FOR DELETE USING (auth.uid() = user_id);
46+
47+
-- Note: After running this migration, existing data will have NULL user_id
48+
-- You may want to clean up existing data or assign it to a specific user

backend/clean-database.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
require('dotenv').config();
2+
const supabase = require('./src/config/supabase');
3+
4+
async function cleanDatabase() {
5+
try {
6+
console.log('Cleaning database...');
7+
8+
// Delete all records from stocks table
9+
const { error: stocksError } = await supabase
10+
.from('stocks')
11+
.delete()
12+
.neq('id', 0); // This will delete all records
13+
14+
if (stocksError) {
15+
console.error('Error deleting stocks:', stocksError);
16+
} else {
17+
console.log('Successfully deleted all stocks');
18+
}
19+
20+
// Delete all records from watchlists table
21+
const { error: watchlistsError } = await supabase
22+
.from('watchlists')
23+
.delete()
24+
.neq('id', 0); // This will delete all records
25+
26+
if (watchlistsError) {
27+
console.error('Error deleting watchlists:', watchlistsError);
28+
} else {
29+
console.log('Successfully deleted all watchlists');
30+
}
31+
32+
console.log('Database cleaning completed');
33+
} catch (error) {
34+
console.error('Error cleaning database:', error);
35+
}
36+
}
37+
38+
// Run the cleaning
39+
cleanDatabase();

backend/fix-rls-policies.sql

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
-- Fix RLS policies to work with backend API authentication
2+
-- Run this in your Supabase SQL Editor
3+
4+
-- Drop existing policies
5+
DROP POLICY IF EXISTS "Users can view own stocks" ON stocks;
6+
DROP POLICY IF EXISTS "Users can insert own stocks" ON stocks;
7+
DROP POLICY IF EXISTS "Users can update own stocks" ON stocks;
8+
DROP POLICY IF EXISTS "Users can delete own stocks" ON stocks;
9+
10+
DROP POLICY IF EXISTS "Users can view own watchlist" ON watchlists;
11+
DROP POLICY IF EXISTS "Users can insert own watchlist items" ON watchlists;
12+
DROP POLICY IF EXISTS "Users can update own watchlist items" ON watchlists;
13+
DROP POLICY IF EXISTS "Users can delete own watchlist items" ON watchlists;
14+
15+
-- Create new policies that work with backend API
16+
-- For stocks table
17+
CREATE POLICY "Users can view own stocks" ON stocks
18+
FOR SELECT USING (user_id IS NOT NULL);
19+
20+
CREATE POLICY "Users can insert own stocks" ON stocks
21+
FOR INSERT WITH CHECK (user_id IS NOT NULL);
22+
23+
CREATE POLICY "Users can update own stocks" ON stocks
24+
FOR UPDATE USING (user_id IS NOT NULL);
25+
26+
CREATE POLICY "Users can delete own stocks" ON stocks
27+
FOR DELETE USING (user_id IS NOT NULL);
28+
29+
-- For watchlists table
30+
CREATE POLICY "Users can view own watchlist" ON watchlists
31+
FOR SELECT USING (user_id IS NOT NULL);
32+
33+
CREATE POLICY "Users can insert own watchlist items" ON watchlists
34+
FOR INSERT WITH CHECK (user_id IS NOT NULL);
35+
36+
CREATE POLICY "Users can update own watchlist items" ON watchlists
37+
FOR UPDATE USING (user_id IS NOT NULL);
38+
39+
CREATE POLICY "Users can delete own watchlist items" ON watchlists
40+
FOR DELETE USING (user_id IS NOT NULL);
41+
42+
-- Note: This approach relies on the backend to properly set user_id
43+
-- The backend authentication middleware ensures only authenticated users can access the API

backend/init-db.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
require('dotenv').config();
2+
const supabase = require('./src/config/supabase');
3+
4+
async function initializeDatabase() {
5+
try {
6+
console.log('Testing connection to Supabase...');
7+
8+
// Test connection by fetching version
9+
const { data, error } = await supabase.from('stocks').select('*').limit(1);
10+
11+
if (error) {
12+
console.error('Error connecting to Supabase:', error);
13+
console.log('Please make sure:');
14+
console.log('1. Your Supabase URL and API key are correct in .env');
15+
console.log('2. You have created the "stocks" and "watchlists" tables in Supabase');
16+
console.log('3. You have proper permissions set up');
17+
process.exit(1);
18+
}
19+
20+
console.log('Successfully connected to Supabase!');
21+
console.log('Tables should be created in the Supabase dashboard.');
22+
console.log('Make sure you have the following tables:');
23+
console.log('1. stocks - with columns: id, name, ticker, shares, buy_price, current_price, target_price, is_in_watchlist, last_updated');
24+
console.log('2. watchlists - with columns: id, name, ticker, target_price, current_price, last_updated, created_at, updated_at');
25+
26+
} catch (error) {
27+
console.error('Error initializing database:', error);
28+
process.exit(1);
29+
}
30+
}
31+
32+
// Run the initialization
33+
initializeDatabase();

backend/src/config/supabase.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const { createClient } = require('@supabase/supabase-js');
2+
require('dotenv').config();
3+
4+
const SUPABASE_URL = process.env.SUPABASE_URL;
5+
const SUPABASE_KEY = process.env.SUPABASE_KEY;
6+
7+
if (!SUPABASE_URL || !SUPABASE_KEY) {
8+
console.warn('SUPABASE_URL or SUPABASE_KEY environment variables are not set. Some features will be limited.');
9+
}
10+
11+
const supabase = createClient(
12+
SUPABASE_URL || 'https://example.supabase.co',
13+
SUPABASE_KEY || 'demo-key'
14+
);
15+
16+
module.exports = supabase;

backend/src/middleware/auth.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
const { createClient } = require('@supabase/supabase-js');
2+
3+
// Debug environment variables
4+
console.log('🔍 Auth Middleware Environment Check:', {
5+
hasSupabaseUrl: !!process.env.SUPABASE_URL,
6+
hasSupabaseKey: !!process.env.SUPABASE_KEY,
7+
supabaseUrl: process.env.SUPABASE_URL ? 'Set' : 'Missing',
8+
supabaseKey: process.env.SUPABASE_KEY ? 'Set' : 'Missing'
9+
});
10+
11+
// Create Supabase client for auth verification
12+
const supabase = createClient(
13+
process.env.SUPABASE_URL,
14+
process.env.SUPABASE_KEY
15+
);
16+
17+
const authenticateUser = async (req, res, next) => {
18+
try {
19+
console.log('🔍 Auth Middleware Debug:', {
20+
url: req.url,
21+
method: req.method,
22+
hasAuthHeader: !!req.headers.authorization,
23+
authHeaderPreview: req.headers.authorization ? `${req.headers.authorization.substring(0, 30)}...` : 'No header'
24+
});
25+
26+
const authHeader = req.headers.authorization;
27+
28+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
29+
console.log('❌ No valid auth header found');
30+
return res.status(401).json({ error: 'No token provided' });
31+
}
32+
33+
const token = authHeader.substring(7); // Remove 'Bearer ' prefix
34+
console.log('🔑 Token extracted, length:', token.length);
35+
36+
// Verify the JWT token with Supabase
37+
const { data: { user }, error } = await supabase.auth.getUser(token);
38+
39+
if (error || !user) {
40+
console.error('❌ Token verification failed:', error);
41+
return res.status(401).json({ error: 'Invalid token' });
42+
}
43+
44+
console.log('✅ User authenticated:', user.id);
45+
46+
// Add user to request object
47+
req.user = user;
48+
next();
49+
} catch (error) {
50+
console.error('❌ Auth middleware error:', error);
51+
res.status(500).json({ error: 'Authentication failed' });
52+
}
53+
};
54+
55+
module.exports = { authenticateUser };

backend/supabase-schema.sql

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
-- Create stocks table
2+
CREATE TABLE IF NOT EXISTS stocks (
3+
id SERIAL PRIMARY KEY,
4+
name TEXT NOT NULL,
5+
ticker TEXT NOT NULL,
6+
shares FLOAT NOT NULL DEFAULT 0,
7+
buy_price FLOAT NOT NULL DEFAULT 0,
8+
current_price FLOAT NOT NULL DEFAULT 0,
9+
target_price FLOAT NOT NULL DEFAULT 0,
10+
is_in_watchlist BOOLEAN NOT NULL DEFAULT false,
11+
last_updated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
12+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
13+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
14+
);
15+
16+
-- Create watchlists table
17+
CREATE TABLE IF NOT EXISTS watchlists (
18+
id SERIAL PRIMARY KEY,
19+
name TEXT NOT NULL,
20+
ticker TEXT NOT NULL,
21+
target_price DECIMAL(10, 2) NOT NULL,
22+
current_price DECIMAL(10, 2),
23+
last_updated TIMESTAMP WITH TIME ZONE,
24+
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
25+
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
26+
);
27+
28+
-- Create indexes for better performance
29+
CREATE INDEX IF NOT EXISTS idx_stocks_ticker ON stocks(ticker);
30+
CREATE INDEX IF NOT EXISTS idx_watchlists_ticker ON watchlists(ticker);
31+
32+
-- Note: Run this script in the Supabase SQL Editor

0 commit comments

Comments
 (0)