Backend:
- New /api/clients CRUD (create, list, delete, rename)
- dropdowns.py: _load_dropdowns(client_id) — per-client file first, global fallback
- admin.py: per-client dropdown upload/preview/delete endpoints
- ai_command.py: reads sheet's client_id, builds hierarchy from client-specific file
- sheets/manager.py: client_id stored in sheet metadata; get/set_sheet_client_id helpers
- sheets.py: create sheet accepts client_id; PATCH /{id}/client endpoint
- config_runtime.py: CLIENTS_FILE, CLIENTS_DROPDOWNS_DIR, ADMIN_EMAILS list
- user_store.py: bootstrap admin from ADMIN_EMAILS (daveporter + vadymsamoilenko)
Frontend:
- New Client type; SheetMeta gains client_id
- api/clients.ts, stores/useClientStore.ts — client CRUD
- useDropdownStore: re-fetches when client changes (no stale cache)
- SheetPage: client selector in header; fetches per-client categories
- BriefUploadPage: client selector before upload
- AdminClientsPage: create/delete clients, upload per-client .xlsx, preview before apply
- Sidebar: separate admin nav links (Users / Clients / Dropdowns)
- App.tsx: /admin/clients route
Data:
- 4 clients pre-seeded (Adidas, USTUDIO, 3M Colab, Bissell) with custom hierarchy files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
103 lines
2.7 KiB
Python
103 lines
2.7 KiB
Python
"""
|
|
Client management API.
|
|
Clients group sheets and have their own dropdown (category/media) hierarchy.
|
|
"""
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
import random
|
|
import time
|
|
from datetime import datetime, timezone
|
|
|
|
from quart import Blueprint, jsonify, request
|
|
|
|
from ..auth.middleware import auth_required, admin_required
|
|
from ..config_runtime import server_config
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
clients_bp = Blueprint('clients', __name__, url_prefix='/api/clients')
|
|
|
|
|
|
def load_clients() -> list:
|
|
path = server_config.CLIENTS_FILE
|
|
if not os.path.exists(path):
|
|
return []
|
|
try:
|
|
with open(path, 'r') as f:
|
|
return json.load(f)
|
|
except Exception:
|
|
return []
|
|
|
|
|
|
def _save_clients(clients: list):
|
|
with open(server_config.CLIENTS_FILE, 'w') as f:
|
|
json.dump(clients, f, indent=2)
|
|
|
|
|
|
def get_client_by_id(client_id: str) -> dict | None:
|
|
for c in load_clients():
|
|
if c['id'] == client_id:
|
|
return c
|
|
return None
|
|
|
|
|
|
@clients_bp.route('', methods=['GET'])
|
|
@auth_required
|
|
async def list_clients():
|
|
return jsonify({'clients': load_clients()})
|
|
|
|
|
|
@clients_bp.route('', methods=['POST'])
|
|
@admin_required
|
|
async def create_client():
|
|
body = await request.get_json() or {}
|
|
name = body.get('name', '').strip()
|
|
if not name:
|
|
return jsonify({'error': 'name_required', 'message': 'Client name is required'}), 400
|
|
|
|
client_id = f"client_{int(time.time())}{random.randint(100, 999)}"
|
|
client = {
|
|
'id': client_id,
|
|
'name': name,
|
|
'created': datetime.now(timezone.utc).isoformat(),
|
|
'hasCustomDropdowns': False,
|
|
}
|
|
clients = load_clients()
|
|
clients.append(client)
|
|
_save_clients(clients)
|
|
return jsonify({'client': client}), 201
|
|
|
|
|
|
@clients_bp.route('/<client_id>', methods=['DELETE'])
|
|
@admin_required
|
|
async def delete_client(client_id: str):
|
|
clients = load_clients()
|
|
clients = [c for c in clients if c['id'] != client_id]
|
|
_save_clients(clients)
|
|
|
|
# Remove client-specific dropdown file if present
|
|
dropdown_path = os.path.join(server_config.CLIENTS_DROPDOWNS_DIR, f"{client_id}.json")
|
|
if os.path.exists(dropdown_path):
|
|
os.remove(dropdown_path)
|
|
|
|
return jsonify({'success': True})
|
|
|
|
|
|
@clients_bp.route('/<client_id>', methods=['PATCH'])
|
|
@admin_required
|
|
async def update_client(client_id: str):
|
|
body = await request.get_json() or {}
|
|
clients = load_clients()
|
|
updated = None
|
|
for c in clients:
|
|
if c['id'] == client_id:
|
|
if 'name' in body:
|
|
c['name'] = body['name'].strip()
|
|
updated = c
|
|
break
|
|
if not updated:
|
|
return jsonify({'error': 'not_found'}), 404
|
|
_save_clients(clients)
|
|
return jsonify({'client': updated})
|