feat: silent wechat login, marketing tab optimization, admin page foundation

- Add silent WeChat login for MP/browser environments
- Fix Python 3.6 compatibility (remove typing.Annotated usage)
- Marketing page: tab-based content generation with category support
- Translate page: add auto-detect language default
- Homepage: add TTS playback, announcement ticker, remove redundant quick-actions
- Fix FAB button overlap with custom tabbar on customers/quotation pages
- Make openai/anthropic imports lazy for Python 3.6 compat
This commit is contained in:
TradeMate Dev
2026-05-14 00:30:48 +08:00
parent f70dd24c7d
commit 23a31f7c00
30 changed files with 485 additions and 269 deletions
BIN
View File
Binary file not shown.
+7 -1
View File
@@ -1,6 +1,5 @@
from typing import Dict, Any, Optional
import json
from anthropic import AsyncAnthropic
from app.ai.base import AIProvider
@@ -19,6 +18,13 @@ SYSTEM_PROMPTS = {
class ClaudeProvider(AIProvider):
def __init__(self, api_key: str, model: str = "claude-sonnet-4-20250514"):
try:
from anthropic import AsyncAnthropic
except ImportError:
raise ImportError(
"anthropic SDK is required for ClaudeProvider. "
"Install it with: pip install anthropic"
)
self.client = AsyncAnthropic(api_key=api_key)
self.model = model
self._name = f"claude-sonnet"
+7 -1
View File
@@ -1,6 +1,5 @@
from typing import Dict, Any, Optional
import json
from openai import AsyncOpenAI
from app.ai.base import AIProvider
@@ -20,6 +19,13 @@ SYSTEM_PROMPTS = {
class OpenAIProvider(AIProvider):
def __init__(self, api_key: str, model: str = "gpt-4o", base_url: Optional[str] = None):
try:
from openai import AsyncOpenAI
except ImportError:
raise ImportError(
"openai>=1.0 is required for OpenAIProvider. "
"Install it with: pip install 'openai>=1.0'"
)
kwargs = {"api_key": api_key}
if base_url:
kwargs["base_url"] = base_url
+4 -1
View File
@@ -1,6 +1,5 @@
from typing import Dict, Any, Optional
import json
from openai import AsyncOpenAI
from app.ai.base import AIProvider
@@ -18,6 +17,10 @@ SYSTEM_PROMPTS = {
class SparkProvider(AIProvider):
def __init__(self, api_key: str, model: str = "astron-code-latest", base_url: str = None):
from app.config import settings
try:
from openai import AsyncOpenAI
except ImportError:
raise ImportError("openai>=1.0 is required for SparkProvider")
self.client = AsyncOpenAI(
api_key=api_key,
base_url=base_url or settings.IFLYTEK_API_BASE,
+5 -6
View File
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated
from app.database import get_db
from app.services.admin import AdminService
from app.api.v1.deps import get_current_user
@@ -17,7 +16,7 @@ async def require_admin(current_user: dict = Depends(get_current_user)) -> dict:
@router.get("/dashboard")
async def get_dashboard(
_: dict = Depends(require_admin),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
return await service.get_dashboard()
@@ -28,7 +27,7 @@ async def list_users(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
_: dict = Depends(require_admin),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
return await service.list_users(page, size)
@@ -39,7 +38,7 @@ async def update_user_tier(
target_user_id: str,
data: dict,
_: dict = Depends(require_admin),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
tier = data.get("tier")
@@ -55,7 +54,7 @@ async def update_user_tier(
async def toggle_user_active(
target_user_id: str,
_: dict = Depends(require_admin),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
success = await service.toggle_user_active(target_user_id)
@@ -66,7 +65,7 @@ async def toggle_user_active(
@router.get("/health")
async def system_health(
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = AdminService(db)
return await service.get_system_health()
+6 -7
View File
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated
from app.database import get_db
from app.services.analytics import AnalyticsService
from app.api.v1.deps import get_current_user_id
@@ -11,7 +10,7 @@ router = APIRouter()
@router.get("/customers")
async def customer_analytics(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = AnalyticsService(db)
return await service.get_customer_stats(user_id)
@@ -20,7 +19,7 @@ async def customer_analytics(
@router.get("/translations")
async def translation_analytics(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = AnalyticsService(db)
return await service.get_translation_stats(user_id)
@@ -29,7 +28,7 @@ async def translation_analytics(
@router.get("/quotations")
async def quotation_analytics(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = AnalyticsService(db)
return await service.get_quotation_stats(user_id)
@@ -38,7 +37,7 @@ async def quotation_analytics(
@router.get("/messages")
async def message_analytics(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = AnalyticsService(db)
return await service.get_message_stats(user_id)
@@ -47,7 +46,7 @@ async def message_analytics(
@router.get("/overview")
async def overview(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = AnalyticsService(db)
customers = await service.get_customer_stats(user_id)
@@ -67,7 +66,7 @@ async def overview(
@router.get("/marketing")
async def marketing_analytics(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = AnalyticsService(db)
return await service.get_marketing_stats(user_id)
+16 -7
View File
@@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, status, Header
from fastapi.security import OAuth2PasswordRequestForm
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from typing import Annotated, Optional
from typing import Optional
import uuid
from app.database import get_db
from app.models.user import User
@@ -31,7 +31,7 @@ class RefreshRequest(BaseModel):
@router.post("/register")
async def register(data: RegisterRequest, db: Annotated[AsyncSession, Depends(get_db)] = None):
async def register(data: RegisterRequest, db: AsyncSession = Depends(get_db)):
existing = await db.execute(select(User).where(User.phone == data.phone))
if existing.scalar_one_or_none():
raise HTTPException(status_code=400, detail="Phone already registered")
@@ -56,8 +56,8 @@ async def register(data: RegisterRequest, db: Annotated[AsyncSession, Depends(ge
@router.post("/login", response_model=LoginResponse)
async def login(
form: Annotated[OAuth2PasswordRequestForm, Depends()],
db: Annotated[AsyncSession, Depends(get_db)] = None,
form: OAuth2PasswordRequestForm = Depends(),
db: AsyncSession = Depends(get_db),
):
result = await db.execute(select(User).where(User.phone == form.username))
user = result.scalar_one_or_none()
@@ -128,7 +128,7 @@ async def refresh(data: RefreshRequest):
@router.get("/me")
async def get_me(
authorization: Optional[str] = Header(None, alias="Authorization"),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing token")
@@ -178,8 +178,17 @@ class WeChatLoginRequest(BaseModel):
iv: str = ""
@router.get("/wechat/config")
async def wechat_config():
from app.config import settings
return {
"available": bool(settings.WECHAT_APP_ID and settings.WECHAT_APP_SECRET),
"app_id": settings.WECHAT_APP_ID or "",
}
@router.post("/wechat-login")
async def wechat_login(data: WeChatLoginRequest, db: Annotated[AsyncSession, Depends(get_db)] = None):
async def wechat_login(data: WeChatLoginRequest, db: AsyncSession = Depends(get_db)):
from app.services.wechat import wechat_service
session = await wechat_service.code2session(data.code)
@@ -216,7 +225,7 @@ async def wechat_login(data: WeChatLoginRequest, db: Annotated[AsyncSession, Dep
async def update_settings(
data: SettingsUpdate,
authorization: Optional[str] = Header(None, alias="Authorization"),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="Missing token")
+13 -13
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException, Query, UploadFile, File, Response
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated, Optional, List
from typing import Optional, List
from app.database import get_db
from app.services.customer import CustomerService
from app.services.customer_health import CustomerHealthService
@@ -18,7 +18,7 @@ async def list_customers(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
return await service.list_customers(user_id, status, page, size)
@@ -28,7 +28,7 @@ async def list_customers(
async def get_silent(
days: int = Query(3, ge=1),
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
customers = await service.get_silent_customers(user_id, days)
@@ -43,7 +43,7 @@ async def get_silent(
async def get_customer(
customer_id: str,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
customer = await service.get_customer(user_id, customer_id)
@@ -56,7 +56,7 @@ async def get_customer(
async def create_customer(
data: dict,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
customer = await service.create_customer(user_id, data)
@@ -68,7 +68,7 @@ async def update_customer(
customer_id: str,
data: dict,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
customer = await service.update_customer(user_id, customer_id, data)
@@ -81,7 +81,7 @@ async def update_customer(
async def delete_customer(
customer_id: str,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
deleted = await service.delete_customer(user_id, customer_id)
@@ -94,7 +94,7 @@ async def delete_customer(
async def import_customers(
file: UploadFile = File(...),
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
from app.workers.tasks import process_customer_import
@@ -135,7 +135,7 @@ async def import_customers(
async def export_customers(
status: Optional[str] = None,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
result = await service.list_customers(user_id, status, 1, 9999)
@@ -151,7 +151,7 @@ async def export_customers(
@router.get("/health-overview")
async def get_health_overview(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = CustomerHealthService(db)
return await service.get_health_overview(user_id)
@@ -160,7 +160,7 @@ async def get_health_overview(
@router.get("/health-scores")
async def get_all_health_scores(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = CustomerHealthService(db)
return {"items": await service.get_all_health_scores(user_id)}
@@ -170,7 +170,7 @@ async def get_all_health_scores(
async def get_customer_health(
customer_id: str,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = CustomerHealthService(db)
health = await service.get_customer_health(user_id, customer_id)
@@ -185,7 +185,7 @@ async def get_conversation(
page: int = Query(1, ge=1),
size: int = Query(50, ge=1, le=200),
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = CustomerService(db)
return await service.get_conversation(user_id, customer_id, page, size)
+1 -2
View File
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated
from pydantic import BaseModel
from app.database import get_db
from app.models.feedback import Feedback
@@ -19,7 +18,7 @@ class FeedbackRequest(BaseModel):
async def submit_feedback(
data: FeedbackRequest,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
if not data.content.strip():
raise HTTPException(status_code=400, detail="Content is required")
+8 -8
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated, Optional
from typing import Optional
from app.database import get_db
from app.services.followup_engine import FollowupEngine
from app.api.v1.deps import get_current_user_id
@@ -11,7 +11,7 @@ router = APIRouter()
@router.get("/strategies")
async def list_strategies(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
engine = FollowupEngine(db)
await engine.ensure_default_strategies()
@@ -23,7 +23,7 @@ async def get_pending_followups(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
engine = FollowupEngine(db)
return await engine.get_pending_followups(user_id, page, size)
@@ -34,7 +34,7 @@ async def get_followup_logs(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
engine = FollowupEngine(db)
return await engine.get_followup_logs(user_id, page, size)
@@ -44,7 +44,7 @@ async def get_followup_logs(
async def mark_followup_sent(
log_id: str,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
engine = FollowupEngine(db)
success = await engine.mark_sent(user_id, log_id)
@@ -58,7 +58,7 @@ async def edit_and_send_followup(
log_id: str,
body: dict,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
edited_text = body.get("edited_text", "")
if not edited_text:
@@ -73,7 +73,7 @@ async def edit_and_send_followup(
@router.get("/stats")
async def get_followup_stats(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
engine = FollowupEngine(db)
return await engine.get_stats(user_id)
@@ -82,7 +82,7 @@ async def get_followup_stats(
@router.post("/scan")
async def trigger_followup_scan(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
engine = FollowupEngine(db)
result = await engine.scan_and_followup()
+7 -8
View File
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated
from app.database import get_db
from app.services.preference import UserPreferenceService
from app.services.marketing_effect import MarketingEffectService
@@ -13,7 +12,7 @@ router = APIRouter()
async def record_selection(
data: dict,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
message_id = data.get("message_id")
selected_index = data.get("selected_index")
@@ -30,7 +29,7 @@ async def record_selection(
async def record_edit(
data: dict,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
message_id = data.get("message_id")
edited_text = data.get("edited_text")
@@ -46,7 +45,7 @@ async def record_edit(
@router.post("/analyze")
async def analyze_preferences(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = UserPreferenceService(db)
preferences = await service.analyze_preferences(user_id)
@@ -56,7 +55,7 @@ async def analyze_preferences(
@router.get("/preferences")
async def get_preferences(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = UserPreferenceService(db)
return await service.get_analysis(user_id)
@@ -66,7 +65,7 @@ async def get_preferences(
async def track_marketing_effect(
data: dict,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = MarketingEffectService(db)
result = await service.track_event(
@@ -87,7 +86,7 @@ async def get_marketing_effects(
page: int = 1,
size: int = 20,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = MarketingEffectService(db)
return await service.get_effects(user_id, page, size)
@@ -96,7 +95,7 @@ async def get_marketing_effects(
@router.get("/marketing-effects/stats")
async def get_marketing_effect_stats(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = MarketingEffectService(db)
return await service.get_stats(user_id)
+2 -2
View File
@@ -1,5 +1,5 @@
from fastapi import APIRouter, HTTPException, Depends
from typing import Optional, Annotated
from typing import Optional
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
@@ -43,7 +43,7 @@ class CompetitorRequest(BaseModel):
async def generate_marketing(
data: MarketingRequest,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = MarketingService()
pref_service = UserPreferenceService(db)
+6 -6
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated, Optional
from typing import Optional
from app.database import get_db
from app.services.notification import NotificationService
from app.api.v1.deps import get_current_user_id
@@ -14,7 +14,7 @@ async def list_notifications(
size: int = Query(20, ge=1, le=100),
unread_only: bool = Query(False),
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = NotificationService(db)
return await service.list_notifications(user_id, page, size, unread_only)
@@ -23,7 +23,7 @@ async def list_notifications(
@router.get("/unread-count")
async def unread_count(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = NotificationService(db)
count = await service.get_unread_count(user_id)
@@ -34,7 +34,7 @@ async def unread_count(
async def mark_read(
notification_id: str,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = NotificationService(db)
success = await service.mark_read(user_id, notification_id)
@@ -46,7 +46,7 @@ async def mark_read(
@router.post("/read-all")
async def mark_all_read(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = NotificationService(db)
count = await service.mark_all_read(user_id)
@@ -57,7 +57,7 @@ async def mark_all_read(
async def delete_notification(
notification_id: str,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = NotificationService(db)
success = await service.delete_notification(user_id, notification_id)
+2 -3
View File
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated
from pydantic import BaseModel
from app.database import get_db
from app.services.onboarding import OnboardingService
@@ -19,7 +18,7 @@ class OnboardingRequest(BaseModel):
@router.get("/status")
async def get_status(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = OnboardingService(db)
return await service.check_status(user_id)
@@ -29,7 +28,7 @@ async def get_status(
async def create_first_product(
data: OnboardingRequest,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
if not data.name.strip():
raise HTTPException(status_code=400, detail="Product name is required")
+3 -4
View File
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated
from pydantic import BaseModel
from app.database import get_db
from app.services.payment import PaymentService
@@ -27,7 +26,7 @@ async def get_plans():
@router.get("/subscription")
async def get_subscription(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
svc = PaymentService(db)
return await svc.get_current_subscription(user_id)
@@ -37,7 +36,7 @@ async def get_subscription(
async def create_order(
data: CreateOrderRequest,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
svc = PaymentService(db)
try:
@@ -49,7 +48,7 @@ async def create_order(
@router.post("/callback")
async def payment_callback(
data: PaymentCallbackRequest,
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
svc = PaymentService(db)
success = await svc.handle_payment_callback(data.payment_id, data.success)
+6 -6
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated, Optional
from typing import Optional
from app.database import get_db
from app.services.product import ProductService
from app.api.v1.deps import get_current_user_id
@@ -44,7 +44,7 @@ async def list_products(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = ProductService(db)
return await service.list_products(user_id, category, page, size)
@@ -54,7 +54,7 @@ async def list_products(
async def get_product(
product_id: str,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = ProductService(db)
product = await service.get_product(user_id, product_id)
@@ -67,7 +67,7 @@ async def get_product(
async def create_product(
data: ProductCreate,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = ProductService(db)
product = await service.create_product(user_id, data.dict())
@@ -79,7 +79,7 @@ async def update_product(
product_id: str,
data: ProductUpdate,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = ProductService(db)
product = await service.update_product(user_id, product_id, data.dict(exclude_unset=True))
@@ -92,7 +92,7 @@ async def update_product(
async def delete_product(
product_id: str,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = ProductService(db)
deleted = await service.delete_product(user_id, product_id)
+4 -4
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated, Optional
from typing import Optional
from pydantic import BaseModel
from app.database import get_db
from app.services.push import PushService
@@ -20,7 +20,7 @@ class DeviceRegisterRequest(BaseModel):
async def register_device(
data: DeviceRegisterRequest,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = PushService(db)
device = await service.register_device(
@@ -41,7 +41,7 @@ async def register_device(
async def unregister_device(
data: dict,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
client_id = data.get("client_id")
if not client_id:
@@ -56,7 +56,7 @@ async def unregister_device(
@router.get("/devices")
async def list_devices(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = PushService(db)
devices = await service.get_user_devices(user_id)
+8 -8
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException, Query, Response
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated, Optional
from typing import Optional
from pydantic import BaseModel
from app.database import get_db
from app.services.quotation import QuotationService
@@ -23,7 +23,7 @@ class InquiryRequest(BaseModel):
async def generate_from_inquiry(
data: InquiryRequest,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = QuotationService(db)
result = await service.generate_from_inquiry(
@@ -38,7 +38,7 @@ async def generate_from_inquiry(
async def create_quotation(
data: dict,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = QuotationService(db)
try:
@@ -53,7 +53,7 @@ async def list_quotations(
page: int = Query(1, ge=1),
size: int = Query(20, ge=1, le=100),
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = QuotationService(db)
return await service.list_quotations(user_id, page, size)
@@ -63,7 +63,7 @@ async def list_quotations(
async def get_quotation(
quotation_id: str,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = QuotationService(db)
quotation = await service.get_quotation(user_id, quotation_id)
@@ -77,7 +77,7 @@ async def update_quotation_status(
quotation_id: str,
data: dict,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = QuotationService(db)
quotation = await service.update_status(user_id, quotation_id, data.get("status", "draft"))
@@ -89,7 +89,7 @@ async def update_quotation_status(
@router.get("/export/csv")
async def export_quotations(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = QuotationService(db)
result = await service.list_quotations(user_id, 1, 9999)
@@ -106,7 +106,7 @@ async def export_quotations(
async def export_quotation_pdf(
quotation_id: str,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = QuotationService(db)
quotation = await service.get_quotation(user_id, quotation_id)
+2 -3
View File
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated
from app.database import get_db
from app.services.silent_pattern import SilentPatternService
from app.api.v1.deps import get_current_user_id
@@ -11,7 +10,7 @@ router = APIRouter()
@router.get("/risk-analysis")
async def get_silent_risk_analysis(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = SilentPatternService(db)
risks = await service.analyze_silent_risk(user_id)
@@ -27,7 +26,7 @@ async def get_silent_risk_analysis(
async def get_followup_suggestions(
customer_id: str,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = SilentPatternService(db)
suggestions = await service.get_suggestions(user_id, customer_id)
+8 -8
View File
@@ -1,6 +1,6 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated, Optional
from typing import Optional
from pydantic import BaseModel
from app.database import get_db
from app.services.team import TeamService
@@ -26,7 +26,7 @@ class UpdateRoleRequest(BaseModel):
async def create_team(
data: CreateTeamRequest,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = TeamService(db)
try:
@@ -39,7 +39,7 @@ async def create_team(
@router.get("")
async def list_teams(
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = TeamService(db)
return {"teams": await service.list_user_teams(user_id)}
@@ -49,7 +49,7 @@ async def list_teams(
async def get_team(
team_id: str,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = TeamService(db)
team = await service.get_team(team_id, user_id)
@@ -63,7 +63,7 @@ async def invite_member(
team_id: str,
data: InviteRequest,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = TeamService(db)
try:
@@ -78,7 +78,7 @@ async def remove_member(
team_id: str,
member_id: str,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = TeamService(db)
success = await service.remove_member(team_id, user_id, member_id)
@@ -91,7 +91,7 @@ async def remove_member(
async def leave_team(
team_id: str,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = TeamService(db)
success = await service.leave_team(team_id, user_id)
@@ -106,7 +106,7 @@ async def update_member_role(
member_id: str,
data: UpdateRoleRequest,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
service = TeamService(db)
if data.role not in ("admin", "member", "viewer"):
+4 -5
View File
@@ -1,6 +1,5 @@
from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession
from typing import Annotated
from app.database import get_db
from app.services.corpus_trainer import CorpusTrainer
from app.api.v1.deps import get_current_user_id
@@ -10,7 +9,7 @@ router = APIRouter()
@router.post("/corpus/run")
async def run_corpus_training(
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
trainer = CorpusTrainer(db)
result = await trainer.run_pipeline()
@@ -20,7 +19,7 @@ async def run_corpus_training(
@router.post("/corpus/embeddings")
async def compute_embeddings(
batch_size: int = 50,
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
trainer = CorpusTrainer(db)
result = await trainer.compute_embeddings(batch_size)
@@ -29,7 +28,7 @@ async def compute_embeddings(
@router.get("/corpus/stats")
async def corpus_stats(
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
trainer = CorpusTrainer(db)
return await trainer.get_stats()
@@ -37,7 +36,7 @@ async def corpus_stats(
@router.post("/corpus/deduplicate")
async def deduplicate_corpus(
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
trainer = CorpusTrainer(db)
result = await trainer.deduplicate()
+2 -2
View File
@@ -1,5 +1,5 @@
from fastapi import APIRouter, HTTPException, Response, Depends
from typing import Optional, Dict, Any, Annotated
from typing import Optional, Dict, Any
from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
@@ -51,7 +51,7 @@ async def translate_text(
async def generate_reply(
data: ReplyRequest,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
pref_service = UserPreferenceService(db)
pref_context = await pref_service.get_preference_context(user_id, "reply")
+3 -3
View File
@@ -1,7 +1,7 @@
from fastapi import APIRouter, Request, HTTPException, Depends, Header
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, and_
from typing import Annotated, Optional
from typing import Optional
from pydantic import BaseModel
from app.database import get_db
from app.services.whatsapp import WhatsAppService
@@ -30,7 +30,7 @@ async def verify_webhook(
async def handle_webhook(
request: Request,
x_hub_signature_256: Optional[str] = Header(None),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
svc = WhatsAppService()
body = await request.body()
@@ -80,7 +80,7 @@ class SendMessageRequest(BaseModel):
async def send_message(
data: SendMessageRequest,
user_id: str = Depends(get_current_user_id),
db: Annotated[AsyncSession, Depends(get_db)] = None,
db: AsyncSession = Depends(get_db),
):
svc = WhatsAppService()