Skip to content

Commit 3ccfec1

Browse files
committed
add
1 parent 2b2ba94 commit 3ccfec1

3 files changed

Lines changed: 70 additions & 1 deletion

File tree

main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from prometheus_fastapi_instrumentator import Instrumentator
44

55
from database import engine, Base
6-
from routes import comments, likes, subscribe, reactions
6+
from routes import comments, likes, subscribe, reactions, views
77

88
# 테이블 자동 생성
99
Base.metadata.create_all(bind=engine)
@@ -29,6 +29,7 @@
2929
app.include_router(comments.router)
3030
app.include_router(likes.router)
3131
app.include_router(reactions.router)
32+
app.include_router(views.router)
3233
app.include_router(subscribe.router)
3334

3435

models.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,15 @@ class Subscriber(Base):
6363
DateTime(timezone=True),
6464
default=lambda: datetime.now(timezone.utc),
6565
)
66+
67+
68+
class PostView(Base):
69+
__tablename__ = "post_views"
70+
71+
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
72+
post_slug: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False)
73+
views: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
74+
last_viewed: Mapped[datetime] = mapped_column(
75+
DateTime(timezone=True),
76+
default=lambda: datetime.now(timezone.utc),
77+
)

routes/views.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
from fastapi import APIRouter, Depends, Query
2+
from sqlalchemy.orm import Session
3+
from sqlalchemy.dialects.postgresql import insert
4+
5+
from database import get_db
6+
from models import PostView
7+
8+
router = APIRouter(prefix="/posts", tags=["views"])
9+
10+
11+
@router.post("/{slug:path}/view")
12+
def increment_view(slug: str, db: Session = Depends(get_db)) -> dict:
13+
"""조회수 1 증가 (없으면 새로 생성)"""
14+
stmt = (
15+
insert(PostView)
16+
.values(post_slug=slug, views=1)
17+
.on_conflict_do_update(
18+
index_elements=["post_slug"],
19+
set_={"views": PostView.views + 1},
20+
)
21+
)
22+
db.execute(stmt)
23+
db.commit()
24+
row = db.query(PostView).filter(PostView.post_slug == slug).first()
25+
return {"slug": slug, "views": row.views if row else 1}
26+
27+
28+
@router.get("/views/bulk")
29+
def get_views_bulk(
30+
slugs: str = Query(..., description="쉼표로 구분된 슬러그 목록"),
31+
db: Session = Depends(get_db),
32+
) -> dict[str, int]:
33+
"""여러 포스트의 조회수 한번에 조회"""
34+
slug_list = [s.strip() for s in slugs.split(",") if s.strip()]
35+
if not slug_list:
36+
return {}
37+
rows = db.query(PostView).filter(PostView.post_slug.in_(slug_list)).all()
38+
result = {slug: 0 for slug in slug_list}
39+
for row in rows:
40+
result[row.post_slug] = row.views
41+
return result
42+
43+
44+
@router.get("/views/top")
45+
def get_top_views(
46+
limit: int = Query(default=5, le=100),
47+
db: Session = Depends(get_db),
48+
) -> list[dict]:
49+
"""조회수 상위 포스트 목록"""
50+
rows = (
51+
db.query(PostView)
52+
.order_by(PostView.views.desc())
53+
.limit(limit)
54+
.all()
55+
)
56+
return [{"slug": r.post_slug, "views": r.views} for r in rows]

0 commit comments

Comments
 (0)