mirror of
https://github.com/Ad-closeNN/blog-fuwari.git
synced 2026-05-31 01:20:06 -04:00
Compare commits
3 Commits
ef41390d2e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| cafe49c9e1 | |||
| e9d50e2735 | |||
| b2313d1796 |
+7
-1
@@ -36,4 +36,10 @@ yarn.lock
|
|||||||
.cache
|
.cache
|
||||||
|
|
||||||
build.log
|
build.log
|
||||||
.traces
|
.traces
|
||||||
|
|
||||||
|
# 2026/5/23 Feedback module api backend
|
||||||
|
feedback-api/*
|
||||||
|
!feedback-api/database.py
|
||||||
|
!feedback-api/main.py
|
||||||
|
!feedback-api/requirements.txt
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import aiosqlite
|
||||||
|
|
||||||
|
DB_PATH = "feedback.db"
|
||||||
|
|
||||||
|
|
||||||
|
async def init_db():
|
||||||
|
async with aiosqlite.connect(DB_PATH) as db:
|
||||||
|
await db.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS feedbacks (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
choice TEXT NOT NULL CHECK(choice IN ('yes', 'no')),
|
||||||
|
timestamp TEXT NOT NULL,
|
||||||
|
ip_hash TEXT NOT NULL,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
await db.execute("""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_feedbacks_ip_hash
|
||||||
|
ON feedbacks(ip_hash, created_at)
|
||||||
|
""")
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
async def add_feedback(ip_hash: str, url: str, choice: str, timestamp: str):
|
||||||
|
async with aiosqlite.connect(DB_PATH) as db:
|
||||||
|
await db.execute(
|
||||||
|
"INSERT INTO feedbacks (url, choice, timestamp, ip_hash) VALUES (?, ?, ?, ?)",
|
||||||
|
(url, choice, timestamp, ip_hash),
|
||||||
|
)
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
|
||||||
|
async def check_rate_limit(ip_hash: str, limit: int = 5, window_seconds: int = 3600) -> bool:
|
||||||
|
async with aiosqlite.connect(DB_PATH) as db:
|
||||||
|
cursor = await db.execute(
|
||||||
|
"SELECT COUNT(*) FROM feedbacks WHERE ip_hash = ? AND created_at > datetime('now', ?)",
|
||||||
|
(ip_hash, f"-{window_seconds} seconds"),
|
||||||
|
)
|
||||||
|
row = await cursor.fetchone()
|
||||||
|
return row[0] >= limit
|
||||||
|
|
||||||
|
|
||||||
|
async def get_stats() -> dict:
|
||||||
|
async with aiosqlite.connect(DB_PATH) as db:
|
||||||
|
cursor = await db.execute("SELECT COUNT(*) FROM feedbacks")
|
||||||
|
total = (await cursor.fetchone())[0]
|
||||||
|
|
||||||
|
cursor = await db.execute("SELECT choice, COUNT(*) FROM feedbacks GROUP BY choice")
|
||||||
|
rows = await cursor.fetchall()
|
||||||
|
counts = {row[0]: row[1] for row in rows}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"total": total,
|
||||||
|
"yes": counts.get("yes", 0),
|
||||||
|
"no": counts.get("no", 0),
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import hashlib
|
||||||
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
|
from fastapi import FastAPI, Request
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
from database import add_feedback, check_rate_limit, get_stats, init_db
|
||||||
|
|
||||||
|
|
||||||
|
class FeedbackIn(BaseModel):
|
||||||
|
url: str
|
||||||
|
choice: str
|
||||||
|
timestamp: str
|
||||||
|
|
||||||
|
|
||||||
|
RATE_LIMIT = 5
|
||||||
|
RATE_WINDOW = 3600
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def lifespan(app: FastAPI):
|
||||||
|
await init_db()
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
app = FastAPI(lifespan=lifespan)
|
||||||
|
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=["*"],
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_client_ip(request: Request) -> str:
|
||||||
|
cf_ip = request.headers.get("cf-connecting-ip")
|
||||||
|
if cf_ip:
|
||||||
|
return cf_ip
|
||||||
|
forwarded = request.headers.get("x-forwarded-for")
|
||||||
|
if forwarded:
|
||||||
|
return forwarded.split(",")[0].strip()
|
||||||
|
return request.client.host or "unknown"
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/feedback")
|
||||||
|
async def receive_feedback(body: FeedbackIn, request: Request):
|
||||||
|
ip = get_client_ip(request)
|
||||||
|
ip_hash = hashlib.sha256(ip.encode()).hexdigest()
|
||||||
|
|
||||||
|
if not await check_rate_limit(ip_hash, RATE_LIMIT, RATE_WINDOW):
|
||||||
|
await add_feedback(ip_hash, body.url, body.choice, body.timestamp)
|
||||||
|
|
||||||
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/feedback/stats")
|
||||||
|
async def stats():
|
||||||
|
return await get_stats()
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/feedback/health")
|
||||||
|
async def health():
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
uvicorn.run("main:app", host="0.0.0.0", port=8005)
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
fastapi>=0.115.0
|
||||||
|
uvicorn[standard]>=0.32.0
|
||||||
|
aiosqlite>=0.20.0
|
||||||
+1
-1
@@ -129,7 +129,7 @@ export const expressiveCodeConfig: ExpressiveCodeConfig = {
|
|||||||
|
|
||||||
export const feedbackConfig: FeedbackConfig = {
|
export const feedbackConfig: FeedbackConfig = {
|
||||||
enable: true,
|
enable: true,
|
||||||
apiEndpoint: "https://blog-feedback.adclosenn.top",
|
apiEndpoint: "https://blog-feedback.adclosenn.top/api/feedback",
|
||||||
debug: false,
|
debug: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user