Reorganize repository into monorepo structure

Restructured the project to support multiple frontend applications:
- Move web app to web/ directory
- Create pezkuwi-sdk-ui/ for Polkadot SDK clone (planned)
- Create mobile/ directory for mobile app development
- Add shared/ directory with common utilities, types, and blockchain code
- Update README.md with comprehensive documentation
- Remove obsolete DKSweb/ directory

This monorepo structure enables better code sharing and organized
development across web, mobile, and SDK UI projects.
This commit is contained in:
Claude
2025-11-14 00:46:35 +00:00
parent d66e46034a
commit 24be8d4411
206 changed files with 502 additions and 4 deletions
@@ -0,0 +1,255 @@
-- ========================================
-- PezkuwiChain - Initial Database Schema
-- ========================================
-- Run this in Supabase SQL Editor to set up required tables
-- Dashboard → SQL Editor → New Query → Paste & Run
-- ========================================
-- 1. PROFILES TABLE
-- ========================================
-- Stores user profile information and referral data
CREATE TABLE IF NOT EXISTS public.profiles (
id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
username TEXT UNIQUE NOT NULL,
email TEXT,
full_name TEXT,
avatar_url TEXT,
referred_by TEXT,
referral_code TEXT UNIQUE,
referral_count INTEGER DEFAULT 0,
total_referral_rewards DECIMAL(20, 4) DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL,
PRIMARY KEY (id)
);
-- Create index for faster lookups
CREATE INDEX IF NOT EXISTS idx_profiles_username ON public.profiles(username);
CREATE INDEX IF NOT EXISTS idx_profiles_email ON public.profiles(email);
CREATE INDEX IF NOT EXISTS idx_profiles_referral_code ON public.profiles(referral_code);
CREATE INDEX IF NOT EXISTS idx_profiles_referred_by ON public.profiles(referred_by);
-- Enable Row Level Security
ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY;
-- Drop existing policies if they exist
DROP POLICY IF EXISTS "Users can view their own profile" ON public.profiles;
DROP POLICY IF EXISTS "Users can update their own profile" ON public.profiles;
DROP POLICY IF EXISTS "Public profiles are viewable by everyone" ON public.profiles;
-- Create policies
CREATE POLICY "Users can view their own profile"
ON public.profiles FOR SELECT
USING (auth.uid() = id);
CREATE POLICY "Users can update their own profile"
ON public.profiles FOR UPDATE
USING (auth.uid() = id);
CREATE POLICY "Public profiles are viewable by everyone"
ON public.profiles FOR SELECT
USING (true);
-- ========================================
-- 2. ADMIN ROLES TABLE
-- ========================================
-- Stores admin and moderator role assignments
CREATE TABLE IF NOT EXISTS public.admin_roles (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
role TEXT NOT NULL CHECK (role IN ('admin', 'super_admin', 'moderator')),
granted_by UUID REFERENCES auth.users(id),
granted_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL,
UNIQUE(user_id)
);
-- Create index for faster lookups
CREATE INDEX IF NOT EXISTS idx_admin_roles_user_id ON public.admin_roles(user_id);
-- Enable Row Level Security
ALTER TABLE public.admin_roles ENABLE ROW LEVEL SECURITY;
-- Drop existing policies if they exist
DROP POLICY IF EXISTS "Admins can view admin roles" ON public.admin_roles;
DROP POLICY IF EXISTS "Super admins can manage admin roles" ON public.admin_roles;
-- Create policies
CREATE POLICY "Admins can view admin roles"
ON public.admin_roles FOR SELECT
USING (
EXISTS (
SELECT 1 FROM public.admin_roles
WHERE user_id = auth.uid() AND role IN ('admin', 'super_admin')
)
);
CREATE POLICY "Super admins can manage admin roles"
ON public.admin_roles FOR ALL
USING (
EXISTS (
SELECT 1 FROM public.admin_roles
WHERE user_id = auth.uid() AND role = 'super_admin'
)
);
-- ========================================
-- 3. WALLETS TABLE
-- ========================================
-- Stores user wallet addresses and metadata
CREATE TABLE IF NOT EXISTS public.wallets (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
address TEXT NOT NULL,
network TEXT NOT NULL DEFAULT 'pezkuwichain',
is_primary BOOLEAN DEFAULT false,
nickname TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL,
last_used_at TIMESTAMP WITH TIME ZONE,
UNIQUE(user_id, address, network)
);
-- Create index for faster lookups
CREATE INDEX IF NOT EXISTS idx_wallets_user_id ON public.wallets(user_id);
CREATE INDEX IF NOT EXISTS idx_wallets_address ON public.wallets(address);
-- Enable Row Level Security
ALTER TABLE public.wallets ENABLE ROW LEVEL SECURITY;
-- Drop existing policies if they exist
DROP POLICY IF EXISTS "Users can view their own wallets" ON public.wallets;
DROP POLICY IF EXISTS "Users can manage their own wallets" ON public.wallets;
-- Create policies
CREATE POLICY "Users can view their own wallets"
ON public.wallets FOR SELECT
USING (auth.uid() = user_id);
CREATE POLICY "Users can manage their own wallets"
ON public.wallets FOR ALL
USING (auth.uid() = user_id);
-- ========================================
-- 4. REFERRAL TRACKING TABLE
-- ========================================
-- Tracks referral rewards and history
CREATE TABLE IF NOT EXISTS public.referral_history (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
referrer_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
referred_user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
referral_code TEXT NOT NULL,
reward_amount DECIMAL(20, 4) DEFAULT 0,
reward_token TEXT DEFAULT 'PEZ',
reward_claimed BOOLEAN DEFAULT false,
claimed_at TIMESTAMP WITH TIME ZONE,
created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW()) NOT NULL,
UNIQUE(referred_user_id)
);
-- Create index for faster lookups
CREATE INDEX IF NOT EXISTS idx_referral_history_referrer ON public.referral_history(referrer_id);
CREATE INDEX IF NOT EXISTS idx_referral_history_referred ON public.referral_history(referred_user_id);
-- Enable Row Level Security
ALTER TABLE public.referral_history ENABLE ROW LEVEL SECURITY;
-- Drop existing policies if they exist
DROP POLICY IF EXISTS "Users can view their referral history" ON public.referral_history;
-- Create policies
CREATE POLICY "Users can view their referral history"
ON public.referral_history FOR SELECT
USING (auth.uid() = referrer_id OR auth.uid() = referred_user_id);
-- ========================================
-- 5. FUNCTIONS
-- ========================================
-- Function to auto-generate referral code on profile creation
CREATE OR REPLACE FUNCTION public.generate_referral_code()
RETURNS TRIGGER AS $$
DECLARE
new_code TEXT;
code_exists BOOLEAN;
BEGIN
-- Generate a random 8-character referral code
LOOP
new_code := UPPER(SUBSTRING(MD5(RANDOM()::TEXT) FROM 1 FOR 8));
-- Check if code already exists
SELECT EXISTS(SELECT 1 FROM public.profiles WHERE referral_code = new_code) INTO code_exists;
-- Exit loop if code is unique
EXIT WHEN NOT code_exists;
END LOOP;
NEW.referral_code := new_code;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Function to update updated_at timestamp
CREATE OR REPLACE FUNCTION public.update_updated_at_column()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = NOW();
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- ========================================
-- 6. TRIGGERS
-- ========================================
-- Drop existing triggers if they exist
DROP TRIGGER IF EXISTS trigger_generate_referral_code ON public.profiles;
DROP TRIGGER IF EXISTS trigger_update_profiles_updated_at ON public.profiles;
-- Auto-generate referral code when profile is created
CREATE TRIGGER trigger_generate_referral_code
BEFORE INSERT ON public.profiles
FOR EACH ROW
WHEN (NEW.referral_code IS NULL)
EXECUTE FUNCTION public.generate_referral_code();
-- Auto-update updated_at timestamp
CREATE TRIGGER trigger_update_profiles_updated_at
BEFORE UPDATE ON public.profiles
FOR EACH ROW
EXECUTE FUNCTION public.update_updated_at_column();
-- ========================================
-- 7. INITIAL DATA (OPTIONAL)
-- ========================================
-- Add founder as super admin (if exists)
-- Note: Replace 'founder-001' with actual founder user ID after first login
-- This is commented out by default for security
-- INSERT INTO public.admin_roles (user_id, role, granted_by)
-- VALUES ('founder-001', 'super_admin', 'founder-001')
-- ON CONFLICT (user_id) DO NOTHING;
-- ========================================
-- SUCCESS MESSAGE
-- ========================================
DO $$
BEGIN
RAISE NOTICE '========================================';
RAISE NOTICE 'Database schema created successfully!';
RAISE NOTICE '========================================';
RAISE NOTICE 'Tables created:';
RAISE NOTICE ' - profiles';
RAISE NOTICE ' - admin_roles';
RAISE NOTICE ' - wallets';
RAISE NOTICE ' - referral_history';
RAISE NOTICE '';
RAISE NOTICE 'Next steps:';
RAISE NOTICE '1. Test sign up on the web app';
RAISE NOTICE '2. Check if profile is created automatically';
RAISE NOTICE '3. Assign admin role manually if needed';
RAISE NOTICE '========================================';
END $$;
@@ -0,0 +1,79 @@
-- ========================================
-- Add Missing Columns to Profiles Table
-- ========================================
-- Run this in Supabase SQL Editor
-- Dashboard → SQL Editor → New Query → Paste & Run
-- Add bio column
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS bio TEXT;
-- Add phone column
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS phone TEXT;
-- Add language column (default: 'en')
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS language TEXT DEFAULT 'en';
-- Add theme column (default: 'dark')
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS theme TEXT DEFAULT 'dark';
-- Add notification preferences
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS notifications_email BOOLEAN DEFAULT true;
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS notifications_push BOOLEAN DEFAULT false;
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS notifications_sms BOOLEAN DEFAULT false;
-- Add 2FA column
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS two_factor_enabled BOOLEAN DEFAULT false;
-- Add location and website columns (for completeness)
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS location TEXT;
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS website TEXT;
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS phone_number TEXT;
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS recovery_email TEXT;
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS recovery_email_verified BOOLEAN DEFAULT false;
-- Add email_verified (for compatibility, though we prefer user.email_confirmed_at)
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS email_verified BOOLEAN DEFAULT false;
-- Add joined_at column
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS joined_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc'::text, NOW());
-- Add role column
ALTER TABLE public.profiles
ADD COLUMN IF NOT EXISTS role TEXT DEFAULT 'Member';
-- Create indexes for frequently queried columns
CREATE INDEX IF NOT EXISTS idx_profiles_phone ON public.profiles(phone);
CREATE INDEX IF NOT EXISTS idx_profiles_language ON public.profiles(language);
CREATE INDEX IF NOT EXISTS idx_profiles_location ON public.profiles(location);
-- Update existing users' joined_at to their created_at if null
UPDATE public.profiles
SET joined_at = created_at
WHERE joined_at IS NULL;
-- Success message
DO $$
BEGIN
RAISE NOTICE 'Profile columns added successfully!';
END $$;
@@ -0,0 +1,48 @@
-- ========================================
-- Fix Profile Creation Issue
-- ========================================
-- Adds INSERT policy and makes username nullable
-- Make username nullable and add default (must be done before policies)
ALTER TABLE public.profiles
ALTER COLUMN username DROP NOT NULL;
ALTER TABLE public.profiles
ALTER COLUMN username SET DEFAULT '';
-- Drop ALL existing policies to start fresh
DROP POLICY IF EXISTS "Users can view their own profile" ON public.profiles;
DROP POLICY IF EXISTS "Users can update their own profile" ON public.profiles;
DROP POLICY IF EXISTS "Public profiles are viewable by everyone" ON public.profiles;
DROP POLICY IF EXISTS "Users can insert their own profile" ON public.profiles;
-- Recreate policies with proper permissions
-- SELECT: Users can view their own profile
CREATE POLICY "Users can view their own profile"
ON public.profiles FOR SELECT
USING (auth.uid() = id);
-- INSERT: Users can create their own profile only
CREATE POLICY "Users can insert their own profile"
ON public.profiles FOR INSERT
WITH CHECK (auth.uid() = id);
-- UPDATE: Users can update their own profile only
CREATE POLICY "Users can update their own profile"
ON public.profiles FOR UPDATE
USING (auth.uid() = id)
WITH CHECK (auth.uid() = id);
-- PUBLIC SELECT: Allow everyone to view public profile info
CREATE POLICY "Public profiles are viewable by everyone"
ON public.profiles FOR SELECT
USING (true);
-- Success message
DO $$
BEGIN
RAISE NOTICE '========================================';
RAISE NOTICE 'Profile creation fix applied successfully!';
RAISE NOTICE 'Users can now create and update their profiles.';
RAISE NOTICE '========================================';
END $$;
@@ -0,0 +1,97 @@
-- ========================================
-- Create Secure Upsert Function for Profiles
-- ========================================
-- Uses SECURITY DEFINER to bypass RLS for authenticated users
-- First, ensure username is nullable
ALTER TABLE public.profiles
ALTER COLUMN username DROP NOT NULL;
ALTER TABLE public.profiles
ALTER COLUMN username SET DEFAULT '';
-- Create secure upsert function
CREATE OR REPLACE FUNCTION public.upsert_user_profile(
p_username TEXT DEFAULT '',
p_full_name TEXT DEFAULT NULL,
p_bio TEXT DEFAULT NULL,
p_phone_number TEXT DEFAULT NULL,
p_location TEXT DEFAULT NULL,
p_website TEXT DEFAULT NULL,
p_language TEXT DEFAULT 'en',
p_theme TEXT DEFAULT 'dark',
p_notifications_email BOOLEAN DEFAULT true,
p_notifications_push BOOLEAN DEFAULT false,
p_notifications_sms BOOLEAN DEFAULT false
)
RETURNS public.profiles AS $$
DECLARE
result public.profiles;
BEGIN
-- Use auth.uid() to ensure user can only upsert their own profile
INSERT INTO public.profiles (
id,
username,
full_name,
bio,
phone_number,
location,
website,
language,
theme,
notifications_email,
notifications_push,
notifications_sms,
updated_at
)
VALUES (
auth.uid(),
p_username,
p_full_name,
p_bio,
p_phone_number,
p_location,
p_website,
p_language,
p_theme,
p_notifications_email,
p_notifications_push,
p_notifications_sms,
NOW()
)
ON CONFLICT (id)
DO UPDATE SET
username = COALESCE(NULLIF(EXCLUDED.username, ''), profiles.username, ''),
full_name = COALESCE(EXCLUDED.full_name, profiles.full_name),
bio = COALESCE(EXCLUDED.bio, profiles.bio),
phone_number = COALESCE(EXCLUDED.phone_number, profiles.phone_number),
location = COALESCE(EXCLUDED.location, profiles.location),
website = COALESCE(EXCLUDED.website, profiles.website),
language = COALESCE(EXCLUDED.language, profiles.language),
theme = COALESCE(EXCLUDED.theme, profiles.theme),
notifications_email = COALESCE(EXCLUDED.notifications_email, profiles.notifications_email),
notifications_push = COALESCE(EXCLUDED.notifications_push, profiles.notifications_push),
notifications_sms = COALESCE(EXCLUDED.notifications_sms, profiles.notifications_sms),
updated_at = NOW()
RETURNING *
INTO result;
RETURN result;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- Grant execute permission to authenticated users only
GRANT EXECUTE ON FUNCTION public.upsert_user_profile TO authenticated;
-- Revoke from public for security
REVOKE EXECUTE ON FUNCTION public.upsert_user_profile FROM PUBLIC;
-- Success message
DO $$
BEGIN
RAISE NOTICE '========================================';
RAISE NOTICE 'Profile upsert function created successfully!';
RAISE NOTICE 'Function: upsert_user_profile()';
RAISE NOTICE 'This bypasses RLS using SECURITY DEFINER';
RAISE NOTICE '========================================';
END $$;