bmc_hub/app/settings/backend/router.py
Christian 3a35042788 feat: Implement Vendors API and Frontend
- Added a new API router for managing vendors with endpoints for listing, creating, updating, retrieving, and deleting vendors.
- Implemented frontend views for displaying vendor lists and details using Jinja2 templates.
- Created HTML templates for vendor list and detail pages with responsive design and dynamic content loading.
- Added JavaScript functionality for vendor management, including pagination, filtering, and modal forms for creating new vendors.
- Introduced a settings table in the database for system configuration and extended the users table with additional fields.
- Developed a script to import vendors from an OmniSync database into the PostgreSQL database, handling errors and logging progress.
2025-12-06 11:04:19 +01:00

240 lines
7.1 KiB
Python

"""
Settings and User Management API Router
"""
from fastapi import APIRouter, HTTPException
from typing import List, Optional, Dict
from pydantic import BaseModel
from app.core.database import execute_query
import logging
logger = logging.getLogger(__name__)
router = APIRouter()
# Pydantic Models
class Setting(BaseModel):
id: int
key: str
value: Optional[str]
category: str
description: Optional[str]
value_type: str
is_public: bool
class SettingUpdate(BaseModel):
value: str
class User(BaseModel):
id: int
username: str
email: Optional[str]
full_name: Optional[str]
is_active: bool
last_login: Optional[str]
created_at: str
class UserCreate(BaseModel):
username: str
email: str
password: str
full_name: Optional[str] = None
class UserUpdate(BaseModel):
email: Optional[str] = None
full_name: Optional[str] = None
is_active: Optional[bool] = None
# Settings Endpoints
@router.get("/settings", response_model=List[Setting], tags=["Settings"])
async def get_settings(category: Optional[str] = None):
"""Get all settings or filter by category"""
query = "SELECT * FROM settings"
params = []
if category:
query += " WHERE category = %s"
params.append(category)
query += " ORDER BY category, key"
result = execute_query(query, tuple(params) if params else None)
return result or []
@router.get("/settings/{key}", response_model=Setting, tags=["Settings"])
async def get_setting(key: str):
"""Get a specific setting by key"""
query = "SELECT * FROM settings WHERE key = %s"
result = execute_query(query, (key,))
if not result:
raise HTTPException(status_code=404, detail="Setting not found")
return result[0]
@router.put("/settings/{key}", response_model=Setting, tags=["Settings"])
async def update_setting(key: str, setting: SettingUpdate):
"""Update a setting value"""
query = """
UPDATE settings
SET value = %s, updated_at = CURRENT_TIMESTAMP
WHERE key = %s
RETURNING *
"""
result = execute_query(query, (setting.value, key))
if not result:
raise HTTPException(status_code=404, detail="Setting not found")
logger.info(f"✅ Updated setting: {key}")
return result[0]
@router.get("/settings/categories/list", tags=["Settings"])
async def get_setting_categories():
"""Get list of all setting categories"""
query = "SELECT DISTINCT category FROM settings ORDER BY category"
result = execute_query(query)
return [row['category'] for row in result] if result else []
# User Management Endpoints
@router.get("/users", response_model=List[User], tags=["Users"])
async def get_users(is_active: Optional[bool] = None):
"""Get all users"""
query = "SELECT user_id as id, username, email, full_name, is_active, last_login, created_at FROM users"
params = []
if is_active is not None:
query += " WHERE is_active = %s"
params.append(is_active)
query += " ORDER BY username"
result = execute_query(query, tuple(params) if params else None)
return result or []
@router.get("/users/{user_id}", response_model=User, tags=["Users"])
async def get_user(user_id: int):
"""Get user by ID"""
query = "SELECT user_id as id, username, email, full_name, is_active, last_login, created_at FROM users WHERE user_id = %s"
result = execute_query(query, (user_id,))
if not result:
raise HTTPException(status_code=404, detail="User not found")
return result[0]
@router.post("/users", response_model=User, tags=["Users"])
async def create_user(user: UserCreate):
"""Create a new user"""
# Check if username exists
existing = execute_query("SELECT user_id FROM users WHERE username = %s", (user.username,))
if existing:
raise HTTPException(status_code=400, detail="Username already exists")
# Hash password (simple SHA256 for now - should use bcrypt in production)
import hashlib
password_hash = hashlib.sha256(user.password.encode()).hexdigest()
query = """
INSERT INTO users (username, email, password_hash, full_name, is_active)
VALUES (%s, %s, %s, %s, true)
RETURNING user_id as id, username, email, full_name, is_active, last_login, created_at
"""
result = execute_query(query, (user.username, user.email, password_hash, user.full_name))
if not result:
raise HTTPException(status_code=500, detail="Failed to create user")
logger.info(f"✅ Created user: {user.username}")
return result[0]
@router.put("/users/{user_id}", response_model=User, tags=["Users"])
async def update_user(user_id: int, user: UserUpdate):
"""Update user details"""
# Check if user exists
existing = execute_query("SELECT user_id FROM users WHERE user_id = %s", (user_id,))
if not existing:
raise HTTPException(status_code=404, detail="User not found")
# Build update query
update_fields = []
params = []
if user.email is not None:
update_fields.append("email = %s")
params.append(user.email)
if user.full_name is not None:
update_fields.append("full_name = %s")
params.append(user.full_name)
if user.is_active is not None:
update_fields.append("is_active = %s")
params.append(user.is_active)
if not update_fields:
raise HTTPException(status_code=400, detail="No fields to update")
params.append(user_id)
query = f"""
UPDATE users
SET {', '.join(update_fields)}, updated_at = CURRENT_TIMESTAMP
WHERE user_id = %s
RETURNING user_id as id, username, email, full_name, is_active, last_login, created_at
"""
result = execute_query(query, tuple(params))
if not result:
raise HTTPException(status_code=500, detail="Failed to update user")
logger.info(f"✅ Updated user: {user_id}")
return result[0]
@router.delete("/users/{user_id}", tags=["Users"])
async def deactivate_user(user_id: int):
"""Deactivate a user (soft delete)"""
query = """
UPDATE users
SET is_active = false, updated_at = CURRENT_TIMESTAMP
WHERE user_id = %s
RETURNING user_id as id
"""
result = execute_query(query, (user_id,))
if not result:
raise HTTPException(status_code=404, detail="User not found")
logger.info(f"✅ Deactivated user: {user_id}")
return {"message": "User deactivated successfully"}
@router.post("/users/{user_id}/reset-password", tags=["Users"])
async def reset_user_password(user_id: int, new_password: str):
"""Reset user password"""
import hashlib
password_hash = hashlib.sha256(new_password.encode()).hexdigest()
query = """
UPDATE users
SET password_hash = %s, updated_at = CURRENT_TIMESTAMP
WHERE user_id = %s
RETURNING user_id as id
"""
result = execute_query(query, (password_hash, user_id))
if not result:
raise HTTPException(status_code=404, detail="User not found")
logger.info(f"✅ Reset password for user: {user_id}")
return {"message": "Password reset successfully"}