mitai-jinkendo/backend/schema.sql
Lars 0a871fea22
Some checks failed
Deploy Development / deploy (push) Failing after 1s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 1m6s
9b
2026-03-18 08:27:33 +01:00

261 lines
12 KiB
PL/PgSQL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- ================================================================
-- MITAI JINKENDO v9b PostgreSQL Schema
-- ================================================================
-- Migration from SQLite to PostgreSQL
-- Includes v9b Tier System features
-- ================================================================
-- Enable UUID Extension
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
-- ================================================================
-- CORE TABLES
-- ================================================================
-- ── Profiles Table ──────────────────────────────────────────────
-- User/Profile management with auth and permissions
CREATE TABLE IF NOT EXISTS profiles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(255) NOT NULL DEFAULT 'Nutzer',
avatar_color VARCHAR(7) DEFAULT '#1D9E75',
photo_id UUID,
sex VARCHAR(1) DEFAULT 'm' CHECK (sex IN ('m', 'w', 'd')),
dob DATE,
height NUMERIC(5,2) DEFAULT 178,
goal_weight NUMERIC(5,2),
goal_bf_pct NUMERIC(4,2),
-- Auth & Permissions
role VARCHAR(20) DEFAULT 'user' CHECK (role IN ('user', 'admin')),
pin_hash TEXT,
auth_type VARCHAR(20) DEFAULT 'pin' CHECK (auth_type IN ('pin', 'email')),
session_days INTEGER DEFAULT 30,
ai_enabled BOOLEAN DEFAULT TRUE,
ai_limit_day INTEGER,
export_enabled BOOLEAN DEFAULT TRUE,
email VARCHAR(255) UNIQUE,
-- v9b: Tier System
tier VARCHAR(20) DEFAULT 'free' CHECK (tier IN ('free', 'basic', 'premium', 'selfhosted')),
tier_expires_at TIMESTAMP WITH TIME ZONE,
trial_ends_at TIMESTAMP WITH TIME ZONE,
invited_by UUID REFERENCES profiles(id),
-- Timestamps
created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_profiles_email ON profiles(email) WHERE email IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_profiles_tier ON profiles(tier);
-- ── Sessions Table ──────────────────────────────────────────────
-- Auth token management
CREATE TABLE IF NOT EXISTS sessions (
token VARCHAR(64) PRIMARY KEY,
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_sessions_profile_id ON sessions(profile_id);
CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
-- ── AI Usage Tracking ───────────────────────────────────────────
-- Daily AI call limits per profile
CREATE TABLE IF NOT EXISTS ai_usage (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
date DATE NOT NULL,
call_count INTEGER DEFAULT 0,
UNIQUE(profile_id, date)
);
CREATE INDEX IF NOT EXISTS idx_ai_usage_profile_date ON ai_usage(profile_id, date);
-- ================================================================
-- TRACKING TABLES
-- ================================================================
-- ── Weight Log ──────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS weight_log (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
date DATE NOT NULL,
weight NUMERIC(5,2) NOT NULL,
note TEXT,
source VARCHAR(20) DEFAULT 'manual',
created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_weight_log_profile_date ON weight_log(profile_id, date DESC);
CREATE UNIQUE INDEX IF NOT EXISTS idx_weight_log_profile_date_unique ON weight_log(profile_id, date);
-- ── Circumference Log ───────────────────────────────────────────
CREATE TABLE IF NOT EXISTS circumference_log (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
date DATE NOT NULL,
c_neck NUMERIC(5,2),
c_chest NUMERIC(5,2),
c_waist NUMERIC(5,2),
c_belly NUMERIC(5,2),
c_hip NUMERIC(5,2),
c_thigh NUMERIC(5,2),
c_calf NUMERIC(5,2),
c_arm NUMERIC(5,2),
notes TEXT,
photo_id UUID,
created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_circumference_profile_date ON circumference_log(profile_id, date DESC);
-- ── Caliper Log ─────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS caliper_log (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
date DATE NOT NULL,
sf_method VARCHAR(20) DEFAULT 'jackson3',
sf_chest NUMERIC(5,2),
sf_axilla NUMERIC(5,2),
sf_triceps NUMERIC(5,2),
sf_subscap NUMERIC(5,2),
sf_suprailiac NUMERIC(5,2),
sf_abdomen NUMERIC(5,2),
sf_thigh NUMERIC(5,2),
sf_calf_med NUMERIC(5,2),
sf_lowerback NUMERIC(5,2),
sf_biceps NUMERIC(5,2),
body_fat_pct NUMERIC(4,2),
lean_mass NUMERIC(5,2),
fat_mass NUMERIC(5,2),
notes TEXT,
created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_caliper_profile_date ON caliper_log(profile_id, date DESC);
-- ── Nutrition Log ───────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS nutrition_log (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
date DATE NOT NULL,
kcal NUMERIC(7,2),
protein_g NUMERIC(6,2),
fat_g NUMERIC(6,2),
carbs_g NUMERIC(6,2),
source VARCHAR(20) DEFAULT 'csv',
created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_nutrition_profile_date ON nutrition_log(profile_id, date DESC);
-- ── Activity Log ────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS activity_log (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
date DATE NOT NULL,
start_time TIME,
end_time TIME,
activity_type VARCHAR(50) NOT NULL,
duration_min NUMERIC(6,2),
kcal_active NUMERIC(7,2),
kcal_resting NUMERIC(7,2),
hr_avg NUMERIC(5,2),
hr_max NUMERIC(5,2),
distance_km NUMERIC(7,2),
rpe INTEGER CHECK (rpe >= 1 AND rpe <= 10),
source VARCHAR(20) DEFAULT 'manual',
notes TEXT,
created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_activity_profile_date ON activity_log(profile_id, date DESC);
-- ── Photos ──────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS photos (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
date DATE,
path TEXT NOT NULL,
created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_photos_profile_date ON photos(profile_id, date DESC);
-- ================================================================
-- AI TABLES
-- ================================================================
-- ── AI Insights ─────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS ai_insights (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
profile_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
scope VARCHAR(50) NOT NULL,
content TEXT NOT NULL,
created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_ai_insights_profile_scope ON ai_insights(profile_id, scope, created DESC);
-- ── AI Prompts ──────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS ai_prompts (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(255) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
template TEXT NOT NULL,
active BOOLEAN DEFAULT TRUE,
sort_order INTEGER DEFAULT 0,
created TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_ai_prompts_slug ON ai_prompts(slug);
CREATE INDEX IF NOT EXISTS idx_ai_prompts_active_sort ON ai_prompts(active, sort_order);
-- ================================================================
-- TRIGGERS
-- ================================================================
-- Auto-update timestamp trigger for profiles
CREATE OR REPLACE FUNCTION update_updated_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER IF NOT EXISTS trigger_profiles_updated
BEFORE UPDATE ON profiles
FOR EACH ROW
EXECUTE FUNCTION update_updated_timestamp();
CREATE TRIGGER IF NOT EXISTS trigger_ai_prompts_updated
BEFORE UPDATE ON ai_prompts
FOR EACH ROW
EXECUTE FUNCTION update_updated_timestamp();
-- ================================================================
-- COMMENTS (Documentation)
-- ================================================================
COMMENT ON TABLE profiles IS 'User profiles with auth, permissions, and tier system';
COMMENT ON TABLE sessions IS 'Active auth tokens';
COMMENT ON TABLE ai_usage IS 'Daily AI call tracking per profile';
COMMENT ON TABLE weight_log IS 'Weight measurements';
COMMENT ON TABLE circumference_log IS 'Body circumference measurements (8 points)';
COMMENT ON TABLE caliper_log IS 'Skinfold measurements with body fat calculations';
COMMENT ON TABLE nutrition_log IS 'Daily nutrition intake (calories + macros)';
COMMENT ON TABLE activity_log IS 'Training sessions and activities';
COMMENT ON TABLE photos IS 'Progress photos';
COMMENT ON TABLE ai_insights IS 'AI-generated analysis results';
COMMENT ON TABLE ai_prompts IS 'Configurable AI prompt templates';
COMMENT ON COLUMN profiles.tier IS 'Subscription tier: free, basic, premium, selfhosted';
COMMENT ON COLUMN profiles.trial_ends_at IS 'Trial expiration timestamp (14 days from registration)';
COMMENT ON COLUMN profiles.tier_expires_at IS 'Paid tier expiration timestamp';
COMMENT ON COLUMN profiles.invited_by IS 'Profile ID of inviter (for beta invitations)';