Merge to LOAF-PROD for Demo #27

Merged
andika merged 10 commits from dev into loaf-prod 2026-02-02 11:11:36 +00:00
3 changed files with 216 additions and 0 deletions
Showing only changes of commit dd41cf773b - Show all commits

Binary file not shown.

View File

@@ -127,6 +127,10 @@ PERMISSIONS = [
# ========== REGISTRATION MODULE (2) ==========
{"code": "registration.view", "name": "View Registration Settings", "description": "View registration form schema and settings", "module": "registration"},
{"code": "registration.manage", "name": "Manage Registration Form", "description": "Edit registration form schema, steps, and fields", "module": "registration"},
# ========== DIRECTORY MODULE (2) ==========
{"code": "directory.view", "name": "View Directory Settings", "description": "View member directory field configuration", "module": "directory"},
{"code": "directory.manage", "name": "Manage Directory Fields", "description": "Enable/disable directory fields shown in Profile and Directory pages", "module": "directory"},
]
# Default system roles that must exist
@@ -210,6 +214,8 @@ DEFAULT_ROLE_PERMISSIONS = {
"payment_methods.delete", "payment_methods.set_default",
# Registration form management
"registration.view", "registration.manage",
# Directory configuration
"directory.view", "directory.manage",
],
"superadmin": [

210
server.py
View File

@@ -15,6 +15,7 @@ import uuid
import secrets
import csv
import io
import json
from database import engine, get_db, Base
from models import User, Event, EventRSVP, UserStatus, UserRole, RSVPStatus, SubscriptionPlan, Subscription, SubscriptionStatus, StorageUsage, EventGallery, NewsletterArchive, FinancialReport, BylawsDocument, Permission, RolePermission, Role, UserInvitation, InvitationStatus, ImportJob, ImportJobStatus, ImportRollbackAudit, Donation, DonationType, DonationStatus, SystemSettings, PaymentMethod, PaymentMethodType
@@ -2727,6 +2728,215 @@ async def get_field_types(
return FIELD_TYPES
# ============================================================================
# Directory Configuration Endpoints
# ============================================================================
# Default directory configuration - defines which fields are shown in Profile and Directory
DEFAULT_DIRECTORY_CONFIG = {
"version": "1.0",
"fields": {
"show_in_directory": {
"enabled": True,
"label": "Show in Directory",
"description": "Allow members to opt-in to the member directory",
"required": False,
"editable": True # Whether this field can be disabled by admin
},
"directory_email": {
"enabled": True,
"label": "Directory Email",
"description": "Email address visible to other members",
"required": False,
"editable": True
},
"directory_bio": {
"enabled": True,
"label": "Bio",
"description": "Short biography for the directory",
"required": False,
"editable": True
},
"directory_address": {
"enabled": True,
"label": "Address",
"description": "Address visible to other members",
"required": False,
"editable": True
},
"directory_phone": {
"enabled": True,
"label": "Phone",
"description": "Phone number visible to other members",
"required": False,
"editable": True
},
"directory_dob": {
"enabled": True,
"label": "Birthday",
"description": "Birthday visible to other members",
"required": False,
"editable": True
},
"directory_partner_name": {
"enabled": True,
"label": "Partner Name",
"description": "Partner name visible to other members",
"required": False,
"editable": True
},
"volunteer_interests": {
"enabled": True,
"label": "Volunteer Interests",
"description": "Volunteer interests shown in directory profile",
"required": False,
"editable": True
},
"social_media": {
"enabled": True,
"label": "Social Media Links",
"description": "Social media links (Facebook, Instagram, Twitter, LinkedIn)",
"required": False,
"editable": True
}
}
}
def get_directory_config(db: Session) -> dict:
"""Get directory configuration from database or return default"""
setting = db.query(SystemSettings).filter(
SystemSettings.setting_key == "directory.config"
).first()
if setting and setting.setting_value:
try:
return json.loads(setting.setting_value)
except json.JSONDecodeError:
return DEFAULT_DIRECTORY_CONFIG
return DEFAULT_DIRECTORY_CONFIG
def save_directory_config(db: Session, config: dict, user_id) -> dict:
"""Save directory configuration to database"""
setting = db.query(SystemSettings).filter(
SystemSettings.setting_key == "directory.config"
).first()
config_json = json.dumps(config)
if setting:
setting.setting_value = config_json
setting.updated_by = user_id
setting.updated_at = datetime.now(timezone.utc)
else:
from models import SettingType
setting = SystemSettings(
setting_key="directory.config",
setting_value=config_json,
setting_type=SettingType.json,
description="Member directory field configuration",
updated_by=user_id
)
db.add(setting)
db.commit()
db.refresh(setting)
return json.loads(setting.setting_value)
# Public endpoint - get directory field configuration
@api_router.get("/directory/config")
async def get_public_directory_config(db: Session = Depends(get_db)):
"""Get directory field configuration (public endpoint for Profile page)"""
config = get_directory_config(db)
# Return only the fields and their enabled status for frontend
return {
"fields": {
field_id: {
"enabled": field_data.get("enabled", True),
"label": field_data.get("label", field_id),
"required": field_data.get("required", False)
}
for field_id, field_data in config.get("fields", {}).items()
}
}
# Admin endpoint - get full directory configuration with metadata
@api_router.get("/admin/directory/config")
async def get_admin_directory_config(
current_user: User = Depends(require_permission("directory.view")),
db: Session = Depends(get_db)
):
"""Get full directory configuration for admin"""
config = get_directory_config(db)
# Get metadata
setting = db.query(SystemSettings).filter(
SystemSettings.setting_key == "directory.config"
).first()
return {
"config": config,
"metadata": {
"updated_at": setting.updated_at.isoformat() if setting and setting.updated_at else None,
"updated_by": setting.updated_by if setting else None,
"is_default": setting is None
}
}
# Admin endpoint - update directory configuration
@api_router.put("/admin/directory/config")
async def update_directory_config(
request: Request,
current_user: User = Depends(require_permission("directory.manage")),
db: Session = Depends(get_db)
):
"""Update directory field configuration"""
try:
body = await request.json()
config = body.get("config", {})
# Validate config structure
if "fields" not in config:
raise HTTPException(status_code=400, detail="Config must contain 'fields' object")
# Ensure show_in_directory is always enabled (core functionality)
if "show_in_directory" in config["fields"]:
config["fields"]["show_in_directory"]["enabled"] = True
# Add version if not present
if "version" not in config:
config["version"] = "1.0"
saved_config = save_directory_config(db, config, current_user.id)
return {
"message": "Directory configuration updated successfully",
"config": saved_config
}
except Exception as e:
logger.error(f"Error updating directory config: {str(e)}")
raise HTTPException(status_code=400, detail=str(e))
# Admin endpoint - reset directory configuration to default
@api_router.post("/admin/directory/config/reset")
async def reset_directory_config(
current_user: User = Depends(require_permission("directory.manage")),
db: Session = Depends(get_db)
):
"""Reset directory configuration to default"""
saved_config = save_directory_config(db, DEFAULT_DIRECTORY_CONFIG, current_user.id)
return {
"message": "Directory configuration reset to default",
"config": saved_config
}
# ============================================================================
# Admin Routes
# ============================================================================