Update src/main.py
Browse files- src/main.py +28 -14
src/main.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
|
|
| 1 |
import os
|
| 2 |
from fastapi import FastAPI, HTTPException, Response, Request
|
| 3 |
from fastapi.middleware.cors import CORSMiddleware
|
| 4 |
from fastapi.staticfiles import StaticFiles
|
| 5 |
-
from fastapi.responses import
|
| 6 |
from sqlmodel import select
|
| 7 |
from dotenv import load_dotenv
|
| 8 |
|
|
@@ -19,6 +20,7 @@ load_dotenv()
|
|
| 19 |
|
| 20 |
app = FastAPI(title="Checkin-CRM")
|
| 21 |
|
|
|
|
| 22 |
origins = os.getenv("CORS_ALLOW_ORIGINS", "*").split(",")
|
| 23 |
app.add_middleware(
|
| 24 |
CORSMiddleware,
|
|
@@ -28,22 +30,25 @@ app.add_middleware(
|
|
| 28 |
allow_headers=["*"],
|
| 29 |
)
|
| 30 |
|
| 31 |
-
#
|
| 32 |
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 33 |
|
| 34 |
@app.on_event("startup")
|
| 35 |
def on_startup():
|
|
|
|
| 36 |
init_db()
|
| 37 |
|
| 38 |
-
#
|
| 39 |
@app.get("/")
|
| 40 |
def root_redirect():
|
| 41 |
return RedirectResponse(url="/static/admin.html", status_code=307)
|
| 42 |
|
| 43 |
-
# ---
|
|
|
|
|
|
|
| 44 |
@app.post("/api/admin/venues")
|
| 45 |
def create_venue(vc: VenueCreate):
|
| 46 |
-
# デモ
|
| 47 |
with get_session() as s:
|
| 48 |
exists = s.exec(select(Venue).where(Venue.slug == vc.slug)).first()
|
| 49 |
if exists:
|
|
@@ -68,7 +73,9 @@ def seed_demo():
|
|
| 68 |
s.add(v); s.commit(); s.refresh(v)
|
| 69 |
return {"id": v.id, "name": v.name, "slug": v.slug, "created": True}
|
| 70 |
|
| 71 |
-
# ---
|
|
|
|
|
|
|
| 72 |
@app.post("/api/checkin/session")
|
| 73 |
def create_session(sc: SessionCreate):
|
| 74 |
with get_session() as s:
|
|
@@ -91,15 +98,22 @@ def get_session_info(session_id: str):
|
|
| 91 |
venue = s.get(Venue, cs.venue_id)
|
| 92 |
return SessionInfo(session_id=session_id, venue_name=venue.name, nonce=cs.nonce, expires_at=cs.expires_at)
|
| 93 |
|
| 94 |
-
# 絶対URLを埋め込
|
| 95 |
@app.get("/qrcode/{session_id}")
|
| 96 |
def qrcode_png(session_id: str, request: Request):
|
| 97 |
-
base = str(request.base_url).rstrip("/") # https://
|
| 98 |
url = f"{base}/checkin/{session_id}"
|
| 99 |
png = make_qr_png(url)
|
| 100 |
-
return
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
@app.get("/checkin/{session_id}")
|
| 104 |
def checkin_page(session_id: str):
|
| 105 |
return Response(status_code=307, headers={"Location": f"/static/index.html?session={session_id}"})
|
|
@@ -147,7 +161,9 @@ def verify_and_reward(payload: VerifyPayload):
|
|
| 147 |
|
| 148 |
return VerifyResult(ok=True, message="Check-in success", points_awarded=POINTS_PER_CHECKIN)
|
| 149 |
|
| 150 |
-
#
|
|
|
|
|
|
|
| 151 |
@app.get("/api/wallet/{address}/balance", response_model=Balance)
|
| 152 |
def get_balance(address: str):
|
| 153 |
with get_session() as s:
|
|
@@ -157,12 +173,10 @@ def get_balance(address: str):
|
|
| 157 |
bal = s.exec(select(TokenBalance).where(TokenBalance.user_id == user.id, TokenBalance.symbol == "POINT")).first()
|
| 158 |
return Balance(address=address, balance=bal.amount if bal else 0)
|
| 159 |
|
| 160 |
-
# 共有URL
|
| 161 |
@app.get("/api/share")
|
| 162 |
def get_share(text: str, url: str):
|
| 163 |
return JSONResponse(share_urls(text, url))
|
| 164 |
|
| 165 |
-
# CRM
|
| 166 |
@app.get("/api/crm/segments")
|
| 167 |
def crm_segments():
|
| 168 |
with get_session() as s:
|
|
|
|
| 1 |
+
# src/main.py
|
| 2 |
import os
|
| 3 |
from fastapi import FastAPI, HTTPException, Response, Request
|
| 4 |
from fastapi.middleware.cors import CORSMiddleware
|
| 5 |
from fastapi.staticfiles import StaticFiles
|
| 6 |
+
from fastapi.responses import JSONResponse, RedirectResponse, Response as FastAPIResponse
|
| 7 |
from sqlmodel import select
|
| 8 |
from dotenv import load_dotenv
|
| 9 |
|
|
|
|
| 20 |
|
| 21 |
app = FastAPI(title="Checkin-CRM")
|
| 22 |
|
| 23 |
+
# CORS(必要に応じて制限)
|
| 24 |
origins = os.getenv("CORS_ALLOW_ORIGINS", "*").split(",")
|
| 25 |
app.add_middleware(
|
| 26 |
CORSMiddleware,
|
|
|
|
| 30 |
allow_headers=["*"],
|
| 31 |
)
|
| 32 |
|
| 33 |
+
# 静的ファイル(UI)
|
| 34 |
app.mount("/static", StaticFiles(directory="static"), name="static")
|
| 35 |
|
| 36 |
@app.on_event("startup")
|
| 37 |
def on_startup():
|
| 38 |
+
# ディレクトリ作成やパスの可否は db.py 側で吸収。ここはテーブル作成のみ。
|
| 39 |
init_db()
|
| 40 |
|
| 41 |
+
# ルートは管理UIへ
|
| 42 |
@app.get("/")
|
| 43 |
def root_redirect():
|
| 44 |
return RedirectResponse(url="/static/admin.html", status_code=307)
|
| 45 |
|
| 46 |
+
# -------------------------
|
| 47 |
+
# 管理系(簡易デモ用)
|
| 48 |
+
# -------------------------
|
| 49 |
@app.post("/api/admin/venues")
|
| 50 |
def create_venue(vc: VenueCreate):
|
| 51 |
+
# デモ簡略化のため認証なし(本番は認証を追加してください)
|
| 52 |
with get_session() as s:
|
| 53 |
exists = s.exec(select(Venue).where(Venue.slug == vc.slug)).first()
|
| 54 |
if exists:
|
|
|
|
| 73 |
s.add(v); s.commit(); s.refresh(v)
|
| 74 |
return {"id": v.id, "name": v.name, "slug": v.slug, "created": True}
|
| 75 |
|
| 76 |
+
# -------------------------
|
| 77 |
+
# チェックイン発行〜検証
|
| 78 |
+
# -------------------------
|
| 79 |
@app.post("/api/checkin/session")
|
| 80 |
def create_session(sc: SessionCreate):
|
| 81 |
with get_session() as s:
|
|
|
|
| 98 |
venue = s.get(Venue, cs.venue_id)
|
| 99 |
return SessionInfo(session_id=session_id, venue_name=venue.name, nonce=cs.nonce, expires_at=cs.expires_at)
|
| 100 |
|
| 101 |
+
# QR画像(絶対URLを埋め込み/キャッシュ抑止/埋め込み環境でも安定描画)
|
| 102 |
@app.get("/qrcode/{session_id}")
|
| 103 |
def qrcode_png(session_id: str, request: Request):
|
| 104 |
+
base = str(request.base_url).rstrip("/") # 例: https://xxx.hf.space
|
| 105 |
url = f"{base}/checkin/{session_id}"
|
| 106 |
png = make_qr_png(url)
|
| 107 |
+
return FastAPIResponse(
|
| 108 |
+
content=png,
|
| 109 |
+
media_type="image/png",
|
| 110 |
+
headers={
|
| 111 |
+
"Cache-Control": "no-store, max-age=0",
|
| 112 |
+
"Content-Disposition": f'inline; filename="checkin_{session_id}.png"',
|
| 113 |
+
},
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
# チェックイン画面(利用者UIへリダイレクト)
|
| 117 |
@app.get("/checkin/{session_id}")
|
| 118 |
def checkin_page(session_id: str):
|
| 119 |
return Response(status_code=307, headers={"Location": f"/static/index.html?session={session_id}"})
|
|
|
|
| 161 |
|
| 162 |
return VerifyResult(ok=True, message="Check-in success", points_awarded=POINTS_PER_CHECKIN)
|
| 163 |
|
| 164 |
+
# -------------------------
|
| 165 |
+
# ウォレット/共有/CRM
|
| 166 |
+
# -------------------------
|
| 167 |
@app.get("/api/wallet/{address}/balance", response_model=Balance)
|
| 168 |
def get_balance(address: str):
|
| 169 |
with get_session() as s:
|
|
|
|
| 173 |
bal = s.exec(select(TokenBalance).where(TokenBalance.user_id == user.id, TokenBalance.symbol == "POINT")).first()
|
| 174 |
return Balance(address=address, balance=bal.amount if bal else 0)
|
| 175 |
|
|
|
|
| 176 |
@app.get("/api/share")
|
| 177 |
def get_share(text: str, url: str):
|
| 178 |
return JSONResponse(share_urls(text, url))
|
| 179 |
|
|
|
|
| 180 |
@app.get("/api/crm/segments")
|
| 181 |
def crm_segments():
|
| 182 |
with get_session() as s:
|