- 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.
240 lines
7.1 KiB
Python
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"}
|