Skip to content
11 changes: 11 additions & 0 deletions wacruit/src/apps/common/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,14 @@ class RecruitingApplicationStatus(Enum):
IN_PROGRESS = 1
ACCEPTED = 2
REJECTED = 3


class ProjectType(Enum):
SERVICE = 1
STUDY = 2


class Position(Enum):
FRONTEND = 1
BACKEND = 2
DESIGNER = 3
10 changes: 10 additions & 0 deletions wacruit/src/apps/common/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,13 @@ def responses_from(
for exc in exceptions:
responses.update(exc().to_response())
return responses


class InvalidRecruitTypeException(WacruitException):
def __init__(self, type: str):
super().__init__(status_code=400, detail=f"Invalid recruiting type: {type}")


class InvalidProjectTypeException(WacruitException):
def __init__(self, type: str):
super().__init__(status_code=400, detail=f"Invalid project type: {type}")
11 changes: 11 additions & 0 deletions wacruit/src/apps/member/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from fastapi import HTTPException


class MemberNotFoundException(HTTPException):
def __init__(self):
super().__init__(status_code=404, detail="Member not found.")


class MemberAlreadyExistsException(HTTPException):
def __init__(self):
super().__init__(status_code=400, detail="Member already exists.")
7 changes: 2 additions & 5 deletions wacruit/src/apps/member/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship

from wacruit.src.apps.common.enums import Position
from wacruit.src.apps.common.sql import CURRENT_TIMESTAMP
from wacruit.src.apps.project.models import project_member_association
from wacruit.src.database.base import DeclarativeBase
from wacruit.src.database.base import intpk
from wacruit.src.database.base import str30
Expand All @@ -31,9 +31,6 @@ class Member(DeclarativeBase):
generation: Mapped[str30]
is_active: Mapped[bool]
created_at: Mapped[datetime] = mapped_column(server_default=CURRENT_TIMESTAMP)
position: Mapped[Position | None] = mapped_column(default=None)

leading_projects: Mapped[list["Project"]] = relationship(back_populates="leader")
projects: Mapped[list["Project"]] = relationship(
secondary=project_member_association, back_populates="members"
)
review: Mapped["Review"] = relationship(back_populates="member")
13 changes: 13 additions & 0 deletions wacruit/src/apps/member/repositories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from fastapi import Depends
from sqlalchemy.orm import Session

from wacruit.src.apps.member.models import Member
from wacruit.src.database.connection import get_db_session


class MemberRepository:
def __init__(self, session: Session = Depends(get_db_session)) -> None:
self.session = session

def get_member_by_id(self, member_id: int) -> Member | None:
return self.session.query(Member).filter(Member.id == member_id).first()
32 changes: 32 additions & 0 deletions wacruit/src/apps/problem/schemas_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ class TestcaseDto(OrmModel):
expected_output: str


class TestCaseCreateDto(BaseModel):
stdin: str = Field(..., max_length=10000)
expected_output: str = Field(..., max_length=10000)
time_limit: float = Field(..., ge=0.0)
extra_time: float = Field(..., ge=0.0)
memory_limit: int = Field(..., ge=0.0)
stack_limit: int = Field(..., ge=0.0)
is_example: bool


class ProblemResponse(OrmModel):
id: int
num: int
Expand All @@ -36,4 +46,26 @@ class CodeSubmissionResultResponse(BaseModel):
memory: float | None = None


class ProblemCreateDto(BaseModel):
num: int
body: str
testcases: list[TestCaseCreateDto] = Field(..., min_items=1)

@classmethod
def from_orm(cls, obj) -> "ProblemCreateDto":
testcases = [
TestCaseCreateDto(
stdin=testcase.stdin,
expected_output=testcase.expected_output,
time_limit=float(testcase.time_limit),
extra_time=float(testcase.extra_time),
memory_limit=testcase.memory_limit,
stack_limit=testcase.stack_limit,
is_example=testcase.is_example,
)
for testcase in obj.testcases
]
return cls(num=obj.num, body=obj.body, testcases=testcases)


TokenStr = str
3 changes: 2 additions & 1 deletion wacruit/src/apps/problem/views_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,6 @@ async def get_submission(
problem_service: Annotated[ProblemService, Depends()],
):
return EventSourceResponse(
problem_service.get_recent_submission_result(request, user, problem_id), ping=3600
problem_service.get_recent_submission_result(request, user, problem_id),
ping=3600,
)
1 change: 1 addition & 0 deletions wacruit/src/apps/project/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .views import v3_router as router
11 changes: 11 additions & 0 deletions wacruit/src/apps/project/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from wacruit.src.apps.common.exceptions import WacruitException


class ProjectNotFoundException(WacruitException):
def __init__(self):
super().__init__(status_code=404, detail="존재하지 않는 프로젝트입니다.")


class ProjectAlreadyExistsException(WacruitException):
def __init__(self):
super().__init__(status_code=409, detail="이미 존재하는 프로젝트입니다.")
22 changes: 7 additions & 15 deletions wacruit/src/apps/project/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from sqlalchemy.orm import mapped_column
from sqlalchemy.orm import relationship

from wacruit.src.apps.common.enums import ProjectType
from wacruit.src.database.base import DeclarativeBase
from wacruit.src.database.base import intpk
from wacruit.src.database.base import str30
Expand All @@ -16,13 +17,6 @@
if TYPE_CHECKING:
from wacruit.src.apps.member.models import Member

project_member_association = Table(
"project_member_association",
DeclarativeBase.metadata,
Column("member_id", ForeignKey("member.id")),
Column("project_id", ForeignKey("project.id")),
)


class Project(DeclarativeBase):
__tablename__ = "project"
Expand All @@ -32,15 +26,12 @@ class Project(DeclarativeBase):
summary: Mapped[str50 | None]
introduction: Mapped[str255 | None]
thumbnail_url: Mapped[str255 | None]
leader_id: Mapped[int] = mapped_column(ForeignKey("member.id"))
is_service: Mapped[bool]

leader: Mapped["Member"] = relationship(back_populates="leading_projects")
members: Mapped[list["Member"]] = relationship(
secondary=project_member_association, back_populates="projects"
project_type: Mapped[ProjectType]
is_active: Mapped[bool] = mapped_column(default=True)
urls: Mapped[list["ProjectURL"] | None] = relationship(
back_populates="source_project"
)
urls: Mapped[list["ProjectURL"]] = relationship(back_populates="source_project")
image_urls: Mapped[list["ProjectImageURL"]] = relationship(
image_urls: Mapped[list["ProjectImageURL"] | None] = relationship(
back_populates="source_project"
)

Expand All @@ -50,6 +41,7 @@ class ProjectURL(DeclarativeBase):

id: Mapped[intpk]
project_id: Mapped[int] = mapped_column(ForeignKey("project.id"))
title: Mapped[str50]
url: Mapped[str255]

source_project: Mapped["Project"] = relationship(back_populates="urls")
Expand Down
47 changes: 47 additions & 0 deletions wacruit/src/apps/project/repositories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from fastapi import Depends
from sqlalchemy.orm import joinedload
from sqlalchemy.orm import Session

from wacruit.src.apps.project.models import Project
from wacruit.src.apps.project.models import ProjectImageURL
from wacruit.src.apps.project.models import ProjectURL
from wacruit.src.database.connection import get_db_session
from wacruit.src.database.connection import Transaction


class ProjectRepository:
def __init__(
self,
session: Session = Depends(get_db_session),
transaction: Transaction = Depends(),
):
self.session = session
self.transaction = transaction

def get_project_by_id(self, project_id: int) -> Project | None:
return (
self.session.query(Project)
.options(
joinedload(Project.urls),
joinedload(Project.image_urls),
)
.filter(Project.id == project_id)
.first()
)

def get_project_by_name(self, name: str) -> Project | None:
return self.session.query(Project).filter(Project.name == name).first()

def get_projects(self) -> list[Project]:
return self.session.query(Project).all()

def create_project(self, project: Project) -> Project:
with self.transaction:
self.session.add(project)
return project

def update_project(self, project: Project) -> Project:
with self.transaction:
self.session.merge(project)

return project
92 changes: 92 additions & 0 deletions wacruit/src/apps/project/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
from __future__ import annotations

from pydantic import BaseModel
from pydantic import Field

from wacruit.src.apps.common.enums import ProjectType
from wacruit.src.apps.common.schemas import OrmModel


class ProjectLinkDto(BaseModel):
title: str
url: str


class ProjectCreateRequest(BaseModel):
name: str = Field(..., max_length=30)
summary: str | None = Field(None, max_length=50)
introduction: str | None = Field(None, max_length=255)
thumbnail_url: str | None = Field(None, max_length=255)
project_type: str
is_active: bool
images: list[str] | None
urls: list[ProjectLinkDto] | None


class ProjectUpdateRequest(BaseModel):
name: str | None = Field(None, max_length=30)
summary: str | None = Field(None, max_length=50)
introduction: str | None = Field(None, max_length=255)
thumbnail_url: str | None = Field(None, max_length=255)
project_type: str | None
is_active: bool | None
images: list[str] | None
urls: list[ProjectLinkDto] | None


class ProjectDetailResponse(OrmModel):
id: int
name: str
summary: str | None
introduction: str | None
thumbnail_url: str | None
project_type: str
is_active: bool
images: list[str] | None
urls: list[ProjectLinkDto] | None

@classmethod
def from_orm(cls, obj):
urls = None
if obj.urls:
urls = [ProjectLinkDto(title=url.title, url=url.url) for url in obj.urls]

images = None
if obj.image_urls:
images = [img.url for img in obj.image_urls]

return cls(
id=obj.id,
name=obj.name,
summary=obj.summary,
introduction=obj.introduction,
thumbnail_url=obj.thumbnail_url,
project_type=obj.project_type.name,
is_active=obj.is_active,
images=images,
urls=urls,
)


class ProjectBriefResponse(OrmModel):
id: int
name: str
summary: str | None
thumbnail_url: str | None
project_type: str
is_active: bool

@classmethod
def from_orm(cls, obj):
return cls(
id=obj.id,
name=obj.name,
summary=obj.summary,
thumbnail_url=obj.thumbnail_url,
project_type=obj.project_type.name,
is_active=obj.is_active,
)


class ProjectListResponse(OrmModel):
projects: list[ProjectBriefResponse]
Loading