- Add Dockerfile and .dockerignore- Fix initial DB creation- Fix seed permission
This commit is contained in:
83
.dockerignore
Normal file
83
.dockerignore
Normal file
@@ -0,0 +1,83 @@
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Python
|
||||
__pycache__
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Virtual environments
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
.venv/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Testing
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
|
||||
# Environment files (will be mounted or passed via env vars)
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
||||
*.env
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
|
||||
# Database
|
||||
*.db
|
||||
*.sqlite3
|
||||
|
||||
# Alembic
|
||||
alembic/versions/__pycache__/
|
||||
|
||||
# Docker
|
||||
Dockerfile
|
||||
docker-compose*.yml
|
||||
.docker/
|
||||
|
||||
# Documentation
|
||||
*.md
|
||||
docs/
|
||||
|
||||
# Temporary files
|
||||
tmp/
|
||||
temp/
|
||||
*.tmp
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Uploads (will be mounted as volume)
|
||||
uploads/
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -245,6 +245,9 @@ temp_uploads/
|
||||
tmp/
|
||||
temporary/
|
||||
|
||||
# Generated SQL files (from scripts)
|
||||
create_superadmin.sql
|
||||
|
||||
# CSV imports
|
||||
imports/*.csv
|
||||
!imports/.gitkeep
|
||||
|
||||
40
Dockerfile
Normal file
40
Dockerfile
Normal file
@@ -0,0 +1,40 @@
|
||||
# Backend Dockerfile - FastAPI with Python
|
||||
FROM python:3.11-slim
|
||||
|
||||
# Set environment variables
|
||||
ENV PYTHONDONTWRITEBYTECODE=1
|
||||
ENV PYTHONUNBUFFERED=1
|
||||
ENV PYTHONPATH=/app
|
||||
|
||||
# Set work directory
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
libpq-dev \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Python dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir --upgrade pip && \
|
||||
pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application code
|
||||
COPY . .
|
||||
|
||||
# Create non-root user for security
|
||||
RUN adduser --disabled-password --gecos '' appuser && \
|
||||
chown -R appuser:appuser /app
|
||||
USER appuser
|
||||
|
||||
# Expose port
|
||||
EXPOSE 8000
|
||||
|
||||
# Health check
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:8000/health || exit 1
|
||||
|
||||
# Run the application
|
||||
CMD ["uvicorn", "server:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
@@ -1,38 +1,15 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Create Superadmin User Script
|
||||
Generates a superadmin user with hashed password for LOAF membership platform
|
||||
Directly creates a superadmin user in the database for LOAF membership platform
|
||||
"""
|
||||
|
||||
import bcrypt
|
||||
import sys
|
||||
import os
|
||||
from getpass import getpass
|
||||
|
||||
def generate_password_hash(password: str) -> str:
|
||||
"""Generate bcrypt hash for password"""
|
||||
return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
|
||||
|
||||
def generate_sql(email: str, password_hash: str, first_name: str, last_name: str) -> str:
|
||||
"""Generate SQL INSERT statement"""
|
||||
return f"""
|
||||
-- Create Superadmin User
|
||||
INSERT INTO users (
|
||||
id, email, password_hash, first_name, last_name,
|
||||
status, role, email_verified, created_at, updated_at
|
||||
) VALUES (
|
||||
gen_random_uuid(),
|
||||
'{email}',
|
||||
'{password_hash}',
|
||||
'{first_name}',
|
||||
'{last_name}',
|
||||
'active',
|
||||
'superadmin',
|
||||
true,
|
||||
NOW(),
|
||||
NOW()
|
||||
);
|
||||
"""
|
||||
# Add the backend directory to path for imports
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
@@ -40,6 +17,15 @@ def main():
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
# Check for DATABASE_URL
|
||||
from dotenv import load_dotenv
|
||||
load_dotenv()
|
||||
|
||||
database_url = os.getenv("DATABASE_URL")
|
||||
if not database_url:
|
||||
print("❌ DATABASE_URL not found in environment or .env file")
|
||||
sys.exit(1)
|
||||
|
||||
# Get user input
|
||||
email = input("Email address: ").strip()
|
||||
if not email or '@' not in email:
|
||||
@@ -68,38 +54,93 @@ def main():
|
||||
sys.exit(1)
|
||||
|
||||
print()
|
||||
print("Generating password hash...")
|
||||
password_hash = generate_password_hash(password)
|
||||
print("Creating superadmin user...")
|
||||
|
||||
try:
|
||||
# Import database dependencies
|
||||
from sqlalchemy import create_engine, text
|
||||
from passlib.context import CryptContext
|
||||
|
||||
# Create password hash
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
password_hash = pwd_context.hash(password)
|
||||
|
||||
# Connect to database
|
||||
engine = create_engine(database_url)
|
||||
|
||||
with engine.connect() as conn:
|
||||
# Check if user already exists
|
||||
result = conn.execute(
|
||||
text("SELECT id FROM users WHERE email = :email"),
|
||||
{"email": email}
|
||||
)
|
||||
if result.fetchone():
|
||||
print(f"❌ User with email '{email}' already exists")
|
||||
sys.exit(1)
|
||||
|
||||
# Insert superadmin user
|
||||
conn.execute(
|
||||
text("""
|
||||
INSERT INTO users (
|
||||
id, email, password_hash, first_name, last_name,
|
||||
phone, address, city, state, zipcode, date_of_birth,
|
||||
status, role, email_verified,
|
||||
newsletter_subscribed, accepts_tos,
|
||||
created_at, updated_at
|
||||
) VALUES (
|
||||
gen_random_uuid(),
|
||||
:email,
|
||||
:password_hash,
|
||||
:first_name,
|
||||
:last_name,
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'',
|
||||
'1990-01-01',
|
||||
'active',
|
||||
'superadmin',
|
||||
true,
|
||||
false,
|
||||
true,
|
||||
NOW(),
|
||||
NOW()
|
||||
)
|
||||
"""),
|
||||
{
|
||||
"email": email,
|
||||
"password_hash": password_hash,
|
||||
"first_name": first_name,
|
||||
"last_name": last_name
|
||||
}
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
print("✅ Password hash generated")
|
||||
print()
|
||||
print("=" * 70)
|
||||
print("SQL STATEMENT")
|
||||
print("✅ Superadmin user created successfully!")
|
||||
print("=" * 70)
|
||||
|
||||
sql = generate_sql(email, password_hash, first_name, last_name)
|
||||
print(sql)
|
||||
|
||||
# Save to file
|
||||
output_file = "create_superadmin.sql"
|
||||
with open(output_file, 'w') as f:
|
||||
f.write(sql)
|
||||
|
||||
print("=" * 70)
|
||||
print(f"✅ SQL saved to: {output_file}")
|
||||
print()
|
||||
print("Run this command to create the user:")
|
||||
print(f" psql -U postgres -d loaf_new -f {output_file}")
|
||||
print(f" Email: {email}")
|
||||
print(f" Name: {first_name} {last_name}")
|
||||
print(f" Role: superadmin")
|
||||
print(f" Status: active")
|
||||
print()
|
||||
print("Or copy the SQL above and run it directly in psql")
|
||||
print("You can now log in with these credentials.")
|
||||
print("=" * 70)
|
||||
|
||||
except ImportError as e:
|
||||
print(f"❌ Missing dependency: {e}")
|
||||
print(" Run: pip install sqlalchemy psycopg2-binary passlib python-dotenv")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"❌ Database error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n❌ Cancelled by user")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"\n❌ Error: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
@@ -94,6 +94,30 @@ BEGIN;
|
||||
-- SECTION 2: Create Core Tables
|
||||
-- ============================================================================
|
||||
|
||||
-- Import Jobs table (must be created before users due to FK reference)
|
||||
CREATE TABLE IF NOT EXISTS import_jobs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
filename VARCHAR NOT NULL,
|
||||
status importjobstatus NOT NULL DEFAULT 'processing',
|
||||
total_rows INTEGER DEFAULT 0,
|
||||
processed_rows INTEGER DEFAULT 0,
|
||||
success_count INTEGER DEFAULT 0,
|
||||
error_count INTEGER DEFAULT 0,
|
||||
error_log JSONB DEFAULT '[]'::jsonb,
|
||||
|
||||
-- WordPress import enhancements
|
||||
field_mapping JSONB DEFAULT '{}'::jsonb,
|
||||
wordpress_metadata JSONB DEFAULT '{}'::jsonb,
|
||||
imported_user_ids JSONB DEFAULT '[]'::jsonb,
|
||||
rollback_at TIMESTAMP WITH TIME ZONE,
|
||||
rollback_by UUID, -- Will be updated with FK after users table exists
|
||||
|
||||
started_by UUID, -- Will be updated with FK after users table exists
|
||||
started_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
completed_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
-- Users table
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
@@ -103,6 +127,7 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
password_hash VARCHAR NOT NULL,
|
||||
email_verified BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
email_verification_token VARCHAR UNIQUE,
|
||||
email_verification_expires TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Personal Information
|
||||
first_name VARCHAR NOT NULL,
|
||||
@@ -113,7 +138,6 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
state VARCHAR(2),
|
||||
zipcode VARCHAR(10),
|
||||
date_of_birth DATE,
|
||||
bio TEXT,
|
||||
|
||||
-- Profile
|
||||
profile_photo_url VARCHAR,
|
||||
@@ -137,20 +161,44 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
-- Status & Role
|
||||
status userstatus NOT NULL DEFAULT 'pending_email',
|
||||
role userrole NOT NULL DEFAULT 'guest',
|
||||
role_id UUID, -- For dynamic RBAC (added in later migration)
|
||||
role_id UUID, -- For dynamic RBAC
|
||||
|
||||
-- Rejection Tracking
|
||||
rejection_reason TEXT,
|
||||
rejected_at TIMESTAMP WITH TIME ZONE,
|
||||
rejected_by UUID REFERENCES users(id),
|
||||
-- Newsletter Preferences
|
||||
newsletter_subscribed BOOLEAN DEFAULT TRUE,
|
||||
newsletter_publish_name BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
newsletter_publish_photo BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
newsletter_publish_birthday BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
newsletter_publish_none BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
|
||||
-- Volunteer Interests
|
||||
volunteer_interests JSONB DEFAULT '[]'::jsonb,
|
||||
|
||||
-- Scholarship Request
|
||||
scholarship_requested BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
scholarship_reason TEXT,
|
||||
|
||||
-- Directory Settings
|
||||
show_in_directory BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
directory_email VARCHAR,
|
||||
directory_bio TEXT,
|
||||
directory_address VARCHAR,
|
||||
directory_phone VARCHAR,
|
||||
directory_dob DATE,
|
||||
directory_partner_name VARCHAR,
|
||||
|
||||
-- Password Reset
|
||||
password_reset_token VARCHAR,
|
||||
password_reset_expires TIMESTAMP WITH TIME ZONE,
|
||||
force_password_change BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
|
||||
-- Terms of Service
|
||||
accepts_tos BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
tos_accepted_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Membership
|
||||
member_since DATE,
|
||||
accepts_tos BOOLEAN DEFAULT FALSE,
|
||||
tos_accepted_at TIMESTAMP WITH TIME ZONE,
|
||||
newsletter_subscribed BOOLEAN DEFAULT TRUE,
|
||||
|
||||
-- Reminder Tracking (from migration 004)
|
||||
-- Reminder Tracking
|
||||
email_verification_reminders_sent INTEGER DEFAULT 0 NOT NULL,
|
||||
last_email_verification_reminder_at TIMESTAMP WITH TIME ZONE,
|
||||
event_attendance_reminders_sent INTEGER DEFAULT 0 NOT NULL,
|
||||
@@ -160,12 +208,21 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
renewal_reminders_sent INTEGER DEFAULT 0 NOT NULL,
|
||||
last_renewal_reminder_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Rejection Tracking
|
||||
rejection_reason TEXT,
|
||||
rejected_at TIMESTAMP WITH TIME ZONE,
|
||||
rejected_by UUID REFERENCES users(id),
|
||||
|
||||
-- WordPress Import Tracking
|
||||
import_source VARCHAR(50),
|
||||
import_job_id UUID REFERENCES import_jobs(id),
|
||||
wordpress_user_id BIGINT,
|
||||
wordpress_registered_date TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Role Change Audit Trail
|
||||
role_changed_at TIMESTAMP WITH TIME ZONE,
|
||||
role_changed_by UUID REFERENCES users(id) ON DELETE SET NULL,
|
||||
|
||||
-- Timestamps
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
@@ -255,11 +312,23 @@ CREATE TABLE IF NOT EXISTS subscription_plans (
|
||||
name VARCHAR NOT NULL,
|
||||
description TEXT,
|
||||
price_cents INTEGER NOT NULL,
|
||||
billing_cycle VARCHAR NOT NULL DEFAULT 'annual',
|
||||
billing_cycle VARCHAR NOT NULL DEFAULT 'yearly',
|
||||
stripe_price_id VARCHAR, -- Legacy, deprecated
|
||||
|
||||
-- Configuration
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
features JSONB DEFAULT '[]'::jsonb,
|
||||
|
||||
-- Custom billing cycle fields (for recurring date ranges like Jan 1 - Dec 31)
|
||||
custom_cycle_enabled BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
custom_cycle_start_month INTEGER,
|
||||
custom_cycle_start_day INTEGER,
|
||||
custom_cycle_end_month INTEGER,
|
||||
custom_cycle_end_day INTEGER,
|
||||
|
||||
-- Dynamic pricing fields
|
||||
minimum_price_cents INTEGER DEFAULT 3000 NOT NULL,
|
||||
suggested_price_cents INTEGER,
|
||||
allow_donation BOOLEAN DEFAULT TRUE NOT NULL,
|
||||
|
||||
-- Timestamps
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
@@ -281,13 +350,21 @@ CREATE TABLE IF NOT EXISTS subscriptions (
|
||||
status subscriptionstatus DEFAULT 'active',
|
||||
start_date TIMESTAMP WITH TIME ZONE NOT NULL,
|
||||
end_date TIMESTAMP WITH TIME ZONE,
|
||||
next_billing_date TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- Payment Details
|
||||
amount_paid_cents INTEGER,
|
||||
base_subscription_cents INTEGER NOT NULL,
|
||||
donation_cents INTEGER DEFAULT 0 NOT NULL,
|
||||
|
||||
-- Stripe transaction metadata (for validation and audit)
|
||||
stripe_payment_intent_id VARCHAR,
|
||||
stripe_charge_id VARCHAR,
|
||||
stripe_invoice_id VARCHAR,
|
||||
payment_completed_at TIMESTAMP WITH TIME ZONE,
|
||||
card_last4 VARCHAR(4),
|
||||
card_brand VARCHAR(20),
|
||||
stripe_receipt_url VARCHAR,
|
||||
|
||||
-- Manual Payment Support
|
||||
manual_payment BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
manual_payment_notes TEXT,
|
||||
@@ -319,6 +396,14 @@ CREATE TABLE IF NOT EXISTS donations (
|
||||
stripe_payment_intent_id VARCHAR,
|
||||
payment_method VARCHAR,
|
||||
|
||||
-- Stripe transaction metadata (for validation and audit)
|
||||
stripe_charge_id VARCHAR,
|
||||
stripe_customer_id VARCHAR,
|
||||
payment_completed_at TIMESTAMP WITH TIME ZONE,
|
||||
card_last4 VARCHAR(4),
|
||||
card_brand VARCHAR(20),
|
||||
stripe_receipt_url VARCHAR,
|
||||
|
||||
-- Metadata
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
@@ -466,29 +551,10 @@ CREATE TABLE IF NOT EXISTS user_invitations (
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Import Jobs table
|
||||
CREATE TABLE IF NOT EXISTS import_jobs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
filename VARCHAR NOT NULL,
|
||||
status importjobstatus NOT NULL DEFAULT 'processing',
|
||||
total_rows INTEGER DEFAULT 0,
|
||||
processed_rows INTEGER DEFAULT 0,
|
||||
success_count INTEGER DEFAULT 0,
|
||||
error_count INTEGER DEFAULT 0,
|
||||
error_log JSONB DEFAULT '[]'::jsonb,
|
||||
|
||||
-- WordPress import enhancements
|
||||
field_mapping JSONB DEFAULT '{}'::jsonb,
|
||||
wordpress_metadata JSONB DEFAULT '{}'::jsonb,
|
||||
imported_user_ids JSONB DEFAULT '[]'::jsonb,
|
||||
rollback_at TIMESTAMP WITH TIME ZONE,
|
||||
rollback_by UUID REFERENCES users(id),
|
||||
|
||||
started_by UUID REFERENCES users(id),
|
||||
started_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
completed_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
-- Add FK constraints to import_jobs (now that users table exists)
|
||||
ALTER TABLE import_jobs
|
||||
ADD CONSTRAINT fk_import_jobs_rollback_by FOREIGN KEY (rollback_by) REFERENCES users(id),
|
||||
ADD CONSTRAINT fk_import_jobs_started_by FOREIGN KEY (started_by) REFERENCES users(id);
|
||||
|
||||
-- Import Rollback Audit table (for tracking rollback operations)
|
||||
CREATE TABLE IF NOT EXISTS import_rollback_audit (
|
||||
@@ -542,12 +608,18 @@ CREATE INDEX IF NOT EXISTS idx_subscriptions_user_id ON subscriptions(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_subscriptions_plan_id ON subscriptions(plan_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_subscriptions_status ON subscriptions(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_subscriptions_stripe_subscription_id ON subscriptions(stripe_subscription_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_subscriptions_payment_intent ON subscriptions(stripe_payment_intent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_subscriptions_charge_id ON subscriptions(stripe_charge_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_subscriptions_invoice_id ON subscriptions(stripe_invoice_id);
|
||||
|
||||
-- Donations indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_donation_user ON donations(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_donation_type ON donations(donation_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_donation_status ON donations(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_donation_created ON donations(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_donation_payment_intent ON donations(stripe_payment_intent_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_donation_charge_id ON donations(stripe_charge_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_donation_customer_id ON donations(stripe_customer_id);
|
||||
|
||||
-- Import Jobs indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_import_jobs_status ON import_jobs(status);
|
||||
|
||||
@@ -118,6 +118,40 @@ PERMISSIONS = [
|
||||
{"code": "permissions.audit", "name": "View Permission Audit Log", "description": "View permission change audit logs", "module": "permissions"},
|
||||
]
|
||||
|
||||
# Default system roles that must exist
|
||||
DEFAULT_ROLES = [
|
||||
{
|
||||
"code": "guest",
|
||||
"name": "Guest",
|
||||
"description": "Default role for new registrations with no special permissions",
|
||||
"is_system_role": True
|
||||
},
|
||||
{
|
||||
"code": "member",
|
||||
"name": "Member",
|
||||
"description": "Active paying members with access to member-only content",
|
||||
"is_system_role": True
|
||||
},
|
||||
{
|
||||
"code": "finance",
|
||||
"name": "Finance",
|
||||
"description": "Financial management role with access to payments, subscriptions, and reports",
|
||||
"is_system_role": True
|
||||
},
|
||||
{
|
||||
"code": "admin",
|
||||
"name": "Admin",
|
||||
"description": "Board members with full management access except RBAC",
|
||||
"is_system_role": True
|
||||
},
|
||||
{
|
||||
"code": "superadmin",
|
||||
"name": "Superadmin",
|
||||
"description": "Full system access including RBAC management",
|
||||
"is_system_role": True
|
||||
},
|
||||
]
|
||||
|
||||
# Default permission assignments for dynamic roles
|
||||
DEFAULT_ROLE_PERMISSIONS = {
|
||||
"guest": [], # Guests have no permissions
|
||||
@@ -196,7 +230,34 @@ def seed_permissions():
|
||||
print(f"\n⚠️ WARNING: Tables not fully cleared! Stopping.")
|
||||
return
|
||||
|
||||
# Step 2: Create permissions
|
||||
# Step 2: Create default system roles
|
||||
print(f"\n👤 Creating {len(DEFAULT_ROLES)} system roles...")
|
||||
role_map = {}
|
||||
|
||||
for role_data in DEFAULT_ROLES:
|
||||
# Check if role already exists
|
||||
existing_role = db.query(Role).filter(Role.code == role_data["code"]).first()
|
||||
if existing_role:
|
||||
print(f" • {role_data['name']}: Already exists, updating...")
|
||||
existing_role.name = role_data["name"]
|
||||
existing_role.description = role_data["description"]
|
||||
existing_role.is_system_role = role_data["is_system_role"]
|
||||
role_map[role_data["code"]] = existing_role
|
||||
else:
|
||||
print(f" • {role_data['name']}: Creating...")
|
||||
role = Role(
|
||||
code=role_data["code"],
|
||||
name=role_data["name"],
|
||||
description=role_data["description"],
|
||||
is_system_role=role_data["is_system_role"]
|
||||
)
|
||||
db.add(role)
|
||||
role_map[role_data["code"]] = role
|
||||
|
||||
db.commit()
|
||||
print(f"✓ Created/updated {len(DEFAULT_ROLES)} system roles")
|
||||
|
||||
# Step 3: Create permissions
|
||||
print(f"\n📝 Creating {len(PERMISSIONS)} permissions...")
|
||||
permission_map = {} # Map code to permission object
|
||||
|
||||
@@ -213,13 +274,13 @@ def seed_permissions():
|
||||
db.commit()
|
||||
print(f"✓ Created {len(PERMISSIONS)} permissions")
|
||||
|
||||
# Step 3: Get all roles from database
|
||||
print("\n🔍 Fetching dynamic roles...")
|
||||
# Step 4: Verify roles exist
|
||||
print("\n🔍 Verifying dynamic roles...")
|
||||
roles = db.query(Role).all()
|
||||
role_map = {role.code: role for role in roles}
|
||||
print(f"✓ Found {len(roles)} roles: {', '.join(role_map.keys())}")
|
||||
|
||||
# Step 4: Assign permissions to roles
|
||||
# Step 5: Assign permissions to roles
|
||||
print("\n🔐 Assigning permissions to roles...")
|
||||
|
||||
from models import UserRole # Import for enum mapping
|
||||
@@ -258,7 +319,7 @@ def seed_permissions():
|
||||
db.commit()
|
||||
print(f" ✓ {role.name}: Assigned {len(permission_codes)} permissions")
|
||||
|
||||
# Step 5: Summary
|
||||
# Step 6: Summary
|
||||
print("\n" + "=" * 80)
|
||||
print("📊 SEEDING SUMMARY")
|
||||
print("=" * 80)
|
||||
@@ -273,7 +334,8 @@ def seed_permissions():
|
||||
for module, count in sorted(modules.items()):
|
||||
print(f" • {module.capitalize()}: {count} permissions")
|
||||
|
||||
print(f"\nTotal permissions created: {len(PERMISSIONS)}")
|
||||
print(f"\nTotal system roles created: {len(DEFAULT_ROLES)}")
|
||||
print(f"Total permissions created: {len(PERMISSIONS)}")
|
||||
print(f"Total role-permission mappings: {total_assigned}")
|
||||
print("\n✅ Permission seeding completed successfully!")
|
||||
print("\nNext step: Restart backend server")
|
||||
|
||||
@@ -97,6 +97,15 @@ logging.basicConfig(
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ============================================================
|
||||
# Health Check Endpoint (for Kubernetes probes)
|
||||
# ============================================================
|
||||
|
||||
@app.get("/health")
|
||||
async def health_check():
|
||||
"""Health check endpoint for Kubernetes liveness/readiness probes."""
|
||||
return {"status": "healthy", "service": "membership-backend"}
|
||||
|
||||
# ============================================================
|
||||
# Helper Functions
|
||||
# ============================================================
|
||||
|
||||
Reference in New Issue
Block a user