201 előtti mentés

This commit is contained in:
Roo
2026-03-26 07:09:44 +00:00
parent 89668a9beb
commit 03258db091
124 changed files with 13619 additions and 13347 deletions

View File

@@ -4,7 +4,7 @@ from app.api.v1.endpoints import (
auth, catalog, assets, organizations, documents,
services, admin, expenses, evidence, social, security,
billing, finance_admin, analytics, vehicles, system_parameters,
gamification, translations
gamification, translations, users, reports
)
api_router = APIRouter()
@@ -26,4 +26,6 @@ api_router.include_router(analytics.router, prefix="/analytics", tags=["Analytic
api_router.include_router(vehicles.router, prefix="/vehicles", tags=["Vehicles"])
api_router.include_router(system_parameters.router, prefix="/system/parameters", tags=["System Parameters"])
api_router.include_router(gamification.router, prefix="/gamification", tags=["Gamification"])
api_router.include_router(translations.router, prefix="/translations", tags=["i18n"])
api_router.include_router(translations.router, prefix="/translations", tags=["i18n"])
api_router.include_router(users.router, prefix="/users", tags=["Users"])
api_router.include_router(reports.router, prefix="/reports", tags=["Reports"])

View File

@@ -9,7 +9,7 @@ from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.api import deps
from app.schemas.analytics import TCOSummaryResponse, TCOErrorResponse
from app.schemas.analytics import TCOSummaryResponse, TCOErrorResponse, DashboardResponse
from app.services.analytics_service import TCOAnalytics
from app.models import Vehicle
from app.models.marketplace.organization import OrganizationMember
@@ -190,6 +190,102 @@ async def get_tco_summary(
raise
except Exception as e:
logger.exception(f"Unexpected error in TCO summary for vehicle {vehicle_id}: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Internal server error: {str(e)}"
)
@router.get(
"/dashboard",
response_model=DashboardResponse,
responses={
500: {"model": TCOErrorResponse, "description": "Internal server error"},
},
summary="Get dashboard analytics data",
description="Returns aggregated dashboard data including monthly costs, fuel efficiency trends, "
"and business metrics for the user's fleet."
)
async def get_dashboard_analytics(
db: AsyncSession = Depends(deps.get_db),
current_user = Depends(deps.get_current_active_user),
):
"""
Retrieve dashboard analytics for the user's fleet.
This endpoint returns mock data for now, but will be connected to real
analytics services in the future.
"""
try:
# For now, return mock data matching the frontend expectations
# In production, this would query the database and aggregate real data
# Import the new schema
from app.schemas.analytics import (
DashboardResponse, DashboardMonthlyCost, DashboardFuelEfficiency,
DashboardCostPerKm, DashboardFunFacts, DashboardBusinessMetrics
)
# Mock monthly costs (last 6 months)
monthly_costs = [
DashboardMonthlyCost(month="Oct", maintenance=450, fuel=320, insurance=180, total=950),
DashboardMonthlyCost(month="Nov", maintenance=520, fuel=310, insurance=180, total=1010),
DashboardMonthlyCost(month="Dec", maintenance=380, fuel=290, insurance=180, total=850),
DashboardMonthlyCost(month="Jan", maintenance=620, fuel=350, insurance=200, total=1170),
DashboardMonthlyCost(month="Feb", maintenance=410, fuel=280, insurance=180, total=870),
DashboardMonthlyCost(month="Mar", maintenance=480, fuel=330, insurance=180, total=990),
]
# Mock fuel efficiency trends
fuel_efficiency_trends = [
DashboardFuelEfficiency(month="Oct", efficiency=12.5),
DashboardFuelEfficiency(month="Nov", efficiency=12.8),
DashboardFuelEfficiency(month="Dec", efficiency=13.2),
DashboardFuelEfficiency(month="Jan", efficiency=12.9),
DashboardFuelEfficiency(month="Feb", efficiency=13.5),
DashboardFuelEfficiency(month="Mar", efficiency=13.8),
]
# Mock cost per km trends
cost_per_km_trends = [
DashboardCostPerKm(month="Oct", cost=0.42),
DashboardCostPerKm(month="Nov", cost=0.45),
DashboardCostPerKm(month="Dec", cost=0.38),
DashboardCostPerKm(month="Jan", cost=0.51),
DashboardCostPerKm(month="Feb", cost=0.39),
DashboardCostPerKm(month="Mar", cost=0.41),
]
# Mock fun facts
fun_facts = DashboardFunFacts(
total_km_driven=384400,
total_trees_saved=42,
total_co2_saved=8.5,
total_money_saved=12500,
moon_trips=1,
earth_circuits=10
)
# Mock business metrics
business_metrics = DashboardBusinessMetrics(
fleet_size=24,
average_vehicle_age=3.2,
total_monthly_cost=23500,
average_cost_per_km=0.43,
utilization_rate=78,
downtime_hours=42
)
return DashboardResponse(
monthly_costs=monthly_costs,
fuel_efficiency_trends=fuel_efficiency_trends,
cost_per_km_trends=cost_per_km_trends,
fun_facts=fun_facts,
business_metrics=business_metrics
)
except Exception as e:
logger.exception(f"Unexpected error in dashboard analytics: {e}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Internal server error: {str(e)}"

View File

@@ -18,6 +18,52 @@ from app.schemas.asset import AssetResponse, AssetCreate
router = APIRouter()
@router.get("/vehicles", response_model=List[AssetResponse])
async def get_user_vehicles(
skip: int = 0,
limit: int = 100,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
Get all vehicles/assets belonging to the current user or their organization.
This endpoint returns a paginated list of vehicles that the authenticated user
has access to (either as owner or through organization membership).
"""
# Query assets where user is owner or organization member
from sqlalchemy import or_
# First, get user's organization memberships
from app.models.marketplace.organization import OrganizationMember
org_stmt = select(OrganizationMember.organization_id).where(
OrganizationMember.user_id == current_user.id
)
org_result = await db.execute(org_stmt)
user_org_ids = [row[0] for row in org_result.all()]
# Build query: assets owned by user OR assets in user's organizations
stmt = (
select(Asset)
.where(
or_(
Asset.owner_person_id == current_user.id,
Asset.owner_org_id.in_(user_org_ids) if user_org_ids else False,
Asset.operator_person_id == current_user.id,
Asset.operator_org_id.in_(user_org_ids) if user_org_ids else False
)
)
.order_by(Asset.created_at.desc())
.offset(skip)
.limit(limit)
.options(selectinload(Asset.catalog))
)
result = await db.execute(stmt)
assets = result.scalars().all()
return assets
@router.get("/{asset_id}/financial-summary", response_model=Dict[str, Any])
async def get_asset_financial_report(
asset_id: uuid.UUID,

View File

@@ -3,39 +3,65 @@ from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.api.deps import get_db, get_current_user
from app.models import Asset, AssetCost # JAVÍTVA
from pydantic import BaseModel
from datetime import date
from app.models import Asset, AssetCost
from app.schemas.asset_cost import AssetCostCreate
from datetime import datetime
router = APIRouter()
class ExpenseCreate(BaseModel):
asset_id: str
category: str
amount: float
date: date
@router.post("/add")
async def add_expense(expense: ExpenseCreate, db: AsyncSession = Depends(get_db), current_user = Depends(get_current_user)):
@router.post("/", status_code=201)
async def create_expense(
expense: AssetCostCreate,
db: AsyncSession = Depends(get_db),
current_user = Depends(get_current_user)
):
"""
Create a new expense (fuel, service, tax, insurance) for an asset.
Uses AssetCostCreate schema which includes mileage_at_cost, cost_type, etc.
"""
# Validate asset exists
stmt = select(Asset).where(Asset.id == expense.asset_id)
result = await db.execute(stmt)
asset = result.scalar_one_or_none()
if not asset:
raise HTTPException(status_code=404, detail="Jármű nem található.")
raise HTTPException(status_code=404, detail="Asset not found.")
# Determine organization_id from asset
# Determine organization_id from asset (required by AssetCost model)
organization_id = asset.current_organization_id or asset.owner_org_id
if not organization_id:
raise HTTPException(status_code=400, detail="Az eszközhez nincs társított szervezet.")
raise HTTPException(status_code=400, detail="Asset has no associated organization.")
# Map cost_type to cost_category (AssetCost uses cost_category)
cost_category = expense.cost_type
# Prepare data JSON for extra fields (mileage_at_cost, description, etc.)
data = expense.data.copy() if expense.data else {}
if expense.mileage_at_cost is not None:
data["mileage_at_cost"] = expense.mileage_at_cost
if expense.description:
data["description"] = expense.description
# Create AssetCost instance
new_cost = AssetCost(
asset_id=expense.asset_id,
cost_category=expense.category,
amount_net=expense.amount,
currency="HUF",
organization_id=organization_id,
cost_category=cost_category,
amount_net=expense.amount_local,
currency=expense.currency_local,
date=expense.date,
organization_id=organization_id
invoice_number=data.get("invoice_number"),
data=data
)
db.add(new_cost)
await db.commit()
return {"status": "success"}
await db.refresh(new_cost)
return {
"status": "success",
"id": new_cost.id,
"asset_id": new_cost.asset_id,
"cost_category": new_cost.cost_category,
"amount_net": new_cost.amount_net,
"date": new_cost.date
}

View File

@@ -472,4 +472,455 @@ async def get_leaderboard_top10(
current_level=stats.current_level
)
)
return leaderboard
return leaderboard
# --- QUIZ ENDPOINTS FOR DAILY QUIZ GAMIFICATION ---
@router.get("/quiz/daily")
async def get_daily_quiz(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
Returns daily quiz questions for the user.
Checks if user has already played today.
"""
# Check if user has already played today
today = datetime.now().date()
stmt = select(PointsLedger).where(
PointsLedger.user_id == current_user.id,
func.date(PointsLedger.created_at) == today,
PointsLedger.reason.ilike("%quiz%")
)
result = await db.execute(stmt)
already_played = result.scalar_one_or_none()
if already_played:
raise HTTPException(
status_code=400,
detail="You have already played the daily quiz today. Try again tomorrow."
)
# Return quiz questions (for now, using mock questions - in production these would come from a database)
quiz_questions = [
{
"id": 1,
"question": "Melyik alkatrész felelős a motor levegőüzemanyag keverékének szabályozásáért?",
"options": ["Generátor", "Lambdaszonda", "Féktárcsa", "Olajszűrő"],
"correctAnswer": 1,
"explanation": "A lambdaszonda méri a kipufogógáz oxigéntartalmát, és ezen alapul a befecskendezés."
},
{
"id": 2,
"question": "Mennyi ideig érvényes egy gépjármű műszaki vizsgája Magyarországon?",
"options": ["1 év", "2 év", "4 év", "6 év"],
"correctAnswer": 1,
"explanation": "A személygépkocsik műszaki vizsgája 2 évre érvényes, kivéve az újonnan forgalomba helyezett autókat."
},
{
"id": 3,
"question": "Melyik anyag NEM része a hibrid autók akkumulátorának?",
"options": ["Lítium", "Nikkel", "Ólom", "Kobalt"],
"correctAnswer": 2,
"explanation": "A hibrid és elektromos autók akkumulátoraiban általában lítium, nikkel és kobalt található, ólom az ólomsavas akkukban van."
}
]
return {
"questions": quiz_questions,
"total_questions": len(quiz_questions),
"date": today.isoformat()
}
@router.post("/quiz/answer")
async def submit_quiz_answer(
question_id: int = Body(...),
selected_option: int = Body(...),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
Submit answer to a quiz question and award points if correct.
"""
# Check if user has already played today
today = datetime.now().date()
stmt = select(PointsLedger).where(
PointsLedger.user_id == current_user.id,
func.date(PointsLedger.created_at) == today,
PointsLedger.reason.ilike("%quiz%")
)
result = await db.execute(stmt)
already_played = result.scalar_one_or_none()
if already_played:
raise HTTPException(
status_code=400,
detail="You have already played the daily quiz today. Try again tomorrow."
)
# Mock quiz data - in production this would come from a database
quiz_data = {
1: {"correct_answer": 1, "points": 10, "explanation": "A lambdaszonda méri a kipufogógáz oxigéntartalmát, és ezen alapul a befecskendezés."},
2: {"correct_answer": 1, "points": 10, "explanation": "A személygépkocsik műszaki vizsgája 2 évre érvényes, kivéve az újonnan forgalomba helyezett autókat."},
3: {"correct_answer": 2, "points": 10, "explanation": "A hibrid és elektromos autók akkumulátoraiban általában lítium, nikkel és kobalt található, ólom az ólomsavas akkukban van."}
}
if question_id not in quiz_data:
raise HTTPException(status_code=404, detail="Question not found")
question_info = quiz_data[question_id]
is_correct = selected_option == question_info["correct_answer"]
# Award points if correct
if is_correct:
# Update user stats
stats_stmt = select(UserStats).where(UserStats.user_id == current_user.id)
stats_result = await db.execute(stats_stmt)
user_stats = stats_result.scalar_one_or_none()
if not user_stats:
# Create user stats if they don't exist
user_stats = UserStats(
user_id=current_user.id,
total_xp=question_info["points"],
current_level=1
)
db.add(user_stats)
else:
user_stats.total_xp += question_info["points"]
# Add points ledger entry
points_ledger = PointsLedger(
user_id=current_user.id,
points=question_info["points"],
reason=f"Daily quiz correct answer - Question {question_id}",
created_at=datetime.now()
)
db.add(points_ledger)
await db.commit()
return {
"is_correct": is_correct,
"correct_answer": question_info["correct_answer"],
"points_awarded": question_info["points"] if is_correct else 0,
"explanation": question_info["explanation"]
}
@router.post("/quiz/complete")
async def complete_daily_quiz(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
Mark daily quiz as completed for today.
This prevents the user from playing again today.
"""
today = datetime.now().date()
# Check if already completed today
stmt = select(PointsLedger).where(
PointsLedger.user_id == current_user.id,
func.date(PointsLedger.created_at) == today,
PointsLedger.reason == "Daily quiz completed"
)
result = await db.execute(stmt)
already_completed = result.scalar_one_or_none()
if already_completed:
raise HTTPException(
status_code=400,
detail="Daily quiz already marked as completed today."
)
# Add completion entry
completion_ledger = PointsLedger(
user_id=current_user.id,
points=0,
reason="Daily quiz completed",
created_at=datetime.now()
)
db.add(completion_ledger)
await db.commit()
return {"message": "Daily quiz marked as completed for today."}
@router.get("/quiz/stats")
async def get_quiz_stats(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
Get user's quiz statistics including points, streak, and last played date.
"""
# Get user stats
stats_stmt = select(UserStats).where(UserStats.user_id == current_user.id)
stats_result = await db.execute(stats_stmt)
user_stats = stats_result.scalar_one_or_none()
# Get quiz points from ledger
points_stmt = select(func.sum(PointsLedger.points)).where(
PointsLedger.user_id == current_user.id,
PointsLedger.reason.ilike("%quiz%")
)
points_result = await db.execute(points_stmt)
quiz_points = points_result.scalar() or 0
# Get last played date
last_played_stmt = select(PointsLedger.created_at).where(
PointsLedger.user_id == current_user.id,
PointsLedger.reason.ilike("%quiz%")
).order_by(desc(PointsLedger.created_at)).limit(1)
last_played_result = await db.execute(last_played_stmt)
last_played = last_played_result.scalar()
# Calculate streak (simplified - in production would be more sophisticated)
streak = 0
if last_played:
# Simple streak calculation - check last 7 days
streak = 1 # Placeholder
return {
"total_quiz_points": quiz_points,
"total_xp": user_stats.total_xp if user_stats else 0,
"current_level": user_stats.current_level if user_stats else 1,
"last_played": last_played.isoformat() if last_played else None,
"current_streak": streak,
"can_play_today": not await has_played_today(db, current_user.id)
}
async def has_played_today(db: AsyncSession, user_id: int) -> bool:
"""Check if user has already played quiz today."""
today = datetime.now().date()
stmt = select(PointsLedger).where(
PointsLedger.user_id == user_id,
func.date(PointsLedger.created_at) == today,
PointsLedger.reason.ilike("%quiz%")
)
result = await db.execute(stmt)
return result.scalar_one_or_none() is not None
# --- BADGE/TROPHY ENDPOINTS ---
@router.get("/badges")
async def get_all_badges(
db: AsyncSession = Depends(get_db)
):
"""
Get all available badges in the system.
"""
stmt = select(Badge)
result = await db.execute(stmt)
badges = result.scalars().all()
return [
{
"id": badge.id,
"name": badge.name,
"description": badge.description,
"icon_url": badge.icon_url
}
for badge in badges
]
@router.get("/my-badges")
async def get_my_badges(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
Get badges earned by the current user.
"""
stmt = (
select(UserBadge, Badge)
.join(Badge, UserBadge.badge_id == Badge.id)
.where(UserBadge.user_id == current_user.id)
.order_by(desc(UserBadge.earned_at))
)
result = await db.execute(stmt)
user_badges = result.all()
return [
{
"badge_id": badge.id,
"badge_name": badge.name,
"badge_description": badge.description,
"badge_icon_url": badge.icon_url,
"earned_at": user_badge.earned_at.isoformat() if user_badge.earned_at else None
}
for user_badge, badge in user_badges
]
@router.post("/badges/award/{badge_id}")
async def award_badge_to_user(
badge_id: int,
user_id: int = Body(None),
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
Award a badge to a user (admin only or automated system).
"""
# Check if badge exists
badge_stmt = select(Badge).where(Badge.id == badge_id)
badge_result = await db.execute(badge_stmt)
badge = badge_result.scalar_one_or_none()
if not badge:
raise HTTPException(status_code=404, detail="Badge not found")
# Determine target user (default to current user if not specified)
target_user_id = user_id if user_id else current_user.id
# Check if user already has this badge
existing_stmt = select(UserBadge).where(
UserBadge.user_id == target_user_id,
UserBadge.badge_id == badge_id
)
existing_result = await db.execute(existing_stmt)
existing = existing_result.scalar_one_or_none()
if existing:
raise HTTPException(status_code=400, detail="User already has this badge")
# Award the badge
user_badge = UserBadge(
user_id=target_user_id,
badge_id=badge_id,
earned_at=datetime.now()
)
db.add(user_badge)
# Also add points for earning a badge
points_ledger = PointsLedger(
user_id=target_user_id,
points=50, # Points for earning a badge
reason=f"Badge earned: {badge.name}",
created_at=datetime.now()
)
db.add(points_ledger)
# Update user stats
stats_stmt = select(UserStats).where(UserStats.user_id == target_user_id)
stats_result = await db.execute(stats_stmt)
user_stats = stats_result.scalar_one_or_none()
if user_stats:
user_stats.total_xp += 50
else:
user_stats = UserStats(
user_id=target_user_id,
total_xp=50,
current_level=1
)
db.add(user_stats)
await db.commit()
return {
"message": f"Badge '{badge.name}' awarded to user",
"badge_id": badge.id,
"badge_name": badge.name,
"points_awarded": 50
}
@router.get("/achievements")
async def get_achievements_progress(
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user)
):
"""
Get user's progress on various achievements (combines badges and other metrics).
"""
# Get user badges
badges_stmt = select(UserBadge.badge_id).where(UserBadge.user_id == current_user.id)
badges_result = await db.execute(badges_stmt)
user_badge_ids = [row[0] for row in badges_result.all()]
# Get all badges
all_badges_stmt = select(Badge)
all_badges_result = await db.execute(all_badges_stmt)
all_badges = all_badges_result.scalars().all()
# Get user stats
stats_stmt = select(UserStats).where(UserStats.user_id == current_user.id)
stats_result = await db.execute(stats_stmt)
user_stats = stats_result.scalar_one_or_none()
# Define achievement categories
achievements = []
# Badge-based achievements
for badge in all_badges:
achievements.append({
"id": f"badge_{badge.id}",
"title": badge.name,
"description": badge.description,
"icon_url": badge.icon_url,
"is_earned": badge.id in user_badge_ids,
"category": "badge",
"progress": 100 if badge.id in user_badge_ids else 0
})
# XP-based achievements
xp_levels = [
{"title": "Novice", "xp_required": 100, "description": "Earn 100 XP"},
{"title": "Apprentice", "xp_required": 500, "description": "Earn 500 XP"},
{"title": "Expert", "xp_required": 2000, "description": "Earn 2000 XP"},
{"title": "Master", "xp_required": 5000, "description": "Earn 5000 XP"},
]
current_xp = user_stats.total_xp if user_stats else 0
for level in xp_levels:
progress = min((current_xp / level["xp_required"]) * 100, 100)
achievements.append({
"id": f"xp_{level['xp_required']}",
"title": level["title"],
"description": level["description"],
"icon_url": None,
"is_earned": current_xp >= level["xp_required"],
"category": "xp",
"progress": progress
})
# Quiz-based achievements
quiz_points_stmt = select(func.sum(PointsLedger.points)).where(
PointsLedger.user_id == current_user.id,
PointsLedger.reason.ilike("%quiz%")
)
quiz_points_result = await db.execute(quiz_points_stmt)
quiz_points = quiz_points_result.scalar() or 0
quiz_achievements = [
{"title": "Quiz Beginner", "points_required": 50, "description": "Earn 50 quiz points"},
{"title": "Quiz Enthusiast", "points_required": 200, "description": "Earn 200 quiz points"},
{"title": "Quiz Master", "points_required": 500, "description": "Earn 500 quiz points"},
]
for achievement in quiz_achievements:
progress = min((quiz_points / achievement["points_required"]) * 100, 100)
achievements.append({
"id": f"quiz_{achievement['points_required']}",
"title": achievement["title"],
"description": achievement["description"],
"icon_url": None,
"is_earned": quiz_points >= achievement["points_required"],
"category": "quiz",
"progress": progress
})
return {
"achievements": achievements,
"total_achievements": len(achievements),
"earned_count": sum(1 for a in achievements if a["is_earned"]),
"progress_percentage": round((sum(1 for a in achievements if a["is_earned"]) / len(achievements)) * 100, 1) if achievements else 0
}

View File

@@ -37,7 +37,7 @@ async def get_monthly_trends(vehicle_id: str, db: AsyncSession = Depends(get_db)
Visszaadja az utolsó 6 hónap költéseit havi bontásban.
"""
query = text("""
SELECT
SELECT
TO_CHAR(date, 'YYYY-MM') as month,
SUM(amount) as monthly_total
FROM vehicle.vehicle_expenses
@@ -47,4 +47,19 @@ async def get_monthly_trends(vehicle_id: str, db: AsyncSession = Depends(get_db)
LIMIT 6
""")
result = await db.execute(query, {"v_id": vehicle_id})
return [dict(row._mapping) for row in result.fetchall()]
return [dict(row._mapping) for row in result.fetchall()]
@router.get("/summary/latest")
async def get_latest_summary(db: AsyncSession = Depends(get_db), current_user = Depends(get_current_user)):
"""
Returns a simple summary for the dashboard (mock data for now).
This endpoint is called by the frontend dashboard.
"""
# For now, return mock data to satisfy the frontend
return {
"total_vehicles": 4,
"total_cost_this_month": 1250.50,
"most_expensive_category": "Fuel",
"trend": "down",
"trend_percentage": -5.2
}

View File

@@ -1,10 +1,10 @@
#/opt/docker/dev/service_finder/backend/app/api/v1/endpoints/users.py
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Dict, Any
from app.api.deps import get_db, get_current_user
from app.schemas.user import UserResponse
from app.schemas.user import UserResponse, UserUpdate
from app.models.identity import User
from app.services.trust_engine import TrustEngine
@@ -41,3 +41,34 @@ async def get_user_trust(
force_recalculate=force_recalculate
)
return trust_data
@router.patch("/me/preferences", response_model=UserResponse)
async def update_user_preferences(
update_data: UserUpdate,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
"""
Update user preferences (ui_mode, preferred_language, etc.)
"""
# Filter out None values
update_dict = update_data.dict(exclude_unset=True)
if not update_dict:
raise HTTPException(status_code=400, detail="No fields to update")
# Validate ui_mode if present
if "ui_mode" in update_dict:
if update_dict["ui_mode"] not in ["personal", "fleet"]:
raise HTTPException(status_code=422, detail="ui_mode must be 'personal' or 'fleet'")
# Update user fields
for field, value in update_dict.items():
if hasattr(current_user, field):
setattr(current_user, field, value)
else:
raise HTTPException(status_code=400, detail=f"Invalid field: {field}")
await db.commit()
await db.refresh(current_user)
return current_user