Files
bonamin 34ae328b0d feat: bump client-services (accumulated feature work + deploy fixes)
Snapshot of in-progress work across local_backend, manager_dashboard,
and waiter_pwa (pricing, chat, fiscal, prep zones, recovery codes, CRM,
inventory, permissions), plus the nginx/docker-compose deploy fixes for
the Unraid + NPM reverse-proxy setup.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-19 10:00:14 +03:00

124 lines
4.3 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from pydantic import BaseModel, EmailStr
from typing import Optional
import bcrypt
from database import get_db
from models.user import User
from services.chat_service import add_user_to_system_group
router = APIRouter()
class SetupStatusResponse(BaseModel):
needs_setup: bool
class SetupInitRequest(BaseModel):
username: str
password: str
email: Optional[str] = None
full_name: Optional[str] = None
venue_type: Optional[str] = None
venue_name: Optional[str] = None
pin: Optional[str] = None
class SetupInitResponse(BaseModel):
ok: bool
class SecurityConfigResponse(BaseModel):
login_method: str
autofill_username: bool
@router.get("/security-config", response_model=SecurityConfigResponse)
def security_config(db: Session = Depends(get_db)):
"""Public endpoint — returns only the security settings needed by the login page."""
from models.settings import PosSettings
rows = {r.key: r.value for r in db.query(PosSettings).filter(
PosSettings.key.in_(["security.login_method", "security.autofill_username"])
).all()}
return SecurityConfigResponse(
login_method=rows.get("security.login_method", "password"),
autofill_username=rows.get("security.autofill_username", "true") == "true",
)
@router.get("/status", response_model=SetupStatusResponse)
def setup_status(db: Session = Depends(get_db)):
has_manager = db.query(User).filter(
User.perm_access_dashboard == True,
User.is_active == True,
).first()
return SetupStatusResponse(needs_setup=has_manager is None)
@router.post("/init", response_model=SetupInitResponse)
def setup_init(body: SetupInitRequest, db: Session = Depends(get_db)):
has_manager = db.query(User).filter(
User.perm_access_dashboard == True,
User.is_active == True,
).first()
if has_manager:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Setup already completed — a manager account already exists.",
)
if not body.username.strip():
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Username is required.")
if len(body.password) < 6:
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail="Password must be at least 6 characters.")
existing = db.query(User).filter(User.username == body.username.strip()).first()
if existing:
raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="Username already taken.")
password_hash = bcrypt.hashpw(body.password.encode(), bcrypt.gensalt()).decode()
raw_pin = body.pin if (body.pin and body.pin.isdigit() and 4 <= len(body.pin) <= 6) else "0000"
pin_hash = bcrypt.hashpw(raw_pin.encode(), bcrypt.gensalt()).decode()
user = User(
username=body.username.strip(),
pin_hash=pin_hash,
password_hash=password_hash,
email=body.email,
full_name=body.full_name,
role="store_manager",
is_active=True,
)
# Apply store_manager default permissions
from roles import get_default_permissions
for field, value in get_default_permissions("store_manager").items():
setattr(user, field, value)
db.add(user)
# Persist venue settings if provided
if body.venue_name or body.venue_type:
from models.settings import PosSettings
from datetime import datetime, timezone
now = datetime.now(timezone.utc)
if body.venue_name:
setting = db.query(PosSettings).filter(PosSettings.key == "venue.name").first()
if setting:
setting.value = body.venue_name
setting.updated_at = now
else:
db.add(PosSettings(key="venue.name", value=body.venue_name, updated_at=now))
if body.venue_type:
setting = db.query(PosSettings).filter(PosSettings.key == "venue.type").first()
if setting:
setting.value = body.venue_type
setting.updated_at = now
else:
db.add(PosSettings(key="venue.type", value=body.venue_type, updated_at=now))
db.commit()
db.refresh(user)
add_user_to_system_group(db, user.id)
return SetupInitResponse(ok=True)