Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions fastapi/app/api/v1/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .auth import AuthAPI
from .student_sheet import StudentSheetAPI
from .user import UserAPI
63 changes: 63 additions & 0 deletions fastapi/app/api/v1/student_sheet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from typing import List, Optional
from uuid import UUID

from app.core.exceptions import (
ApiException,
NotFoundObjectMatchingUuid,
PermissionDenied,
)
from app.crud import StudentSheetCRUD
from app.models import StudentSheet
from app.schemas import (
CreateFormStudentSheetSchema,
CreateStudentSheetSchema,
UpdateFormStudentSheetSchema,
UpdateStudentSheetSchema,
)

from fastapi import Request


class StudentSheetAPI:
@classmethod
def gets(cls, request: Request) -> List[StudentSheet]:
return StudentSheetCRUD(request.state.db_session).gets_by_user_uuid(
request.user.uuid
)

@classmethod
def get(cls, request: Request, uuid: UUID) -> Optional[StudentSheet]:
obj = StudentSheetCRUD(request.state.db_session).get_by_uuid(uuid)
if obj is None:
raise ApiException(NotFoundObjectMatchingUuid(StudentSheet, uuid))
if obj.user_uuid != request.user.uuid:
raise ApiException(PermissionDenied())
return obj

@classmethod
def create(
cls, request: Request, schema: CreateFormStudentSheetSchema
) -> StudentSheet:
data = CreateStudentSheetSchema(user_uuid=request.user.uuid, **schema.dict())
return StudentSheetCRUD(request.state.db_session).create(data)

@classmethod
def update(
cls, request: Request, uuid: UUID, schema: UpdateFormStudentSheetSchema
) -> StudentSheet:
obj = StudentSheetCRUD(request.state.db_session).get_by_uuid(uuid)
if not obj:
raise ApiException(NotFoundObjectMatchingUuid(StudentSheet, uuid))
if obj.user_uuid != request.user.uuid:
raise ApiException(PermissionDenied)
data = UpdateStudentSheetSchema(user_uuid=request.user.uuid, **schema.dict())
return StudentSheetCRUD(request.state.db_session).update(uuid, data)

@classmethod
def delete(cls, request: Request, uuid: UUID) -> None:
obj = StudentSheetCRUD(request.state.db_session).get_by_uuid(uuid)
if not obj:
raise ApiException(NotFoundObjectMatchingUuid(StudentSheet, uuid))
if obj.user_uuid != request.user.uuid:
raise ApiException(PermissionDenied)
return StudentSheetCRUD(request.state.db_session).delete_by_uuid(uuid)
4 changes: 4 additions & 0 deletions fastapi/app/core/settings.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import os
from datetime import timedelta, timezone
from functools import lru_cache
from pathlib import Path

Expand Down Expand Up @@ -32,6 +33,9 @@ class Settings(BaseSettings):
# firebase
firebase_credentials_path: str = "/src/firebase_credentials.json"

# timezone
default_timezone = timezone(timedelta(hours=+9), "JST")

class Config:
env_file = os.path.join(BASE_DIR, "fastapi.env")

Expand Down
1 change: 1 addition & 0 deletions fastapi/app/crud/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
from .student_sheet import StudentSheetCRUD
from .user import UserCRUD
59 changes: 59 additions & 0 deletions fastapi/app/crud/student.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from typing import List, Optional
from uuid import UUID

from app.core.exceptions import ApiException, NotFoundObjectMatchingUuid, create_error
from app.db.database import get_db_session
from app.models import Student
from app.schemas import CreateStudentSchema, UpdateStudentSchema
from sqlalchemy.orm import scoped_session

from .base import BaseCRUD

db_session = get_db_session()


class StudentCRUD(BaseCRUD):
def __init__(self, db_session: scoped_session):
super().__init__(db_session, Student)

def gets_by_student_sheet_uuid(self, student_sheet_uuid: UUID) -> List[Student]:
return self.get_query().filter_by(student_sheet_uuid=student_sheet_uuid).all()

def get_by_student_sheet_uuid_and_number(
self, student_sheet_uuid: UUID, number: int
) -> Optional[Student]:
return (
self.get_query()
.filter_by(student_sheet_uuid=student_sheet_uuid, number=number)
.first()
)

def create(self, schema: CreateStudentSchema) -> Student:
if (
self.get_by_student_sheet_uuid_and_number(
schema.student_sheet_uuid, schema.number
)
is not None
):
raise ApiException(
create_error(
f"Student with number {schema.number} already exists in this student sheet."
)
)
return super().create(schema.dict())

def update(self, uuid: UUID, schema: UpdateStudentSchema) -> Student:
update_obj = self.get_by_uuid(uuid)
if update_obj is None:
raise ApiException(NotFoundObjectMatchingUuid(self.model, uuid))

new_data_obj = self.get_by_student_sheet_uuid_and_number(
schema.student_sheet_uuid, schema.number
)
if new_data_obj is not None and new_data_obj.uuid != update_obj.uuid:
raise ApiException(
create_error(
f"Student with number {schema.number} already exists in this student sheet."
)
)
return super().update(uuid, schema.dict())
44 changes: 44 additions & 0 deletions fastapi/app/crud/student_sheet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
from typing import List, Optional
from uuid import UUID

from app.core.exceptions import ApiException, NotFoundObjectMatchingUuid, create_error
from app.db.database import get_db_session
from app.models import StudentSheet
from app.schemas import CreateStudentSheetSchema, UpdateStudentSheetSchema
from sqlalchemy.orm import scoped_session

from .base import BaseCRUD

db_session = get_db_session()


class StudentSheetCRUD(BaseCRUD):
def __init__(self, db_session: scoped_session):
super().__init__(db_session, StudentSheet)

def gets_by_user_uuid(self, user_uuid: UUID) -> List[StudentSheet]:
return self.get_query().filter_by(user_uuid=user_uuid).all()

def get_by_user_uuid_and_name(
self, user_uuid: UUID, name: str
) -> Optional[StudentSheet]:
return self.get_query().filter_by(user_uuid=user_uuid, name=name).first()

def create(self, schema: CreateStudentSheetSchema) -> StudentSheet:
if self.get_by_user_uuid_and_name(schema.user_uuid, schema.name) is not None:
raise ApiException(
create_error(f"StudentSheet with name {schema.name} already exists")
)
return super().create(schema.dict())

def update(self, uuid: UUID, schema: UpdateStudentSheetSchema) -> StudentSheet:
update_obj = self.get_by_uuid(uuid)
if update_obj is None:
raise ApiException(NotFoundObjectMatchingUuid(self.model, uuid))

new_data_obj = self.get_by_user_uuid_and_name(schema.user_uuid, schema.name)
if new_data_obj is not None and new_data_obj.uuid != update_obj.uuid:
raise ApiException(
create_error(f"StudentSheet with name {schema.name} already exists")
)
return super().update(uuid, schema.dict())
17 changes: 11 additions & 6 deletions fastapi/app/crud/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
from app.models import User
from sqlalchemy.orm import scoped_session

from ..core.exceptions import ApiException, FirebaseUidOrPasswordMustBeSet, create_error
from ..core.exceptions import (
ApiException,
FirebaseUidOrPasswordMustBeSet,
NotFoundObjectMatchingUuid,
create_error,
)
from ..core.security import get_password_hash
from .base import BaseCRUD

Expand Down Expand Up @@ -33,11 +38,11 @@ def create(self, data: dict = {}) -> User:

def update(self, uuid: UUID, data: dict = {}) -> User:
# FIXME: セキュリティ的に、そのメールアドレスのユーザーが存在することを伝えるのはよくない?
if (
self.get_by_uuid(uuid) is not None
and data["email"] != self.get_by_uuid(uuid).email
and self.get_query().filter_by(email=data["email"]).first()
):
update_obj = self.get_by_uuid(uuid)
if update_obj is None:
raise ApiException(NotFoundObjectMatchingUuid(self.model, uuid))
new_data_obj = self.get_query().filter_by(email=data["email"]).first()
if new_data_obj is not None and new_data_obj.uuid != update_obj.uuid:
raise ApiException(
create_error(f"User with email {data['email']} already exists")
)
Expand Down
31 changes: 31 additions & 0 deletions fastapi/app/db/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,34 @@
"password": "password",
},
]

STUDENT_SHEETS = [
{
"name": "3-A (1)",
"user": USERS[0],
},
{
"name": "3-A (2)",
"user": USERS[0],
},
{
"name": "3-A (3)",
"user": USERS[0],
},
{
"name": "3-B (1)",
"user": USERS[1],
},
{
"name": "3-B (2)",
"user": USERS[1],
},
{
"name": "3-C (1)",
"user": USERS[2],
},
{
"name": "3-C (2)",
"user": USERS[2],
},
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""student_sheet

Revision ID: 8bf6f6f70d43
Revises: c2dc0f9395d6
Create Date: 2022-01-03 17:55:31.902638+09:00

"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = "8bf6f6f70d43"
down_revision = "c2dc0f9395d6"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"student_sheets",
sa.Column("uuid", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column(
"created_at",
postgresql.TIMESTAMP(timezone=True),
server_default=sa.text("CURRENT_TIMESTAMP"),
nullable=False,
comment="登録日時",
),
sa.Column(
"updated_at",
postgresql.TIMESTAMP(timezone=True),
nullable=True,
comment="最終更新日時",
),
sa.Column("is_active", sa.BOOLEAN(), server_default="true", nullable=False),
sa.Column("name", sa.VARCHAR(length=100), nullable=False),
sa.Column("user_uuid", postgresql.UUID(as_uuid=True), nullable=False),
sa.ForeignKeyConstraint(["user_uuid"], ["users.uuid"], ondelete="CASCADE"),
sa.PrimaryKeyConstraint("uuid"),
sa.UniqueConstraint("name", "user_uuid"),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("student_sheets")
# ### end Alembic commands ###
54 changes: 54 additions & 0 deletions fastapi/app/db/migrations/versions/20220104_110934_student.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""student

Revision ID: d0c79969f425
Revises: 8bf6f6f70d43
Create Date: 2022-01-04 11:09:34.821871+09:00

"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = "d0c79969f425"
down_revision = "8bf6f6f70d43"
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table(
"students",
sa.Column("uuid", postgresql.UUID(as_uuid=True), nullable=False),
sa.Column(
"created_at",
postgresql.TIMESTAMP(timezone=True),
server_default=sa.text("CURRENT_TIMESTAMP"),
nullable=False,
comment="登録日時",
),
sa.Column(
"updated_at",
postgresql.TIMESTAMP(timezone=True),
nullable=True,
comment="最終更新日時",
),
sa.Column("is_active", sa.BOOLEAN(), server_default="true", nullable=False),
sa.Column("name", sa.VARCHAR(length=100), nullable=False),
sa.Column("number", sa.INTEGER(), nullable=False),
sa.Column("student_sheet_uuid", postgresql.UUID(as_uuid=True), nullable=False),
sa.CheckConstraint("number > 0"),
sa.ForeignKeyConstraint(
["student_sheet_uuid"], ["student_sheets.uuid"], ondelete="CASCADE"
),
sa.PrimaryKeyConstraint("uuid"),
sa.UniqueConstraint("number", "student_sheet_uuid"),
)
# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table("students")
# ### end Alembic commands ###
Loading