Skip to content

Commit b6d57ce

Browse files
committed
✨ feat: awesome jmy pagination
1 parent a7ebbbc commit b6d57ce

File tree

8 files changed

+168
-18
lines changed

8 files changed

+168
-18
lines changed

app/api/v1/endpoints/jmy.py

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,55 @@
1+
from typing import Literal
2+
13
from dependency_injector.wiring import Provide, inject
2-
from fastapi import Depends, status
4+
from fastapi import Depends, Query, status
35
from fastapi.responses import ORJSONResponse
46

57
from app.core.auth import (
6-
AdminAuthDeps,
78
GitHubOAuthDeps,
89
GoogleOAuthDeps,
910
PasswordOAuthDeps,
1011
)
1112
from app.core.container import Container
1213
from app.core.router import CoreAPIRouter
13-
from app.schemas.jmy import JmyCompanyOut
14+
from app.schemas.jmy import JmyCompanyOut, JmyPageResponse
1415
from app.services.jmy import JmyService
1516

16-
router = CoreAPIRouter(
17-
prefix="/jmy",
18-
tags=["jmy"],
19-
dependencies=[AdminAuthDeps, PasswordOAuthDeps, GoogleOAuthDeps, GitHubOAuthDeps],
17+
router = CoreAPIRouter(prefix="/jmy", tags=["jmy"])
18+
19+
20+
@router.get(
21+
"",
22+
response_model=JmyPageResponse,
23+
response_class=ORJSONResponse,
24+
status_code=status.HTTP_200_OK,
25+
dependencies=[PasswordOAuthDeps, GoogleOAuthDeps, GitHubOAuthDeps],
26+
summary="",
27+
description="",
2028
)
29+
@inject
30+
async def get_jmy_page(
31+
page: int = Query(1, ge=1),
32+
size: int = Query(10, ge=10, le=100),
33+
sort_by: Literal["old", "new"] = "old",
34+
filter_by: Literal["ms", "phd", "all"] = "all",
35+
service: JmyService = Depends(Provide[Container.jmy_service]),
36+
):
37+
return await service.get_page(
38+
page=page, size=size, sort_by=sort_by, filter_by=filter_by
39+
)
2140

2241

2342
@router.get(
2443
"/{id}",
2544
response_model=JmyCompanyOut,
2645
response_class=ORJSONResponse,
2746
status_code=status.HTTP_200_OK,
47+
dependencies=[PasswordOAuthDeps, GoogleOAuthDeps, GitHubOAuthDeps],
2848
summary="",
2949
description="",
3050
)
3151
@inject
32-
async def get_jmy(
52+
async def get_jmy_by_id(
3353
id: int,
3454
service: JmyService = Depends(Provide[Container.jmy_service]),
3555
):

app/repositories/jmy.py

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
from sqlalchemy import select
1+
import math
2+
from operator import and_
3+
from typing import Literal, Sequence
4+
5+
from sqlalchemy import func, select
26

37
from app.core.database import database
4-
from app.models.jmy import JmyCompany
8+
from app.models.jmy import JmyCompany, JmyTimeSeries
59
from app.repositories.base import BaseRepository
610

711

@@ -18,3 +22,65 @@ async def read_by_name(self, name: str, eager: bool = False) -> JmyCompany | Non
1822
result = await session.execute(stmt)
1923
entity = result.scalar_one_or_none()
2024
return entity
25+
26+
async def read_page(
27+
self,
28+
page: int,
29+
size: int,
30+
sort_by: Literal["old", "new"],
31+
filter_by: Literal["ms", "phd", "all"],
32+
) -> tuple[Sequence[JmyCompany], int]:
33+
jmy_ms = [
34+
"벤처기업부설연구소",
35+
"중견기업부설연구소",
36+
"중소기업부설연구소",
37+
]
38+
latest_date_subq = (
39+
select(
40+
JmyTimeSeries.company_id, func.max(JmyTimeSeries.date).label("max_date")
41+
)
42+
.group_by(JmyTimeSeries.company_id)
43+
.subquery()
44+
)
45+
total_expr = (
46+
(JmyTimeSeries.b_old + JmyTimeSeries.a_old)
47+
if sort_by == "old"
48+
else (JmyTimeSeries.b_new + JmyTimeSeries.a_new)
49+
)
50+
total_subq = (
51+
select(
52+
JmyTimeSeries.company_id,
53+
total_expr.label("total"),
54+
)
55+
.join(
56+
latest_date_subq,
57+
and_(
58+
JmyTimeSeries.company_id == latest_date_subq.c.company_id,
59+
JmyTimeSeries.date == latest_date_subq.c.max_date,
60+
),
61+
)
62+
.subquery()
63+
)
64+
stmt = select(self.model).outerjoin(
65+
total_subq, self.model.id == total_subq.c.company_id
66+
)
67+
if filter_by == "ms":
68+
stmt = stmt.where(self.model.type_.in_(jmy_ms))
69+
elif filter_by == "phd":
70+
stmt = stmt.where(self.model.type_.notin_(jmy_ms))
71+
offset = (page - 1) * size
72+
stmt = stmt.order_by(total_subq.c.total.desc()).offset(offset).limit(size)
73+
session = database.scoped_session()
74+
results = await session.execute(stmt)
75+
entities = results.scalars().all()
76+
_stmt = select(func.count(self.model.id)).outerjoin(
77+
total_subq, self.model.id == total_subq.c.company_id
78+
)
79+
if filter_by == "ms":
80+
_stmt = _stmt.where(self.model.type_.in_(jmy_ms))
81+
elif filter_by == "phd":
82+
_stmt = _stmt.where(self.model.type_.notin_(jmy_ms))
83+
result = await session.execute(_stmt)
84+
total_count = result.scalar_one()
85+
total_pages = math.ceil(total_count / size)
86+
return entities, total_pages

app/schemas/auth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
from pydantic import BaseModel
66

77
from app.models.enums import OAuthProvider
8-
from app.schemas.base import BaseRequest, BaseResponse
8+
from app.schemas.base import BaseDBResponse, BaseRequest
99

1010

1111
class AuthRequest(BaseRequest): ...
1212

1313

14-
class AuthResponse(BaseResponse):
14+
class AuthResponse(BaseDBResponse):
1515
provider: OAuthProvider
1616

1717

app/schemas/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ class BaseRequest(BaseModel, abc.ABC):
1414

1515
class BaseResponse(BaseModel, abc.ABC):
1616
model_config = ConfigDict(from_attributes=True)
17+
18+
19+
class BaseDBResponse(BaseResponse):
1720
id: int
1821
created_at: datetime
1922
updated_at: datetime

app/schemas/jmy.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from datetime import datetime
22

3-
from app.schemas.base import BaseRequest, BaseResponse
3+
from app.schemas.base import BaseDBResponse, BaseRequest, BaseResponse
44

55

66
class JmyCompanyRequest(BaseRequest):
@@ -20,7 +20,7 @@ class JmyCompanyRequest(BaseRequest):
2020
a_old: int
2121

2222

23-
class JmyCompanyResponse(BaseResponse):
23+
class JmyCompanyResponse(BaseDBResponse):
2424
name: str
2525
year: int
2626
location: str
@@ -30,7 +30,22 @@ class JmyCompanyResponse(BaseResponse):
3030
research: str
3131

3232

33-
class JmyTimeSeriesOut(BaseResponse):
33+
class JmyCompanyInfo(JmyCompanyResponse):
34+
date: datetime
35+
b_assigned: int
36+
b_new: int
37+
b_old: int
38+
a_assigned: int
39+
a_new: int
40+
a_old: int
41+
42+
43+
class JmyPageResponse(BaseResponse):
44+
info: list[JmyCompanyInfo]
45+
total_pages: int
46+
47+
48+
class JmyTimeSeriesOut(BaseDBResponse):
3449
date: datetime
3550
b_assigned: int
3651
b_new: int

app/schemas/users.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
from app.models.enums import Role
66
from app.schemas.auth import AuthOut, AuthResponse
7-
from app.schemas.base import BaseRequest, BaseResponse
7+
from app.schemas.base import BaseDBResponse, BaseRequest
88

99

1010
class UserRequest(BaseRequest):
@@ -20,7 +20,7 @@ class UserPasswordAdminRequest(BaseRequest):
2020
password: Annotated[str, StringConstraints(min_length=8, max_length=30)]
2121

2222

23-
class UserResponse(BaseResponse):
23+
class UserResponse(BaseDBResponse):
2424
name: Annotated[str, StringConstraints(min_length=3, max_length=30)]
2525
email: EmailStr
2626
role: Role

app/services/jmy.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
1+
from typing import Literal
2+
13
from app.core.database import database
24
from app.models.jmy import JmyCompany, JmyTimeSeries
35
from app.repositories.jmy import JmyRepository
4-
from app.schemas.jmy import JmyCompanyOut, JmyCompanyRequest
6+
from app.schemas.jmy import (
7+
JmyCompanyInfo,
8+
JmyCompanyOut,
9+
JmyCompanyRequest,
10+
JmyPageResponse,
11+
)
512
from app.services.base import BaseService
613

714

@@ -36,3 +43,40 @@ async def create(self, schema: JmyCompanyRequest) -> JmyCompanyOut:
3643
)
3744
entity = await self.repository.create(entity=jmy_company)
3845
return self.mapper(entity)
46+
47+
@database.transactional
48+
async def get_page(
49+
self,
50+
page: int,
51+
size: int,
52+
sort_by: Literal["old", "new"],
53+
filter_by: Literal["ms", "phd", "all"],
54+
) -> JmyPageResponse:
55+
entities, total_pages = await self.repository.read_page(
56+
page=page, size=size, sort_by=sort_by, filter_by=filter_by
57+
)
58+
jmy_company_info = []
59+
for entity in entities:
60+
time_series = entity.time_series[-1]
61+
jmy_company_info.append(
62+
JmyCompanyInfo(
63+
id=entity.id,
64+
created_at=entity.created_at,
65+
updated_at=entity.updated_at,
66+
name=entity.name,
67+
year=entity.year,
68+
location=entity.location,
69+
address=entity.address,
70+
type_=entity.type_,
71+
size=entity.size,
72+
research=entity.research,
73+
date=time_series.date,
74+
b_assigned=time_series.b_assigned,
75+
b_new=time_series.b_new,
76+
b_old=time_series.b_old,
77+
a_assigned=time_series.a_assigned,
78+
a_new=time_series.a_new,
79+
a_old=time_series.a_old,
80+
)
81+
)
82+
return JmyPageResponse(info=jmy_company_info, total_pages=total_pages)

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ disable = [
5959
"R0801",
6060
"R0903",
6161
"R0913",
62+
"R0914",
6263
"W0221",
6364
"W0511",
6465
"W0622",
@@ -75,6 +76,7 @@ disable = [
7576
# R0801: Similar lines in * files
7677
# R0903: Too few public methods (*/*) (too-few-public-methods)
7778
# R0913: Too many arguments (*/*) (too-many-arguments)
79+
# R0914: Too many local variables (*/*) (too-many-locals)
7880
# W0221: Number of parameters was * in '*' and is now * in overriding '*' method (arguments-differ)
7981
# W0221: Variadics removed in overriding '*' method (arguments-differ)
8082
# W0511: TODO, FIXME

0 commit comments

Comments
 (0)