- Add Dockerfile and .dockerignore- Fix initial DB creation- Fix seed permission

This commit is contained in:
Koncept Kit
2026-01-26 20:06:22 +07:00
parent b29bb641f5
commit ea87b3f6ee
7 changed files with 402 additions and 92 deletions

83
.dockerignore Normal file
View 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
View File

@@ -245,6 +245,9 @@ temp_uploads/
tmp/ tmp/
temporary/ temporary/
# Generated SQL files (from scripts)
create_superadmin.sql
# CSV imports # CSV imports
imports/*.csv imports/*.csv
!imports/.gitkeep !imports/.gitkeep

40
Dockerfile Normal file
View 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"]

View File

@@ -1,38 +1,15 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" """
Create Superadmin User Script 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 sys
import os import os
from getpass import getpass from getpass import getpass
def generate_password_hash(password: str) -> str: # Add the backend directory to path for imports
"""Generate bcrypt hash for password""" sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
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()
);
"""
def main(): def main():
print("=" * 70) print("=" * 70)
@@ -40,6 +17,15 @@ def main():
print("=" * 70) print("=" * 70)
print() 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 # Get user input
email = input("Email address: ").strip() email = input("Email address: ").strip()
if not email or '@' not in email: if not email or '@' not in email:
@@ -68,31 +54,89 @@ def main():
sys.exit(1) sys.exit(1)
print() print()
print("Generating password hash...") print("Creating superadmin user...")
password_hash = generate_password_hash(password)
print("✅ Password hash generated") try:
print() # Import database dependencies
print("=" * 70) from sqlalchemy import create_engine, text
print("SQL STATEMENT") from passlib.context import CryptContext
print("=" * 70)
sql = generate_sql(email, password_hash, first_name, last_name) # Create password hash
print(sql) pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
password_hash = pwd_context.hash(password)
# Save to file # Connect to database
output_file = "create_superadmin.sql" engine = create_engine(database_url)
with open(output_file, 'w') as f:
f.write(sql)
print("=" * 70) with engine.connect() as conn:
print(f"✅ SQL saved to: {output_file}") # Check if user already exists
print() result = conn.execute(
print("Run this command to create the user:") text("SELECT id FROM users WHERE email = :email"),
print(f" psql -U postgres -d loaf_new -f {output_file}") {"email": email}
print() )
print("Or copy the SQL above and run it directly in psql") if result.fetchone():
print("=" * 70) 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()
print("=" * 70)
print("✅ Superadmin user created successfully!")
print("=" * 70)
print()
print(f" Email: {email}")
print(f" Name: {first_name} {last_name}")
print(f" Role: superadmin")
print(f" Status: active")
print()
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__": if __name__ == "__main__":
try: try:
@@ -100,6 +144,3 @@ if __name__ == "__main__":
except KeyboardInterrupt: except KeyboardInterrupt:
print("\n\n❌ Cancelled by user") print("\n\n❌ Cancelled by user")
sys.exit(1) sys.exit(1)
except Exception as e:
print(f"\n❌ Error: {e}")
sys.exit(1)

View File

@@ -94,6 +94,30 @@ BEGIN;
-- SECTION 2: Create Core Tables -- 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 -- Users table
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
@@ -103,6 +127,7 @@ CREATE TABLE IF NOT EXISTS users (
password_hash VARCHAR NOT NULL, password_hash VARCHAR NOT NULL,
email_verified BOOLEAN NOT NULL DEFAULT FALSE, email_verified BOOLEAN NOT NULL DEFAULT FALSE,
email_verification_token VARCHAR UNIQUE, email_verification_token VARCHAR UNIQUE,
email_verification_expires TIMESTAMP WITH TIME ZONE,
-- Personal Information -- Personal Information
first_name VARCHAR NOT NULL, first_name VARCHAR NOT NULL,
@@ -113,7 +138,6 @@ CREATE TABLE IF NOT EXISTS users (
state VARCHAR(2), state VARCHAR(2),
zipcode VARCHAR(10), zipcode VARCHAR(10),
date_of_birth DATE, date_of_birth DATE,
bio TEXT,
-- Profile -- Profile
profile_photo_url VARCHAR, profile_photo_url VARCHAR,
@@ -137,20 +161,44 @@ CREATE TABLE IF NOT EXISTS users (
-- Status & Role -- Status & Role
status userstatus NOT NULL DEFAULT 'pending_email', status userstatus NOT NULL DEFAULT 'pending_email',
role userrole NOT NULL DEFAULT 'guest', role userrole NOT NULL DEFAULT 'guest',
role_id UUID, -- For dynamic RBAC (added in later migration) role_id UUID, -- For dynamic RBAC
-- Rejection Tracking -- Newsletter Preferences
rejection_reason TEXT, newsletter_subscribed BOOLEAN DEFAULT TRUE,
rejected_at TIMESTAMP WITH TIME ZONE, newsletter_publish_name BOOLEAN DEFAULT FALSE NOT NULL,
rejected_by UUID REFERENCES users(id), 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 -- Membership
member_since DATE, 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, email_verification_reminders_sent INTEGER DEFAULT 0 NOT NULL,
last_email_verification_reminder_at TIMESTAMP WITH TIME ZONE, last_email_verification_reminder_at TIMESTAMP WITH TIME ZONE,
event_attendance_reminders_sent INTEGER DEFAULT 0 NOT NULL, 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, renewal_reminders_sent INTEGER DEFAULT 0 NOT NULL,
last_renewal_reminder_at TIMESTAMP WITH TIME ZONE, 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 -- WordPress Import Tracking
import_source VARCHAR(50), import_source VARCHAR(50),
import_job_id UUID REFERENCES import_jobs(id), import_job_id UUID REFERENCES import_jobs(id),
wordpress_user_id BIGINT, wordpress_user_id BIGINT,
wordpress_registered_date TIMESTAMP WITH TIME ZONE, 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 -- Timestamps
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_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, name VARCHAR NOT NULL,
description TEXT, description TEXT,
price_cents INTEGER NOT NULL, 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 -- Configuration
active BOOLEAN NOT NULL DEFAULT TRUE, 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 -- Timestamps
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
@@ -281,13 +350,21 @@ CREATE TABLE IF NOT EXISTS subscriptions (
status subscriptionstatus DEFAULT 'active', status subscriptionstatus DEFAULT 'active',
start_date TIMESTAMP WITH TIME ZONE NOT NULL, start_date TIMESTAMP WITH TIME ZONE NOT NULL,
end_date TIMESTAMP WITH TIME ZONE, end_date TIMESTAMP WITH TIME ZONE,
next_billing_date TIMESTAMP WITH TIME ZONE,
-- Payment Details -- Payment Details
amount_paid_cents INTEGER, amount_paid_cents INTEGER,
base_subscription_cents INTEGER NOT NULL, base_subscription_cents INTEGER NOT NULL,
donation_cents INTEGER DEFAULT 0 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 Support
manual_payment BOOLEAN DEFAULT FALSE NOT NULL, manual_payment BOOLEAN DEFAULT FALSE NOT NULL,
manual_payment_notes TEXT, manual_payment_notes TEXT,
@@ -319,6 +396,14 @@ CREATE TABLE IF NOT EXISTS donations (
stripe_payment_intent_id VARCHAR, stripe_payment_intent_id VARCHAR,
payment_method 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 -- Metadata
notes TEXT, notes TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, 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 created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
); );
-- Import Jobs table -- Add FK constraints to import_jobs (now that users table exists)
CREATE TABLE IF NOT EXISTS import_jobs ( ALTER TABLE import_jobs
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), 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);
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
);
-- Import Rollback Audit table (for tracking rollback operations) -- Import Rollback Audit table (for tracking rollback operations)
CREATE TABLE IF NOT EXISTS import_rollback_audit ( 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_plan_id ON subscriptions(plan_id);
CREATE INDEX IF NOT EXISTS idx_subscriptions_status ON subscriptions(status); 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_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 -- Donations indexes
CREATE INDEX IF NOT EXISTS idx_donation_user ON donations(user_id); 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_type ON donations(donation_type);
CREATE INDEX IF NOT EXISTS idx_donation_status ON donations(status); 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_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 -- Import Jobs indexes
CREATE INDEX IF NOT EXISTS idx_import_jobs_status ON import_jobs(status); CREATE INDEX IF NOT EXISTS idx_import_jobs_status ON import_jobs(status);

View File

@@ -118,6 +118,40 @@ PERMISSIONS = [
{"code": "permissions.audit", "name": "View Permission Audit Log", "description": "View permission change audit logs", "module": "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 permission assignments for dynamic roles
DEFAULT_ROLE_PERMISSIONS = { DEFAULT_ROLE_PERMISSIONS = {
"guest": [], # Guests have no permissions "guest": [], # Guests have no permissions
@@ -196,7 +230,34 @@ def seed_permissions():
print(f"\n⚠️ WARNING: Tables not fully cleared! Stopping.") print(f"\n⚠️ WARNING: Tables not fully cleared! Stopping.")
return 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...") print(f"\n📝 Creating {len(PERMISSIONS)} permissions...")
permission_map = {} # Map code to permission object permission_map = {} # Map code to permission object
@@ -213,13 +274,13 @@ def seed_permissions():
db.commit() db.commit()
print(f"✓ Created {len(PERMISSIONS)} permissions") print(f"✓ Created {len(PERMISSIONS)} permissions")
# Step 3: Get all roles from database # Step 4: Verify roles exist
print("\n🔍 Fetching dynamic roles...") print("\n🔍 Verifying dynamic roles...")
roles = db.query(Role).all() roles = db.query(Role).all()
role_map = {role.code: role for role in roles} role_map = {role.code: role for role in roles}
print(f"✓ Found {len(roles)} roles: {', '.join(role_map.keys())}") 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...") print("\n🔐 Assigning permissions to roles...")
from models import UserRole # Import for enum mapping from models import UserRole # Import for enum mapping
@@ -258,7 +319,7 @@ def seed_permissions():
db.commit() db.commit()
print(f"{role.name}: Assigned {len(permission_codes)} permissions") print(f"{role.name}: Assigned {len(permission_codes)} permissions")
# Step 5: Summary # Step 6: Summary
print("\n" + "=" * 80) print("\n" + "=" * 80)
print("📊 SEEDING SUMMARY") print("📊 SEEDING SUMMARY")
print("=" * 80) print("=" * 80)
@@ -273,7 +334,8 @@ def seed_permissions():
for module, count in sorted(modules.items()): for module, count in sorted(modules.items()):
print(f"{module.capitalize()}: {count} permissions") 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(f"Total role-permission mappings: {total_assigned}")
print("\n✅ Permission seeding completed successfully!") print("\n✅ Permission seeding completed successfully!")
print("\nNext step: Restart backend server") print("\nNext step: Restart backend server")

View File

@@ -97,6 +97,15 @@ logging.basicConfig(
) )
logger = logging.getLogger(__name__) 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 # Helper Functions
# ============================================================ # ============================================================