|
@@ -4,6 +4,7 @@ from datetime import datetime
|
|
|
from typing import List, Optional
|
|
from typing import List, Optional
|
|
|
|
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
from fastapi import APIRouter, HTTPException
|
|
|
|
|
+from fastapi.responses import Response
|
|
|
from pydantic import BaseModel, Field
|
|
from pydantic import BaseModel, Field
|
|
|
|
|
|
|
|
from app.db import get_pool
|
|
from app.db import get_pool
|
|
@@ -11,6 +12,20 @@ from app.db import get_pool
|
|
|
router = APIRouter(tags=["discounts"])
|
|
router = APIRouter(tags=["discounts"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+async def _bump_domain_version(conn, domain: str) -> None:
|
|
|
|
|
+ """Upsert domain_version: insert with version=1 for new domains, increment for existing."""
|
|
|
|
|
+ await conn.execute(
|
|
|
|
|
+ """
|
|
|
|
|
+ INSERT INTO domain_version (domain, version, updated_at)
|
|
|
|
|
+ VALUES ($1, 1, NOW())
|
|
|
|
|
+ ON CONFLICT (domain) DO UPDATE
|
|
|
|
|
+ SET version = domain_version.version + 1,
|
|
|
|
|
+ updated_at = NOW()
|
|
|
|
|
+ """,
|
|
|
|
|
+ domain,
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
class DiscountIn(BaseModel):
|
|
class DiscountIn(BaseModel):
|
|
|
domain: str
|
|
domain: str
|
|
|
discount: float = Field(..., gt=0, le=1, description="折扣系数,如 0.8 表示八折")
|
|
discount: float = Field(..., gt=0, le=1, description="折扣系数,如 0.8 表示八折")
|
|
@@ -36,39 +51,53 @@ async def list_discounts() -> List[DiscountOut]:
|
|
|
@router.post("/discounts", response_model=DiscountOut, status_code=201)
|
|
@router.post("/discounts", response_model=DiscountOut, status_code=201)
|
|
|
async def create_discount(body: DiscountIn) -> DiscountOut:
|
|
async def create_discount(body: DiscountIn) -> DiscountOut:
|
|
|
pool = get_pool()
|
|
pool = get_pool()
|
|
|
- row = await pool.fetchrow(
|
|
|
|
|
- """
|
|
|
|
|
- INSERT INTO discounts (domain, discount, note)
|
|
|
|
|
- VALUES ($1, $2, $3)
|
|
|
|
|
- ON CONFLICT (domain) DO UPDATE
|
|
|
|
|
- SET discount = EXCLUDED.discount,
|
|
|
|
|
- note = EXCLUDED.note,
|
|
|
|
|
- updated_at = NOW()
|
|
|
|
|
- RETURNING *
|
|
|
|
|
- """,
|
|
|
|
|
- body.domain, body.discount, body.note,
|
|
|
|
|
- )
|
|
|
|
|
|
|
+ async with pool.acquire() as conn:
|
|
|
|
|
+ async with conn.transaction():
|
|
|
|
|
+ row = await conn.fetchrow(
|
|
|
|
|
+ """
|
|
|
|
|
+ INSERT INTO discounts (domain, discount, note)
|
|
|
|
|
+ VALUES ($1, $2, $3)
|
|
|
|
|
+ ON CONFLICT (domain) DO UPDATE
|
|
|
|
|
+ SET discount = EXCLUDED.discount,
|
|
|
|
|
+ note = EXCLUDED.note,
|
|
|
|
|
+ updated_at = NOW()
|
|
|
|
|
+ RETURNING *
|
|
|
|
|
+ """,
|
|
|
|
|
+ body.domain, body.discount, body.note,
|
|
|
|
|
+ )
|
|
|
|
|
+ await _bump_domain_version(conn, body.domain)
|
|
|
return DiscountOut(**dict(row))
|
|
return DiscountOut(**dict(row))
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/discounts/{discount_id}", response_model=DiscountOut)
|
|
@router.put("/discounts/{discount_id}", response_model=DiscountOut)
|
|
|
async def update_discount(discount_id: int, body: DiscountIn) -> DiscountOut:
|
|
async def update_discount(discount_id: int, body: DiscountIn) -> DiscountOut:
|
|
|
pool = get_pool()
|
|
pool = get_pool()
|
|
|
- row = await pool.fetchrow(
|
|
|
|
|
- """
|
|
|
|
|
- UPDATE discounts SET domain=$1, discount=$2, note=$3, updated_at=NOW()
|
|
|
|
|
- WHERE id=$4 RETURNING *
|
|
|
|
|
- """,
|
|
|
|
|
- body.domain, body.discount, body.note, discount_id,
|
|
|
|
|
- )
|
|
|
|
|
- if not row:
|
|
|
|
|
- raise HTTPException(status_code=404, detail="不存在")
|
|
|
|
|
|
|
+ async with pool.acquire() as conn:
|
|
|
|
|
+ async with conn.transaction():
|
|
|
|
|
+ row = await conn.fetchrow(
|
|
|
|
|
+ """
|
|
|
|
|
+ UPDATE discounts SET domain=$1, discount=$2, note=$3, updated_at=NOW()
|
|
|
|
|
+ WHERE id=$4 RETURNING *
|
|
|
|
|
+ """,
|
|
|
|
|
+ body.domain, body.discount, body.note, discount_id,
|
|
|
|
|
+ )
|
|
|
|
|
+ if not row:
|
|
|
|
|
+ raise HTTPException(status_code=404, detail="不存在")
|
|
|
|
|
+ await _bump_domain_version(conn, body.domain)
|
|
|
return DiscountOut(**dict(row))
|
|
return DiscountOut(**dict(row))
|
|
|
|
|
|
|
|
|
|
|
|
|
-@router.delete("/discounts/{discount_id}", status_code=204)
|
|
|
|
|
-async def delete_discount(discount_id: int) -> None:
|
|
|
|
|
|
|
+@router.delete("/discounts/{discount_id}", status_code=204, response_model=None)
|
|
|
|
|
+async def delete_discount(discount_id: int) -> Response:
|
|
|
pool = get_pool()
|
|
pool = get_pool()
|
|
|
- result = await pool.execute("DELETE FROM discounts WHERE id=$1", discount_id)
|
|
|
|
|
- if result == "DELETE 0":
|
|
|
|
|
- raise HTTPException(status_code=404, detail="不存在")
|
|
|
|
|
|
|
+ async with pool.acquire() as conn:
|
|
|
|
|
+ async with conn.transaction():
|
|
|
|
|
+ # 先查出 domain,再删除,再 bump 版本
|
|
|
|
|
+ existing = await conn.fetchrow("SELECT domain FROM discounts WHERE id=$1", discount_id)
|
|
|
|
|
+ if not existing:
|
|
|
|
|
+ raise HTTPException(status_code=404, detail="不存在")
|
|
|
|
|
+ result = await conn.execute("DELETE FROM discounts WHERE id=$1", discount_id)
|
|
|
|
|
+ if result == "DELETE 0":
|
|
|
|
|
+ raise HTTPException(status_code=404, detail="不存在")
|
|
|
|
|
+ await conn.execute("DELETE FROM domain_version WHERE domain=$1", existing["domain"])
|
|
|
|
|
+ return Response(status_code=204)
|