updates
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -39,6 +39,7 @@ async def get_all_page_contents(
|
||||
"map_url": content.map_url,
|
||||
"social_links": json.loads(content.social_links) if content.social_links else None,
|
||||
"footer_links": json.loads(content.footer_links) if content.footer_links else None,
|
||||
"badges": json.loads(content.badges) if content.badges else None,
|
||||
"hero_title": content.hero_title,
|
||||
"hero_subtitle": content.hero_subtitle,
|
||||
"hero_image": content.hero_image,
|
||||
@@ -97,8 +98,10 @@ async def get_page_content(
|
||||
"og_image": content.og_image,
|
||||
"canonical_url": content.canonical_url,
|
||||
"contact_info": json.loads(content.contact_info) if content.contact_info else None,
|
||||
"map_url": content.map_url,
|
||||
"social_links": json.loads(content.social_links) if content.social_links else None,
|
||||
"footer_links": json.loads(content.footer_links) if content.footer_links else None,
|
||||
"badges": json.loads(content.badges) if content.badges else None,
|
||||
"hero_title": content.hero_title,
|
||||
"hero_subtitle": content.hero_subtitle,
|
||||
"hero_image": content.hero_image,
|
||||
@@ -141,6 +144,7 @@ async def create_or_update_page_content(
|
||||
map_url: Optional[str] = None,
|
||||
social_links: Optional[str] = None,
|
||||
footer_links: Optional[str] = None,
|
||||
badges: Optional[str] = None,
|
||||
hero_title: Optional[str] = None,
|
||||
hero_subtitle: Optional[str] = None,
|
||||
hero_image: Optional[str] = None,
|
||||
@@ -183,6 +187,15 @@ async def create_or_update_page_content(
|
||||
detail="Invalid JSON in footer_links"
|
||||
)
|
||||
|
||||
if badges:
|
||||
try:
|
||||
json.loads(badges)
|
||||
except json.JSONDecodeError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Invalid JSON in badges"
|
||||
)
|
||||
|
||||
if values:
|
||||
try:
|
||||
json.loads(values)
|
||||
@@ -236,6 +249,8 @@ async def create_or_update_page_content(
|
||||
existing_content.social_links = social_links
|
||||
if footer_links is not None:
|
||||
existing_content.footer_links = footer_links
|
||||
if badges is not None:
|
||||
existing_content.badges = badges
|
||||
if hero_title is not None:
|
||||
existing_content.hero_title = hero_title
|
||||
if hero_subtitle is not None:
|
||||
@@ -286,6 +301,7 @@ async def create_or_update_page_content(
|
||||
map_url=map_url,
|
||||
social_links=social_links,
|
||||
footer_links=footer_links,
|
||||
badges=badges,
|
||||
hero_title=hero_title,
|
||||
hero_subtitle=hero_subtitle,
|
||||
hero_image=hero_image,
|
||||
@@ -346,7 +362,7 @@ async def update_page_content(
|
||||
for key, value in page_data.items():
|
||||
if hasattr(existing_content, key):
|
||||
# Handle JSON fields - convert dict/list to JSON string
|
||||
if key in ["contact_info", "social_links", "footer_links", "values", "features"] and value is not None:
|
||||
if key in ["contact_info", "social_links", "footer_links", "badges", "values", "features"] and value is not None:
|
||||
if isinstance(value, str):
|
||||
# Already a string, validate it's valid JSON
|
||||
try:
|
||||
@@ -383,8 +399,10 @@ async def update_page_content(
|
||||
"og_image": existing_content.og_image,
|
||||
"canonical_url": existing_content.canonical_url,
|
||||
"contact_info": json.loads(existing_content.contact_info) if existing_content.contact_info else None,
|
||||
"map_url": existing_content.map_url,
|
||||
"social_links": json.loads(existing_content.social_links) if existing_content.social_links else None,
|
||||
"footer_links": json.loads(existing_content.footer_links) if existing_content.footer_links else None,
|
||||
"badges": json.loads(existing_content.badges) if existing_content.badges else None,
|
||||
"hero_title": existing_content.hero_title,
|
||||
"hero_subtitle": existing_content.hero_subtitle,
|
||||
"hero_image": existing_content.hero_image,
|
||||
|
||||
@@ -1,11 +1,33 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Request, UploadFile, File
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import Optional
|
||||
from datetime import datetime
|
||||
from pydantic import BaseModel, EmailStr
|
||||
from pathlib import Path
|
||||
import logging
|
||||
import aiofiles
|
||||
import uuid
|
||||
import os
|
||||
|
||||
from ..config.database import get_db
|
||||
from ..middleware.auth import get_current_user, authorize_roles
|
||||
from ..models.user import User
|
||||
from ..models.system_settings import SystemSettings
|
||||
from ..utils.mailer import send_email
|
||||
from ..services.room_service import get_base_url
|
||||
|
||||
|
||||
def normalize_image_url(image_url: str, base_url: str) -> str:
|
||||
"""Normalize image URL to absolute URL"""
|
||||
if not image_url:
|
||||
return image_url
|
||||
if image_url.startswith('http://') or image_url.startswith('https://'):
|
||||
return image_url
|
||||
if image_url.startswith('/'):
|
||||
return f"{base_url}{image_url}"
|
||||
return f"{base_url}/{image_url}"
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/admin/system-settings", tags=["admin-system-settings"])
|
||||
|
||||
@@ -300,3 +322,768 @@ async def update_stripe_settings(
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.get("/smtp")
|
||||
async def get_smtp_settings(
|
||||
current_user: User = Depends(authorize_roles("admin")),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get SMTP email server settings (Admin only)"""
|
||||
try:
|
||||
# Get all SMTP settings
|
||||
smtp_settings = {}
|
||||
setting_keys = [
|
||||
"smtp_host",
|
||||
"smtp_port",
|
||||
"smtp_user",
|
||||
"smtp_password",
|
||||
"smtp_from_email",
|
||||
"smtp_from_name",
|
||||
"smtp_use_tls",
|
||||
]
|
||||
|
||||
for key in setting_keys:
|
||||
setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key == key
|
||||
).first()
|
||||
if setting:
|
||||
smtp_settings[key] = setting.value
|
||||
|
||||
# Mask password for security (only show last 4 characters if set)
|
||||
def mask_password(password_value: str) -> str:
|
||||
if not password_value or len(password_value) < 4:
|
||||
return ""
|
||||
return "*" * (len(password_value) - 4) + password_value[-4:]
|
||||
|
||||
result = {
|
||||
"smtp_host": smtp_settings.get("smtp_host", ""),
|
||||
"smtp_port": smtp_settings.get("smtp_port", ""),
|
||||
"smtp_user": smtp_settings.get("smtp_user", ""),
|
||||
"smtp_password": "",
|
||||
"smtp_password_masked": mask_password(smtp_settings.get("smtp_password", "")),
|
||||
"smtp_from_email": smtp_settings.get("smtp_from_email", ""),
|
||||
"smtp_from_name": smtp_settings.get("smtp_from_name", ""),
|
||||
"smtp_use_tls": smtp_settings.get("smtp_use_tls", "true").lower() == "true",
|
||||
"has_host": bool(smtp_settings.get("smtp_host")),
|
||||
"has_user": bool(smtp_settings.get("smtp_user")),
|
||||
"has_password": bool(smtp_settings.get("smtp_password")),
|
||||
}
|
||||
|
||||
# Get updated_at and updated_by from any setting (prefer password setting if exists)
|
||||
password_setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key == "smtp_password"
|
||||
).first()
|
||||
|
||||
if password_setting:
|
||||
result["updated_at"] = password_setting.updated_at.isoformat() if password_setting.updated_at else None
|
||||
result["updated_by"] = password_setting.updated_by.full_name if password_setting.updated_by else None
|
||||
else:
|
||||
# Try to get from any other SMTP setting
|
||||
any_setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key.in_(setting_keys)
|
||||
).first()
|
||||
if any_setting:
|
||||
result["updated_at"] = any_setting.updated_at.isoformat() if any_setting.updated_at else None
|
||||
result["updated_by"] = any_setting.updated_by.full_name if any_setting.updated_by else None
|
||||
else:
|
||||
result["updated_at"] = None
|
||||
result["updated_by"] = None
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"data": result
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.put("/smtp")
|
||||
async def update_smtp_settings(
|
||||
smtp_data: dict,
|
||||
current_user: User = Depends(authorize_roles("admin")),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Update SMTP email server settings (Admin only)"""
|
||||
try:
|
||||
smtp_host = smtp_data.get("smtp_host", "").strip()
|
||||
smtp_port = smtp_data.get("smtp_port", "").strip()
|
||||
smtp_user = smtp_data.get("smtp_user", "").strip()
|
||||
smtp_password = smtp_data.get("smtp_password", "").strip()
|
||||
smtp_from_email = smtp_data.get("smtp_from_email", "").strip()
|
||||
smtp_from_name = smtp_data.get("smtp_from_name", "").strip()
|
||||
smtp_use_tls = smtp_data.get("smtp_use_tls", True)
|
||||
|
||||
# Validate required fields if provided
|
||||
if smtp_host and not smtp_host:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="SMTP host cannot be empty"
|
||||
)
|
||||
|
||||
if smtp_port:
|
||||
try:
|
||||
port_num = int(smtp_port)
|
||||
if port_num < 1 or port_num > 65535:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="SMTP port must be between 1 and 65535"
|
||||
)
|
||||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="SMTP port must be a valid number"
|
||||
)
|
||||
|
||||
if smtp_from_email:
|
||||
# Basic email validation
|
||||
if "@" not in smtp_from_email or "." not in smtp_from_email.split("@")[1]:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Invalid email address format for 'From Email'"
|
||||
)
|
||||
|
||||
# Helper function to update or create setting
|
||||
def update_setting(key: str, value: str, description: str):
|
||||
setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key == key
|
||||
).first()
|
||||
|
||||
if setting:
|
||||
setting.value = value
|
||||
setting.updated_by_id = current_user.id
|
||||
else:
|
||||
setting = SystemSettings(
|
||||
key=key,
|
||||
value=value,
|
||||
description=description,
|
||||
updated_by_id=current_user.id
|
||||
)
|
||||
db.add(setting)
|
||||
|
||||
# Update or create settings (only update if value is provided)
|
||||
if smtp_host:
|
||||
update_setting(
|
||||
"smtp_host",
|
||||
smtp_host,
|
||||
"SMTP server hostname (e.g., smtp.gmail.com)"
|
||||
)
|
||||
|
||||
if smtp_port:
|
||||
update_setting(
|
||||
"smtp_port",
|
||||
smtp_port,
|
||||
"SMTP server port (e.g., 587 for STARTTLS, 465 for SSL)"
|
||||
)
|
||||
|
||||
if smtp_user:
|
||||
update_setting(
|
||||
"smtp_user",
|
||||
smtp_user,
|
||||
"SMTP authentication username/email"
|
||||
)
|
||||
|
||||
if smtp_password:
|
||||
update_setting(
|
||||
"smtp_password",
|
||||
smtp_password,
|
||||
"SMTP authentication password (stored securely)"
|
||||
)
|
||||
|
||||
if smtp_from_email:
|
||||
update_setting(
|
||||
"smtp_from_email",
|
||||
smtp_from_email,
|
||||
"Default 'From' email address for outgoing emails"
|
||||
)
|
||||
|
||||
if smtp_from_name:
|
||||
update_setting(
|
||||
"smtp_from_name",
|
||||
smtp_from_name,
|
||||
"Default 'From' name for outgoing emails"
|
||||
)
|
||||
|
||||
# Update TLS setting (convert boolean to string)
|
||||
if smtp_use_tls is not None:
|
||||
update_setting(
|
||||
"smtp_use_tls",
|
||||
"true" if smtp_use_tls else "false",
|
||||
"Use TLS/SSL for SMTP connection (true for port 465, false for port 587 with STARTTLS)"
|
||||
)
|
||||
|
||||
db.commit()
|
||||
|
||||
# Return updated settings with masked password
|
||||
def mask_password(password_value: str) -> str:
|
||||
if not password_value or len(password_value) < 4:
|
||||
return ""
|
||||
return "*" * (len(password_value) - 4) + password_value[-4:]
|
||||
|
||||
# Get updated settings
|
||||
updated_settings = {}
|
||||
for key in ["smtp_host", "smtp_port", "smtp_user", "smtp_password", "smtp_from_email", "smtp_from_name", "smtp_use_tls"]:
|
||||
setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key == key
|
||||
).first()
|
||||
if setting:
|
||||
updated_settings[key] = setting.value
|
||||
|
||||
result = {
|
||||
"smtp_host": updated_settings.get("smtp_host", ""),
|
||||
"smtp_port": updated_settings.get("smtp_port", ""),
|
||||
"smtp_user": updated_settings.get("smtp_user", ""),
|
||||
"smtp_password": smtp_password if smtp_password else "",
|
||||
"smtp_password_masked": mask_password(updated_settings.get("smtp_password", "")),
|
||||
"smtp_from_email": updated_settings.get("smtp_from_email", ""),
|
||||
"smtp_from_name": updated_settings.get("smtp_from_name", ""),
|
||||
"smtp_use_tls": updated_settings.get("smtp_use_tls", "true").lower() == "true",
|
||||
"has_host": bool(updated_settings.get("smtp_host")),
|
||||
"has_user": bool(updated_settings.get("smtp_user")),
|
||||
"has_password": bool(updated_settings.get("smtp_password")),
|
||||
}
|
||||
|
||||
# Get updated_by from password setting if it exists
|
||||
password_setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key == "smtp_password"
|
||||
).first()
|
||||
if password_setting:
|
||||
result["updated_at"] = password_setting.updated_at.isoformat() if password_setting.updated_at else None
|
||||
result["updated_by"] = password_setting.updated_by.full_name if password_setting.updated_by else None
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "SMTP settings updated successfully",
|
||||
"data": result
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
class TestEmailRequest(BaseModel):
|
||||
email: EmailStr
|
||||
|
||||
|
||||
@router.post("/smtp/test")
|
||||
async def test_smtp_email(
|
||||
request: TestEmailRequest,
|
||||
current_user: User = Depends(authorize_roles("admin")),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Send a test email to verify SMTP settings (Admin only)"""
|
||||
try:
|
||||
test_email = str(request.email)
|
||||
admin_name = str(current_user.full_name or current_user.email or "Admin")
|
||||
timestamp_str = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
|
||||
|
||||
# Create test email HTML content
|
||||
test_html = f"""
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<style>
|
||||
body {{
|
||||
font-family: Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}}
|
||||
.header {{
|
||||
background: linear-gradient(135deg, #d4af37 0%, #c9a227 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
border-radius: 10px 10px 0 0;
|
||||
}}
|
||||
.content {{
|
||||
background: #f9f9f9;
|
||||
padding: 30px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 0 0 10px 10px;
|
||||
}}
|
||||
.success-icon {{
|
||||
font-size: 48px;
|
||||
margin-bottom: 20px;
|
||||
}}
|
||||
.info-box {{
|
||||
background: white;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-left: 4px solid #d4af37;
|
||||
border-radius: 5px;
|
||||
}}
|
||||
.footer {{
|
||||
margin-top: 30px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
text-align: center;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="header">
|
||||
<h1>✅ SMTP Test Email</h1>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div style="text-align: center;">
|
||||
<div class="success-icon">🎉</div>
|
||||
<h2>Email Configuration Test Successful!</h2>
|
||||
</div>
|
||||
|
||||
<p>This is a test email sent from your Hotel Booking system to verify that the SMTP email settings are configured correctly.</p>
|
||||
|
||||
<div class="info-box">
|
||||
<strong>📧 Test Details:</strong>
|
||||
<ul>
|
||||
<li><strong>Recipient:</strong> {test_email}</li>
|
||||
<li><strong>Sent by:</strong> {admin_name}</li>
|
||||
<li><strong>Time:</strong> {timestamp_str}</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<p>If you received this email, it means your SMTP server settings are working correctly and the system can send emails through your configured email server.</p>
|
||||
|
||||
<p><strong>What's next?</strong></p>
|
||||
<ul>
|
||||
<li>Welcome emails for new user registrations</li>
|
||||
<li>Password reset emails</li>
|
||||
<li>Booking confirmation emails</li>
|
||||
<li>Payment notifications</li>
|
||||
<li>And other system notifications</li>
|
||||
</ul>
|
||||
|
||||
<div class="footer">
|
||||
<p>This is an automated test email from Hotel Booking System</p>
|
||||
<p>If you did not request this test, please ignore this email.</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# Plain text version
|
||||
test_text = f"""
|
||||
SMTP Test Email
|
||||
|
||||
This is a test email sent from your Hotel Booking system to verify that the SMTP email settings are configured correctly.
|
||||
|
||||
Test Details:
|
||||
- Recipient: {test_email}
|
||||
- Sent by: {admin_name}
|
||||
- Time: {timestamp_str}
|
||||
|
||||
If you received this email, it means your SMTP server settings are working correctly and the system can send emails through your configured email server.
|
||||
|
||||
This is an automated test email from Hotel Booking System
|
||||
If you did not request this test, please ignore this email.
|
||||
""".strip()
|
||||
|
||||
# Send the test email
|
||||
await send_email(
|
||||
to=test_email,
|
||||
subject="SMTP Test Email - Hotel Booking System",
|
||||
html=test_html,
|
||||
text=test_text
|
||||
)
|
||||
|
||||
sent_at = datetime.utcnow()
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": f"Test email sent successfully to {test_email}",
|
||||
"data": {
|
||||
"recipient": test_email,
|
||||
"sent_at": sent_at.isoformat()
|
||||
}
|
||||
}
|
||||
except HTTPException:
|
||||
# Re-raise HTTP exceptions (like validation errors from send_email)
|
||||
raise
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
logger.error(f"Error sending test email: {type(e).__name__}: {error_msg}", exc_info=True)
|
||||
|
||||
# Provide more user-friendly error messages
|
||||
if "SMTP mailer not configured" in error_msg:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="SMTP settings are not fully configured. Please configure SMTP Host, Username, and Password in the Email Server settings."
|
||||
)
|
||||
elif "authentication failed" in error_msg.lower() or "invalid credentials" in error_msg.lower():
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="SMTP authentication failed. Please check your SMTP username and password."
|
||||
)
|
||||
elif "connection" in error_msg.lower() or "timeout" in error_msg.lower():
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Cannot connect to SMTP server. Please check your SMTP host and port settings. Error: {error_msg}"
|
||||
)
|
||||
else:
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to send test email: {error_msg}"
|
||||
)
|
||||
|
||||
|
||||
class UpdateCompanySettingsRequest(BaseModel):
|
||||
company_name: Optional[str] = None
|
||||
company_tagline: Optional[str] = None
|
||||
company_phone: Optional[str] = None
|
||||
company_email: Optional[str] = None
|
||||
company_address: Optional[str] = None
|
||||
|
||||
|
||||
@router.get("/company")
|
||||
async def get_company_settings(
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get company settings (public endpoint for frontend)"""
|
||||
try:
|
||||
setting_keys = [
|
||||
"company_name",
|
||||
"company_tagline",
|
||||
"company_logo_url",
|
||||
"company_favicon_url",
|
||||
"company_phone",
|
||||
"company_email",
|
||||
"company_address",
|
||||
]
|
||||
|
||||
settings_dict = {}
|
||||
for key in setting_keys:
|
||||
setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key == key
|
||||
).first()
|
||||
if setting:
|
||||
settings_dict[key] = setting.value
|
||||
else:
|
||||
settings_dict[key] = None
|
||||
|
||||
# Get updated_at and updated_by from logo setting if exists
|
||||
logo_setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key == "company_logo_url"
|
||||
).first()
|
||||
|
||||
updated_at = None
|
||||
updated_by = None
|
||||
if logo_setting:
|
||||
updated_at = logo_setting.updated_at.isoformat() if logo_setting.updated_at else None
|
||||
updated_by = logo_setting.updated_by.full_name if logo_setting.updated_by else None
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"data": {
|
||||
"company_name": settings_dict.get("company_name", ""),
|
||||
"company_tagline": settings_dict.get("company_tagline", ""),
|
||||
"company_logo_url": settings_dict.get("company_logo_url", ""),
|
||||
"company_favicon_url": settings_dict.get("company_favicon_url", ""),
|
||||
"company_phone": settings_dict.get("company_phone", ""),
|
||||
"company_email": settings_dict.get("company_email", ""),
|
||||
"company_address": settings_dict.get("company_address", ""),
|
||||
"updated_at": updated_at,
|
||||
"updated_by": updated_by,
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.put("/company")
|
||||
async def update_company_settings(
|
||||
request_data: UpdateCompanySettingsRequest,
|
||||
current_user: User = Depends(authorize_roles("admin")),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Update company settings (Admin only)"""
|
||||
try:
|
||||
db_settings = {}
|
||||
|
||||
if request_data.company_name is not None:
|
||||
db_settings["company_name"] = request_data.company_name
|
||||
if request_data.company_tagline is not None:
|
||||
db_settings["company_tagline"] = request_data.company_tagline
|
||||
if request_data.company_phone is not None:
|
||||
db_settings["company_phone"] = request_data.company_phone
|
||||
if request_data.company_email is not None:
|
||||
db_settings["company_email"] = request_data.company_email
|
||||
if request_data.company_address is not None:
|
||||
db_settings["company_address"] = request_data.company_address
|
||||
|
||||
for key, value in db_settings.items():
|
||||
# Find or create setting
|
||||
setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key == key
|
||||
).first()
|
||||
|
||||
if setting:
|
||||
# Update existing
|
||||
setting.value = value if value else None
|
||||
setting.updated_at = datetime.utcnow()
|
||||
setting.updated_by_id = current_user.id
|
||||
else:
|
||||
# Create new
|
||||
setting = SystemSettings(
|
||||
key=key,
|
||||
value=value if value else None,
|
||||
updated_by_id=current_user.id
|
||||
)
|
||||
db.add(setting)
|
||||
|
||||
db.commit()
|
||||
|
||||
# Get updated settings
|
||||
updated_settings = {}
|
||||
for key in ["company_name", "company_tagline", "company_logo_url", "company_favicon_url", "company_phone", "company_email", "company_address"]:
|
||||
setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key == key
|
||||
).first()
|
||||
if setting:
|
||||
updated_settings[key] = setting.value
|
||||
else:
|
||||
updated_settings[key] = None
|
||||
|
||||
# Get updated_at and updated_by
|
||||
logo_setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key == "company_logo_url"
|
||||
).first()
|
||||
if not logo_setting:
|
||||
logo_setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key == "company_name"
|
||||
).first()
|
||||
|
||||
updated_at = None
|
||||
updated_by = None
|
||||
if logo_setting:
|
||||
updated_at = logo_setting.updated_at.isoformat() if logo_setting.updated_at else None
|
||||
updated_by = logo_setting.updated_by.full_name if logo_setting.updated_by else None
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Company settings updated successfully",
|
||||
"data": {
|
||||
"company_name": updated_settings.get("company_name", ""),
|
||||
"company_tagline": updated_settings.get("company_tagline", ""),
|
||||
"company_logo_url": updated_settings.get("company_logo_url", ""),
|
||||
"company_favicon_url": updated_settings.get("company_favicon_url", ""),
|
||||
"company_phone": updated_settings.get("company_phone", ""),
|
||||
"company_email": updated_settings.get("company_email", ""),
|
||||
"company_address": updated_settings.get("company_address", ""),
|
||||
"updated_at": updated_at,
|
||||
"updated_by": updated_by,
|
||||
}
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/company/logo")
|
||||
async def upload_company_logo(
|
||||
request: Request,
|
||||
image: UploadFile = File(...),
|
||||
current_user: User = Depends(authorize_roles("admin")),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Upload company logo (Admin only)"""
|
||||
try:
|
||||
# Validate file type
|
||||
if not image.content_type or not image.content_type.startswith('image/'):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="File must be an image"
|
||||
)
|
||||
|
||||
# Validate file size (max 2MB)
|
||||
content = await image.read()
|
||||
if len(content) > 2 * 1024 * 1024: # 2MB
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Logo file size must be less than 2MB"
|
||||
)
|
||||
|
||||
# Create uploads directory
|
||||
upload_dir = Path(__file__).parent.parent.parent / "uploads" / "company"
|
||||
upload_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Delete old logo if exists
|
||||
old_logo_setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key == "company_logo_url"
|
||||
).first()
|
||||
|
||||
if old_logo_setting and old_logo_setting.value:
|
||||
old_logo_path = Path(__file__).parent.parent.parent / old_logo_setting.value.lstrip('/')
|
||||
if old_logo_path.exists() and old_logo_path.is_file():
|
||||
try:
|
||||
old_logo_path.unlink()
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not delete old logo: {e}")
|
||||
|
||||
# Generate filename
|
||||
ext = Path(image.filename).suffix or '.png'
|
||||
# Always use logo.png to ensure we only have one logo
|
||||
filename = "logo.png"
|
||||
file_path = upload_dir / filename
|
||||
|
||||
# Save file
|
||||
async with aiofiles.open(file_path, 'wb') as f:
|
||||
await f.write(content)
|
||||
|
||||
# Store the URL in system_settings
|
||||
image_url = f"/uploads/company/{filename}"
|
||||
|
||||
# Update or create setting
|
||||
logo_setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key == "company_logo_url"
|
||||
).first()
|
||||
|
||||
if logo_setting:
|
||||
logo_setting.value = image_url
|
||||
logo_setting.updated_at = datetime.utcnow()
|
||||
logo_setting.updated_by_id = current_user.id
|
||||
else:
|
||||
logo_setting = SystemSettings(
|
||||
key="company_logo_url",
|
||||
value=image_url,
|
||||
updated_by_id=current_user.id
|
||||
)
|
||||
db.add(logo_setting)
|
||||
|
||||
db.commit()
|
||||
|
||||
# Return the image URL
|
||||
base_url = get_base_url(request)
|
||||
full_url = normalize_image_url(image_url, base_url)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Logo uploaded successfully",
|
||||
"data": {
|
||||
"logo_url": image_url,
|
||||
"full_url": full_url
|
||||
}
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error uploading logo: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@router.post("/company/favicon")
|
||||
async def upload_company_favicon(
|
||||
request: Request,
|
||||
image: UploadFile = File(...),
|
||||
current_user: User = Depends(authorize_roles("admin")),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Upload company favicon (Admin only)"""
|
||||
try:
|
||||
# Validate file type (favicon can be ico, png, svg)
|
||||
if not image.content_type:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="File type could not be determined"
|
||||
)
|
||||
|
||||
allowed_types = ['image/x-icon', 'image/vnd.microsoft.icon', 'image/png', 'image/svg+xml', 'image/ico']
|
||||
if image.content_type not in allowed_types:
|
||||
# Check filename extension as fallback
|
||||
filename_lower = (image.filename or '').lower()
|
||||
if not any(filename_lower.endswith(ext) for ext in ['.ico', '.png', '.svg']):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Favicon must be .ico, .png, or .svg file"
|
||||
)
|
||||
|
||||
# Validate file size (max 500KB)
|
||||
content = await image.read()
|
||||
if len(content) > 500 * 1024: # 500KB
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="Favicon file size must be less than 500KB"
|
||||
)
|
||||
|
||||
# Create uploads directory
|
||||
upload_dir = Path(__file__).parent.parent.parent / "uploads" / "company"
|
||||
upload_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# Delete old favicon if exists
|
||||
old_favicon_setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key == "company_favicon_url"
|
||||
).first()
|
||||
|
||||
if old_favicon_setting and old_favicon_setting.value:
|
||||
old_favicon_path = Path(__file__).parent.parent.parent / old_favicon_setting.value.lstrip('/')
|
||||
if old_favicon_path.exists() and old_favicon_path.is_file():
|
||||
try:
|
||||
old_favicon_path.unlink()
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not delete old favicon: {e}")
|
||||
|
||||
# Generate filename - preserve extension but use standard name
|
||||
filename_lower = (image.filename or '').lower()
|
||||
if filename_lower.endswith('.ico'):
|
||||
filename = "favicon.ico"
|
||||
elif filename_lower.endswith('.svg'):
|
||||
filename = "favicon.svg"
|
||||
else:
|
||||
filename = "favicon.png"
|
||||
|
||||
file_path = upload_dir / filename
|
||||
|
||||
# Save file
|
||||
async with aiofiles.open(file_path, 'wb') as f:
|
||||
await f.write(content)
|
||||
|
||||
# Store the URL in system_settings
|
||||
image_url = f"/uploads/company/{filename}"
|
||||
|
||||
# Update or create setting
|
||||
favicon_setting = db.query(SystemSettings).filter(
|
||||
SystemSettings.key == "company_favicon_url"
|
||||
).first()
|
||||
|
||||
if favicon_setting:
|
||||
favicon_setting.value = image_url
|
||||
favicon_setting.updated_at = datetime.utcnow()
|
||||
favicon_setting.updated_by_id = current_user.id
|
||||
else:
|
||||
favicon_setting = SystemSettings(
|
||||
key="company_favicon_url",
|
||||
value=image_url,
|
||||
updated_by_id=current_user.id
|
||||
)
|
||||
db.add(favicon_setting)
|
||||
|
||||
db.commit()
|
||||
|
||||
# Return the image URL
|
||||
base_url = get_base_url(request)
|
||||
full_url = normalize_image_url(image_url, base_url)
|
||||
|
||||
return {
|
||||
"status": "success",
|
||||
"message": "Favicon uploaded successfully",
|
||||
"data": {
|
||||
"favicon_url": image_url,
|
||||
"full_url": full_url
|
||||
}
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error uploading favicon: {e}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user