mirror of
https://github.com/Dvorinka/1356.git
synced 2026-06-05 12:22:56 +00:00
Added Supabase SQL migrations and environment config
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
# Supabase Configuration
|
||||
# Copy this file to .env and fill in your actual values
|
||||
# Then run the app with: flutter run --dart-define-from-file=.env
|
||||
|
||||
SUPABASE_URL=https://your-project.supabase.co
|
||||
SUPABASE_ANON_KEY=your-anon-key-here
|
||||
@@ -0,0 +1,5 @@
|
||||
# Supabase Configuration
|
||||
# Copy this file to .env and fill in your actual values
|
||||
|
||||
SUPABASE_URL=https://your-project.supabase.co
|
||||
SUPABASE_ANON_KEY=your-anon-key-here
|
||||
@@ -0,0 +1,214 @@
|
||||
-- Enable UUID extension
|
||||
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
||||
|
||||
-- Create users table
|
||||
CREATE TABLE IF NOT EXISTS public.users (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
auth_provider_id TEXT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
avatar_url TEXT,
|
||||
bio TEXT,
|
||||
is_public_profile BOOLEAN DEFAULT false,
|
||||
countdown_start_date TIMESTAMPTZ,
|
||||
countdown_end_date TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Create goals table
|
||||
CREATE TABLE IF NOT EXISTS public.goals (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
owner_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
progress INTEGER DEFAULT 0 CHECK (progress >= 0 AND progress <= 100),
|
||||
location_lat DOUBLE PRECISION,
|
||||
location_lng DOUBLE PRECISION,
|
||||
location_name TEXT,
|
||||
image_url TEXT,
|
||||
completed BOOLEAN DEFAULT false,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Create goal_steps table
|
||||
CREATE TABLE IF NOT EXISTS public.goal_steps (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
goal_id UUID NOT NULL REFERENCES public.goals(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
is_done BOOLEAN DEFAULT false,
|
||||
order_index INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Create followers table
|
||||
CREATE TABLE IF NOT EXISTS public.followers (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
|
||||
follower_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(user_id, follower_id)
|
||||
);
|
||||
|
||||
-- Create activities table
|
||||
CREATE TABLE IF NOT EXISTS public.activities (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
|
||||
type TEXT NOT NULL,
|
||||
payload JSONB,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Create notifications table
|
||||
CREATE TABLE IF NOT EXISTS public.notifications (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES public.users(id) ON DELETE CASCADE,
|
||||
type TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
body TEXT NOT NULL,
|
||||
scheduled_for TIMESTAMPTZ,
|
||||
delivered_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- Create indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_goals_owner_id ON public.goals(owner_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_goal_steps_goal_id ON public.goal_steps(goal_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_followers_user_id ON public.followers(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_followers_follower_id ON public.followers(follower_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_activities_user_id_created_at ON public.activities(user_id, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_notifications_user_id ON public.notifications(user_id);
|
||||
|
||||
-- Enable Row Level Security
|
||||
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.goals ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.goal_steps ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.followers ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.activities ENABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE public.notifications ENABLE ROW LEVEL SECURITY;
|
||||
|
||||
-- RLS Policies for users
|
||||
CREATE POLICY "Users can select their own profile"
|
||||
ON public.users FOR SELECT
|
||||
USING (auth.uid() = id);
|
||||
|
||||
CREATE POLICY "Users can update their own profile"
|
||||
ON public.users FOR UPDATE
|
||||
USING (auth.uid() = id)
|
||||
WITH CHECK (auth.uid() = id);
|
||||
|
||||
-- RLS Policies for goals
|
||||
CREATE POLICY "Users can read their own goals"
|
||||
ON public.goals FOR SELECT
|
||||
USING (auth.uid() = owner_id);
|
||||
|
||||
CREATE POLICY "Users can insert their own goals"
|
||||
ON public.goals FOR INSERT
|
||||
WITH CHECK (auth.uid() = owner_id);
|
||||
|
||||
CREATE POLICY "Users can update their own goals"
|
||||
ON public.goals FOR UPDATE
|
||||
USING (auth.uid() = owner_id)
|
||||
WITH CHECK (auth.uid() = owner_id);
|
||||
|
||||
CREATE POLICY "Users can delete their own goals"
|
||||
ON public.goals FOR DELETE
|
||||
USING (auth.uid() = owner_id);
|
||||
|
||||
-- RLS Policies for goal_steps
|
||||
CREATE POLICY "Users can read steps for their own goals"
|
||||
ON public.goal_steps FOR SELECT
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM public.goals g
|
||||
WHERE g.id = goal_id AND g.owner_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY "Users can insert steps for their own goals"
|
||||
ON public.goal_steps FOR INSERT
|
||||
WITH CHECK (
|
||||
EXISTS (
|
||||
SELECT 1 FROM public.goals g
|
||||
WHERE g.id = goal_id AND g.owner_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY "Users can update steps for their own goals"
|
||||
ON public.goal_steps FOR UPDATE
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM public.goals g
|
||||
WHERE g.id = goal_id AND g.owner_id = auth.uid()
|
||||
)
|
||||
)
|
||||
WITH CHECK (
|
||||
EXISTS (
|
||||
SELECT 1 FROM public.goals g
|
||||
WHERE g.id = goal_id AND g.owner_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
CREATE POLICY "Users can delete steps for their own goals"
|
||||
ON public.goal_steps FOR DELETE
|
||||
USING (
|
||||
EXISTS (
|
||||
SELECT 1 FROM public.goals g
|
||||
WHERE g.id = goal_id AND g.owner_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
-- RLS Policies for followers
|
||||
CREATE POLICY "Users can read their follower relationships"
|
||||
ON public.followers FOR SELECT
|
||||
USING (auth.uid() = user_id OR auth.uid() = follower_id);
|
||||
|
||||
CREATE POLICY "Users can follow others"
|
||||
ON public.followers FOR INSERT
|
||||
WITH CHECK (auth.uid() = follower_id);
|
||||
|
||||
CREATE POLICY "Users can unfollow or remove followers"
|
||||
ON public.followers FOR DELETE
|
||||
USING (auth.uid() = user_id OR auth.uid() = follower_id);
|
||||
|
||||
-- RLS Policies for activities
|
||||
CREATE POLICY "Users can read their own activity"
|
||||
ON public.activities FOR SELECT
|
||||
USING (auth.uid() = user_id);
|
||||
|
||||
CREATE POLICY "Users can insert their own activity events"
|
||||
ON public.activities FOR INSERT
|
||||
WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
-- RLS Policies for notifications
|
||||
CREATE POLICY "Users can read their own notifications"
|
||||
ON public.notifications FOR SELECT
|
||||
USING (auth.uid() = user_id);
|
||||
|
||||
CREATE POLICY "Users can receive their own notifications"
|
||||
ON public.notifications FOR INSERT
|
||||
WITH CHECK (auth.uid() = user_id);
|
||||
|
||||
CREATE POLICY "Users can delete their own notifications"
|
||||
ON public.notifications FOR DELETE
|
||||
USING (auth.uid() = user_id);
|
||||
|
||||
-- Function to update updated_at timestamp
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Triggers for updated_at
|
||||
CREATE TRIGGER update_users_updated_at
|
||||
BEFORE UPDATE ON public.users
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_goals_updated_at
|
||||
BEFORE UPDATE ON public.goals
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
@@ -0,0 +1,58 @@
|
||||
-- Create a view for public profile information
|
||||
-- This view exposes only non-sensitive information for public profiles
|
||||
CREATE OR REPLACE VIEW public.public_profiles AS
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
avatar_url,
|
||||
bio,
|
||||
is_public_profile,
|
||||
countdown_start_date,
|
||||
countdown_end_date,
|
||||
created_at
|
||||
FROM public.users
|
||||
WHERE is_public_profile = true;
|
||||
|
||||
-- Grant access to the view for authenticated users
|
||||
GRANT SELECT ON public.public_profiles TO authenticated;
|
||||
|
||||
-- Function to get public leaderboard
|
||||
CREATE OR REPLACE FUNCTION get_leaderboard(sort_by TEXT DEFAULT 'created_at', limit_count INTEGER DEFAULT 50)
|
||||
RETURNS TABLE (
|
||||
id UUID,
|
||||
username TEXT,
|
||||
avatar_url TEXT,
|
||||
bio TEXT,
|
||||
countdown_start_date TIMESTAMPTZ,
|
||||
countdown_end_date TIMESTAMPTZ,
|
||||
goals_completed_count INTEGER
|
||||
) AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
u.id,
|
||||
u.username,
|
||||
u.avatar_url,
|
||||
u.bio,
|
||||
u.countdown_start_date,
|
||||
u.countdown_end_date,
|
||||
COALESCE(
|
||||
(SELECT COUNT(*) FROM public.goals g WHERE g.owner_id = u.id AND g.completed = true),
|
||||
0
|
||||
) as goals_completed_count
|
||||
FROM public.users u
|
||||
WHERE u.is_public_profile = true
|
||||
ORDER BY
|
||||
CASE sort_by
|
||||
WHEN 'goals_completed' THEN COALESCE(
|
||||
(SELECT COUNT(*) FROM public.goals g WHERE g.owner_id = u.id AND g.completed = true),
|
||||
0
|
||||
)
|
||||
ELSE u.created_at
|
||||
END DESC
|
||||
LIMIT limit_count;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
-- Grant execute on the function
|
||||
GRANT EXECUTE ON FUNCTION get_leaderboard(TEXT, INTEGER) TO authenticated;
|
||||
@@ -0,0 +1,75 @@
|
||||
-- Function to automatically log goal creation activity
|
||||
CREATE OR REPLACE FUNCTION log_goal_created()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
INSERT INTO public.activities (user_id, type, payload)
|
||||
VALUES (
|
||||
NEW.owner_id,
|
||||
'goal_created',
|
||||
jsonb_build_object(
|
||||
'goal_id', NEW.id,
|
||||
'goal_title', NEW.title
|
||||
)
|
||||
);
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
-- Trigger for goal creation
|
||||
CREATE TRIGGER on_goal_created
|
||||
AFTER INSERT ON public.goals
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION log_goal_created();
|
||||
|
||||
-- Function to automatically log goal completion activity
|
||||
CREATE OR REPLACE FUNCTION log_goal_completed()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.completed = true AND (OLD.completed IS NULL OR OLD.completed = false) THEN
|
||||
INSERT INTO public.activities (user_id, type, payload)
|
||||
VALUES (
|
||||
NEW.owner_id,
|
||||
'goal_completed',
|
||||
jsonb_build_object(
|
||||
'goal_id', NEW.id,
|
||||
'goal_title', NEW.title,
|
||||
'progress', NEW.progress
|
||||
)
|
||||
);
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
-- Trigger for goal completion
|
||||
CREATE TRIGGER on_goal_completed
|
||||
AFTER UPDATE ON public.goals
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.completed IS DISTINCT FROM OLD.completed)
|
||||
EXECUTE FUNCTION log_goal_completed();
|
||||
|
||||
-- Function to automatically log countdown start activity
|
||||
CREATE OR REPLACE FUNCTION log_countdown_started()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.countdown_start_date IS NOT NULL AND (OLD.countdown_start_date IS NULL) THEN
|
||||
INSERT INTO public.activities (user_id, type, payload)
|
||||
VALUES (
|
||||
NEW.id,
|
||||
'countdown_started',
|
||||
jsonb_build_object(
|
||||
'start_date', NEW.countdown_start_date,
|
||||
'end_date', NEW.countdown_end_date
|
||||
)
|
||||
);
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
||||
|
||||
-- Trigger for countdown start
|
||||
CREATE TRIGGER on_countdown_started
|
||||
AFTER UPDATE ON public.users
|
||||
FOR EACH ROW
|
||||
WHEN (NEW.countdown_start_date IS DISTINCT FROM OLD.countdown_start_date)
|
||||
EXECUTE FUNCTION log_countdown_started();
|
||||
Reference in New Issue
Block a user