From b984108140d4f933e5ea1cf4ca540a9078ecbcec Mon Sep 17 00:00:00 2001 From: HEKUCHAN Date: Fri, 2 Feb 2024 09:33:15 +0900 Subject: [PATCH 01/20] #24 Add types --- api/api/db/models/room_model.py | 6 +++++- api/api/db/models/user_model.py | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/api/api/db/models/room_model.py b/api/api/db/models/room_model.py index 69d48f1..5e71832 100644 --- a/api/api/db/models/room_model.py +++ b/api/api/db/models/room_model.py @@ -4,6 +4,7 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.ext.declarative import declared_attr from api.db.base import Base @@ -15,4 +16,7 @@ class RoomModel(Base): id: Mapped[UUID] = mapped_column( UUID(as_uuid=True), unique=True, primary_key=True, default=uuid.uuid4 ) - users: Mapped[List[UserModel]] = relationship("UserModel", backref="users") + + @declared_attr + def users(cls): + return relationship(UserModel, back_populates='room') diff --git a/api/api/db/models/user_model.py b/api/api/db/models/user_model.py index da0d668..55f0447 100644 --- a/api/api/db/models/user_model.py +++ b/api/api/db/models/user_model.py @@ -15,3 +15,5 @@ class UserModel(Base): ) is_owner: Mapped[bool] = mapped_column(Boolean, default=False) room_id = mapped_column(UUID(as_uuid=True), ForeignKey("rooms.id")) + +UserModel.room = relationship("RoomModel", back_populates='users') From e4ba3740bb238b62308dbf9a320d2f3fe0424e1b Mon Sep 17 00:00:00 2001 From: HEKUCHAN Date: Fri, 2 Feb 2024 09:33:32 +0900 Subject: [PATCH 02/20] Fix access controll --- .../Camera/Controlls/VideoControlls.tsx | 54 +++++++++++++++++++ web/middleware.ts | 2 +- web/pages/join/[id].tsx | 8 +-- web/pages/room/[id].tsx | 39 +------------- 4 files changed, 58 insertions(+), 45 deletions(-) create mode 100644 web/components/Camera/Controlls/VideoControlls.tsx diff --git a/web/components/Camera/Controlls/VideoControlls.tsx b/web/components/Camera/Controlls/VideoControlls.tsx new file mode 100644 index 0000000..6330ea2 --- /dev/null +++ b/web/components/Camera/Controlls/VideoControlls.tsx @@ -0,0 +1,54 @@ +import MicrophoneButton from "@/components/Camera/Controlls/MicrophoneButton"; +import VideoButton from "@/components/Camera/Controlls/VideoButton"; +import ConfigButton from "@/components/Camera/Controlls/ConfigButton"; +import LeaveButton from "@/components/Camera/Controlls/LeaveButton"; +import ConfigModal from "@/components/Modal/ConfigModal"; +import ChatModal from "@/components/Modal/ChatModal"; +import ChatButton from "@/components/Camera/Controlls/ChatButton"; +import { useRef } from "react"; + + +const VideoControlls = () => { + const buttonClassName = 'mx-1 mb-2' + const buttonSizeClassName = 'h-[3rem] w-[3rem] min-h-[3rem]' + const controllButtonSize = 24 + + const chatModalRef = useRef(null); + const configModalRef = useRef(null); + + return ( +
+ + + configModalRef.current?.showModal()} + /> + chatModalRef.current?.showModal()} + /> + + + +
+ ) +} + +export default VideoControlls; diff --git a/web/middleware.ts b/web/middleware.ts index 3bdf9ac..e1a7242 100644 --- a/web/middleware.ts +++ b/web/middleware.ts @@ -96,7 +96,7 @@ async function middleware(request: NextRequest) { } export const config = { - matcher: ['/room/:path*'], + matcher: [], }; export default middleware; \ No newline at end of file diff --git a/web/pages/join/[id].tsx b/web/pages/join/[id].tsx index 47482dc..352006b 100644 --- a/web/pages/join/[id].tsx +++ b/web/pages/join/[id].tsx @@ -4,7 +4,6 @@ import { userState } from '@/atoms/userState'; import SelfCamera from '@/components/Camera/SelfCamera'; import Link from 'next/link'; import { useRouter } from 'next/router'; -import toast from 'react-hot-toast'; import axios from 'axios'; const Join = () => { @@ -20,12 +19,7 @@ const Join = () => { setUser({...user, username: userName}) } - await axios - .post(`/rooms/${router.query.id}/users/`) - .then(() => router.push(`/room/${router.query.id}`)) - .catch((error) => { - console.log(error) - }) + router.push(`/room/${router.query.id}`) } useEffect(() => { diff --git a/web/pages/room/[id].tsx b/web/pages/room/[id].tsx index 378893d..16f482d 100644 --- a/web/pages/room/[id].tsx +++ b/web/pages/room/[id].tsx @@ -8,13 +8,12 @@ import LeaveButton from "@/components/Camera/Controlls/LeaveButton"; import ConfigModal from "@/components/Modal/ConfigModal"; import ChatModal from "@/components/Modal/ChatModal"; import ChatButton from "@/components/Camera/Controlls/ChatButton"; +import VideoControlls from "@/components/Camera/Controlls/VideoControlls"; const Room = () => { // eslint-disable-next-line react-hooks/exhaustive-deps const users = [{}]; const wrapperRef = useRef(null); - const chatModalRef = useRef(null); - const configModalRef = useRef(null); const [size, setSize] = useState<{ width: number; height: number }>({ width: 0, height: 0, @@ -23,10 +22,6 @@ const Room = () => { const itemPerRow = Math.ceil(Math.sqrt((users?.length ?? 0))); const row = Math.ceil(((users?.length ?? 0)) / itemPerRow); - const buttonClassName = 'mx-1 mb-2' - const buttonSizeClassName = 'h-[3rem] w-[3rem] min-h-[3rem]' - const controllButtonSize = 24 - useEffect(() => { const element = wrapperRef.current; if (!element || !users) return; @@ -57,38 +52,8 @@ const Room = () => { -
- - - configModalRef.current?.showModal()} - /> - chatModalRef.current?.showModal()} - /> - -
+ - - ); }; From 11a0e9c86efd218716cf2fa1adfbfb79dfefd44d Mon Sep 17 00:00:00 2001 From: HEKUCHAN Date: Fri, 2 Feb 2024 09:33:43 +0900 Subject: [PATCH 03/20] #24 Create file --- web/components/Camera/VideoContainer.tsx | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 web/components/Camera/VideoContainer.tsx diff --git a/web/components/Camera/VideoContainer.tsx b/web/components/Camera/VideoContainer.tsx new file mode 100644 index 0000000..e69de29 From e9f4cddf9ce676a55ec307f80e84234a2f2e645d Mon Sep 17 00:00:00 2001 From: HEKUCHAN Date: Fri, 2 Feb 2024 16:47:32 +0900 Subject: [PATCH 04/20] =?UTF-8?q?#24=20=E8=A4=87=E6=95=B0=E3=81=AE?= =?UTF-8?q?=E3=83=AB=E3=83=BC=E3=83=A0=E3=81=AB=E5=8F=82=E5=8A=A0=E5=8F=AF?= =?UTF-8?q?=E8=83=BD=E3=81=AB=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/api/db/dao/room_dao.py | 25 +++++-- api/api/db/dao/user_dao.py | 14 +--- ...82.py => 2024-02-02-06-45_25615d348393.py} | 38 +++++++--- .../versions/2024-02-02-07-05_668743a5e60c.py | 29 ++++++++ .../versions/2024-02-02-07-12_1522526dbc9c.py | 27 ++++++++ api/api/db/models/associations/__init__.py | 0 api/api/db/models/associations/room_user.py | 21 ++++++ api/api/db/models/room_model.py | 22 ++++-- api/api/db/models/user_model.py | 18 +++-- api/api/schemas/room.py | 3 +- api/api/schemas/user.py | 1 - api/api/web/api/rooms/views.py | 69 +++++-------------- api/api/web/api/users/views.py | 38 ++++++++++ 13 files changed, 211 insertions(+), 94 deletions(-) rename api/api/db/migrations/versions/{2024-01-29-08-03_63ccee3c5682.py => 2024-02-02-06-45_25615d348393.py} (60%) create mode 100644 api/api/db/migrations/versions/2024-02-02-07-05_668743a5e60c.py create mode 100644 api/api/db/migrations/versions/2024-02-02-07-12_1522526dbc9c.py create mode 100644 api/api/db/models/associations/__init__.py create mode 100644 api/api/db/models/associations/room_user.py diff --git a/api/api/db/dao/room_dao.py b/api/api/db/dao/room_dao.py index ab4104e..9eb7812 100644 --- a/api/api/db/dao/room_dao.py +++ b/api/api/db/dao/room_dao.py @@ -1,5 +1,6 @@ import uuid from typing import List +from api.db.models.associations.room_user import RoomLinkUser from api.db.models.room_model import RoomModel from api.db.models.user_model import UserModel from fastapi import Depends @@ -13,12 +14,12 @@ class RoomDAO: def __init__(self, session: AsyncSession = Depends(get_db_session)): self.session = session - async def create_room(self): + async def create_room(self, owner_user: UserModel): """Add new room to the database. :returns: if succeed to create room, will return Room object. """ - room = RoomModel() + room = RoomModel(owner_id=owner_user.id) self.session.add(room) await self.session.flush() @@ -36,18 +37,30 @@ async def get_room(self, room_uuid: uuid.UUID) -> RoomModel | None: return user async def add_user(self, room: RoomModel, user: UserModel) -> None: - await self.session.refresh(room, attribute_names=["users"]) - room.users.append(user) + await self.session.refresh(room, attribute_names=["users", "user_associations"]) + if not user in room.users: + room.users.append(user) + await self.session.flush() async def leave_user(self, room: RoomModel, user: UserModel) -> None: await self.session.refresh(room, attribute_names=["users"]) if user in room.users: - room.users = [u for u in room.users if u != user] + room.users.remove(user) + await self.session.flush() async def add_user_by_uuid(self, room_uuid: uuid.UUID, user: UserModel) -> None: room = await self.get_room(room_uuid) await self.session.refresh(room, attribute_names=["users"]) - room.users.append(user) + if (room is not None) and (not user in room.users): + room.users.append(user) + await self.session.flush() + + async def leave_user_by_uuid(self, room_uuid: uuid.UUID, user: UserModel): + room = await self.get_room(room_uuid) + await self.session.refresh(room, attribute_names=["users"]) + if (room is not None) and (user in room.users): + room.users.remove(user) + await self.session.flush() async def get_users(self, room: RoomModel) -> RoomModel: await self.session.refresh(room, attribute_names=["users"]) diff --git a/api/api/db/dao/user_dao.py b/api/api/db/dao/user_dao.py index 3d215bc..cf6d2ea 100644 --- a/api/api/db/dao/user_dao.py +++ b/api/api/db/dao/user_dao.py @@ -1,6 +1,4 @@ import uuid -from typing import Optional -from api.db.models.room_model import RoomModel from api.static import static from api.libs.jwt_token import encode_token @@ -17,16 +15,12 @@ class UserDAO: def __init__(self, session: AsyncSession = Depends(get_db_session)): self.session = session - async def create_user(self, room: RoomModel, is_owner: bool = False): + async def create_user(self): """Add new user to the datebase. :returns: if succeed to create user, will return UserModel object. """ - user = UserModel( - is_owner=is_owner, - room_id=room.id, - ) - user.room = room + user = UserModel() self.session.add(user) await self.session.flush() @@ -47,8 +41,7 @@ def generate_access_token(self, user_model: UserModel) -> str: return encode_token( data={ "token_type": "token", - "user_id": str(user_model.id), - "room_id": str(user_model.room_id), + "user_id": str(user_model.id) }, expires_delta=static.ACCESS_TOKEN_EXPIRE_TIME, ) @@ -58,7 +51,6 @@ def generate_refresh_token(self, user_model: UserModel) -> str: data={ "token_type": "refresh_token", "user_id": str(user_model.id), - "room_id": str(user_model.room_id), }, expires_delta=static.REFRESH_TOKEN_EXPIRE_TIME, ) diff --git a/api/api/db/migrations/versions/2024-01-29-08-03_63ccee3c5682.py b/api/api/db/migrations/versions/2024-02-02-06-45_25615d348393.py similarity index 60% rename from api/api/db/migrations/versions/2024-01-29-08-03_63ccee3c5682.py rename to api/api/db/migrations/versions/2024-02-02-06-45_25615d348393.py index c623f3b..adbacb4 100644 --- a/api/api/db/migrations/versions/2024-01-29-08-03_63ccee3c5682.py +++ b/api/api/db/migrations/versions/2024-02-02-06-45_25615d348393.py @@ -1,15 +1,15 @@ -"""empty message +"""Add Rooms, User, RoomLinkUser Table -Revision ID: 63ccee3c5682 +Revision ID: 25615d348393 Revises: -Create Date: 2024-01-29 08:03:32.262861 +Create Date: 2024-02-02 06:45:34.408905 """ import sqlalchemy as sa from alembic import op # revision identifiers, used by Alembic. -revision = "63ccee3c5682" +revision = "25615d348393" down_revision = None branch_labels = None depends_on = None @@ -18,7 +18,7 @@ def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.create_table( - "rooms", + "users", sa.Column("id", sa.UUID(), nullable=False), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True), @@ -26,24 +26,40 @@ def upgrade() -> None: sa.UniqueConstraint("id"), ) op.create_table( - "users", + "rooms", sa.Column("id", sa.UUID(), nullable=False), - sa.Column("is_owner", sa.Boolean(), nullable=False), - sa.Column("room_id", sa.UUID(), nullable=True), + sa.Column("owner_id", sa.UUID(), nullable=False), sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True), sa.ForeignKeyConstraint( - ["room_id"], - ["rooms.id"], + ["owner_id"], + ["users.id"], ), sa.PrimaryKeyConstraint("id"), sa.UniqueConstraint("id"), ) + op.create_table( + "room_link_user", + sa.Column("room_id", sa.UUID(), nullable=False), + sa.Column("user_id", sa.UUID(), nullable=False), + sa.Column("created_at", sa.DateTime(timezone=True), nullable=False), + sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True), + sa.ForeignKeyConstraint( + ["room_id"], + ["rooms.id"], + ), + sa.ForeignKeyConstraint( + ["user_id"], + ["users.id"], + ), + sa.PrimaryKeyConstraint("room_id", "user_id"), + ) # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("users") + op.drop_table("room_link_user") op.drop_table("rooms") + op.drop_table("users") # ### end Alembic commands ### diff --git a/api/api/db/migrations/versions/2024-02-02-07-05_668743a5e60c.py b/api/api/db/migrations/versions/2024-02-02-07-05_668743a5e60c.py new file mode 100644 index 0000000..06132c2 --- /dev/null +++ b/api/api/db/migrations/versions/2024-02-02-07-05_668743a5e60c.py @@ -0,0 +1,29 @@ +"""Add Rooms, User, RoomLinkUser Table + +Revision ID: 668743a5e60c +Revises: 25615d348393 +Create Date: 2024-02-02 07:05:50.008640 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "668743a5e60c" +down_revision = "25615d348393" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_unique_constraint(None, "rooms", ["id"]) + op.create_unique_constraint(None, "users", ["id"]) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_constraint(None, "users", type_="unique") + op.drop_constraint(None, "rooms", type_="unique") + # ### end Alembic commands ### diff --git a/api/api/db/migrations/versions/2024-02-02-07-12_1522526dbc9c.py b/api/api/db/migrations/versions/2024-02-02-07-12_1522526dbc9c.py new file mode 100644 index 0000000..274d7c1 --- /dev/null +++ b/api/api/db/migrations/versions/2024-02-02-07-12_1522526dbc9c.py @@ -0,0 +1,27 @@ +"""Add Rooms, User, RoomLinkUser Table + +Revision ID: 1522526dbc9c +Revises: 668743a5e60c +Create Date: 2024-02-02 07:12:56.460343 + +""" +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision = "1522526dbc9c" +down_revision = "668743a5e60c" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### diff --git a/api/api/db/models/associations/__init__.py b/api/api/db/models/associations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/api/db/models/associations/room_user.py b/api/api/db/models/associations/room_user.py new file mode 100644 index 0000000..544a693 --- /dev/null +++ b/api/api/db/models/associations/room_user.py @@ -0,0 +1,21 @@ +import uuid +from api.db.base import Base +from typing import TYPE_CHECKING +from sqlalchemy import ForeignKey +from sqlalchemy.orm import Mapped, mapped_column, relationship + +if TYPE_CHECKING: + from api.db.models.room_model import RoomModel + from api.db.models.user_model import UserModel + +class RoomLinkUser(Base): + __tablename__ = "room_link_user" + + room_id: Mapped[uuid.UUID] = mapped_column( + ForeignKey("rooms.id"), primary_key=True, + ) + user_id: Mapped[uuid.UUID] = mapped_column( + ForeignKey("users.id"), primary_key=True, + ) + room: Mapped["RoomModel"] = relationship("RoomModel", back_populates="user_associations") + user: Mapped["UserModel"] = relationship("UserModel", back_populates="room_associations") diff --git a/api/api/db/models/room_model.py b/api/api/db/models/room_model.py index 5e71832..e260dba 100644 --- a/api/api/db/models/room_model.py +++ b/api/api/db/models/room_model.py @@ -1,13 +1,16 @@ -from typing import List import uuid -from api.db.models.user_model import UserModel +from typing import List +from api.db.models.associations.room_user import RoomLinkUser +from typing import TYPE_CHECKING +from sqlalchemy import ForeignKey from sqlalchemy.orm import Mapped, mapped_column, relationship from sqlalchemy.dialects.postgresql import UUID -from sqlalchemy.ext.declarative import declared_attr from api.db.base import Base +if TYPE_CHECKING: + from api.db.models.user_model import UserModel class RoomModel(Base): @@ -16,7 +19,12 @@ class RoomModel(Base): id: Mapped[UUID] = mapped_column( UUID(as_uuid=True), unique=True, primary_key=True, default=uuid.uuid4 ) - - @declared_attr - def users(cls): - return relationship(UserModel, back_populates='room') + owner_id: Mapped[UUID] = mapped_column(UUID(as_uuid=True), ForeignKey('users.id')) + + users: Mapped[List["UserModel"]] = relationship( + secondary="room_link_user", + back_populates="rooms", + ) + user_associations: Mapped[List[RoomLinkUser]] = relationship( + back_populates='room', + ) diff --git a/api/api/db/models/user_model.py b/api/api/db/models/user_model.py index 55f0447..d9f88bb 100644 --- a/api/api/db/models/user_model.py +++ b/api/api/db/models/user_model.py @@ -1,11 +1,17 @@ +from typing import List import uuid -from sqlalchemy import Boolean, ForeignKey +from typing import TYPE_CHECKING + from sqlalchemy.orm import Mapped, mapped_column, relationship +from api.db.models.associations.room_user import RoomLinkUser from sqlalchemy.dialects.postgresql import UUID from api.db.base import Base +if TYPE_CHECKING: + from api.db.models.room_model import RoomModel + class UserModel(Base): __tablename__ = "users" @@ -13,7 +19,11 @@ class UserModel(Base): id: Mapped[UUID] = mapped_column( UUID(as_uuid=True), unique=True, primary_key=True, default=uuid.uuid4 ) - is_owner: Mapped[bool] = mapped_column(Boolean, default=False) - room_id = mapped_column(UUID(as_uuid=True), ForeignKey("rooms.id")) -UserModel.room = relationship("RoomModel", back_populates='users') + rooms: Mapped[List["RoomModel"]] = relationship( + secondary="room_link_user", + back_populates="users", + ) + room_associations: Mapped[List[RoomLinkUser]] = relationship( + back_populates='user', + ) diff --git a/api/api/schemas/room.py b/api/api/schemas/room.py index 63a1ba2..27984ad 100644 --- a/api/api/schemas/room.py +++ b/api/api/schemas/room.py @@ -8,7 +8,8 @@ class RoomInfo(BaseModel): """DTO for Room model.""" id: uuid.UUID - users: List[UserInfo] + owner_id: uuid.UUID + users: List[UserInfo] = [] created_at: datetime updated_at: datetime diff --git a/api/api/schemas/user.py b/api/api/schemas/user.py index a4c9a9b..da6cad0 100644 --- a/api/api/schemas/user.py +++ b/api/api/schemas/user.py @@ -6,6 +6,5 @@ class UserInfo(BaseModel): """DTO for User model.""" id: uuid.UUID - room_id: uuid.UUID created_at: datetime updated_at: datetime diff --git a/api/api/web/api/rooms/views.py b/api/api/web/api/rooms/views.py index fb000a9..5da50c0 100644 --- a/api/api/web/api/rooms/views.py +++ b/api/api/web/api/rooms/views.py @@ -1,71 +1,34 @@ from typing import Annotated, List import uuid +from api.db.models.user_model import UserModel +from api.dependencies.auth import with_authenticate from api.schemas.room import RoomInfo from api.schemas.token import CredentialsTokens from api.schemas.user import UserInfo -from api.static import static -from api.settings import settings from api.db.dao.room_dao import RoomDAO from api.db.dao.user_dao import UserDAO -from api.db.models.room_model import RoomModel from fastapi import APIRouter, Depends, HTTPException, Response, status, Path -from fastapi.responses import JSONResponse from loguru import logger router = APIRouter() logger = logger.bind(Task="Rooms") -def generate_credentials(user_dao: UserDAO, new_user: UserDAO) -> Response: - access_token = user_dao.generate_access_token(new_user) - refresh_token = user_dao.generate_refresh_token(new_user) - - response = JSONResponse({ - "access_token": access_token, - "refresh_token": refresh_token, - }) - response.set_cookie( - key="access_token", - value=access_token, - max_age=int(static.ACCESS_TOKEN_EXPIRE_TIME.total_seconds()), - secure=settings.is_production, - domain=settings.domain, - samesite="strict", - httponly=True, - ) - response.set_cookie( - key="refresh_token", - value=refresh_token, - max_age=int(static.REFRESH_TOKEN_EXPIRE_TIME.total_seconds()), - secure=settings.is_production, - domain=settings.domain, - samesite="strict", - httponly=True, - ) - - return response - - - -@router.post('/', response_model=CredentialsTokens) +@router.post('/', response_model=RoomInfo) async def create_room( - user_dao: UserDAO = Depends(), room_dao: RoomDAO = Depends(), + user: UserModel = Depends(with_authenticate) ) -> Response: - new_room = await room_dao.create_room() - new_user = await user_dao.create_user(new_room, is_owner=True) - await room_dao.add_user(new_room, new_user) + new_room = await room_dao.create_room(owner_user=user) + new_room = await room_dao.get_users(new_room) - response = generate_credentials(user_dao, new_user) - - return response + return new_room @router.get('/{room_uuid}', response_model=RoomInfo) async def room_details( room_uuid: Annotated[uuid.UUID, Path(title="The uuid of room.")], room_dao: RoomDAO = Depends(), ) -> Response: - print(room_uuid) room = await room_dao.get_room(room_uuid) if room is None: @@ -79,8 +42,9 @@ async def room_details( return room +# TODO: Websocketで実装する予定のため、廃止予定 @router.get('/{room_uuid}/users/', response_model=List[UserInfo]) -async def room_users( +async def get_room_users( room_uuid: Annotated[uuid.UUID, Path(title="The uuid of room.")], room_dao: RoomDAO = Depends(), ) -> Response: @@ -96,11 +60,12 @@ async def room_users( return users -@router.post('/{room_uuid}/users/', response_model=CredentialsTokens) -async def create_user( +# TODO: Websocketで実装する予定のため、廃止予定 +@router.post('/{room_uuid}/users/', response_model=RoomInfo) +async def join_room( room_uuid: Annotated[uuid.UUID, Path(title="The uuid of room.")], - user_dao: UserDAO = Depends(), room_dao: RoomDAO = Depends(), + user: UserModel = Depends(with_authenticate) ) -> Response: room = await room_dao.get_room(room_uuid) @@ -110,9 +75,7 @@ async def create_user( detail="Not found Room." ) - new_user = await user_dao.create_user(room, is_owner=False) - await room_dao.add_user(room, new_user) - - response = generate_credentials(user_dao, new_user) + await room_dao.add_user(room, user) + room = await room_dao.get_users(room) - return response + return room diff --git a/api/api/web/api/users/views.py b/api/api/web/api/users/views.py index 8019236..8fdc3dd 100644 --- a/api/api/web/api/users/views.py +++ b/api/api/web/api/users/views.py @@ -1,10 +1,48 @@ +from api.db.dao.user_dao import UserDAO from api.db.models.user_model import UserModel from api.schemas.user import UserInfo from api.dependencies.auth import with_authenticate +from api.static import static +from api.settings import settings from fastapi import APIRouter, Depends, Response +from fastapi.responses import JSONResponse router = APIRouter() +@router.post('/') +async def create_user( + user_dao: UserDAO = Depends(), +) -> Response: + new_user = await user_dao.create_user() + + access_token = user_dao.generate_access_token(new_user) + refresh_token = user_dao.generate_refresh_token(new_user) + + response = JSONResponse({ + "access_token": access_token, + "refresh_token": refresh_token, + }) + response.set_cookie( + key="access_token", + value=access_token, + max_age=int(static.ACCESS_TOKEN_EXPIRE_TIME.total_seconds()), + secure=settings.is_production, + domain=settings.domain, + samesite="strict", + httponly=True, + ) + response.set_cookie( + key="refresh_token", + value=refresh_token, + max_age=int(static.REFRESH_TOKEN_EXPIRE_TIME.total_seconds()), + secure=settings.is_production, + domain=settings.domain, + samesite="strict", + httponly=True, + ) + + return response + @router.get('/me', response_model=UserInfo) def users_me( user: UserModel = Depends(with_authenticate), From 09e39ad1831dd0071fecfb5f20296ddd766407e8 Mon Sep 17 00:00:00 2001 From: HEKUCHAN Date: Fri, 2 Feb 2024 19:22:31 +0900 Subject: [PATCH 05/20] =?UTF-8?q?#24=20=E6=96=B0=E3=81=97=E3=81=84?= =?UTF-8?q?=E8=AA=8D=E8=A8=BC=E6=96=B9=E6=B3=95=E3=81=AB=E5=90=88=E3=82=8F?= =?UTF-8?q?=E3=81=9B=E3=81=9Fmiddleware=E3=81=AE=E4=BD=9C=E6=88=90?= =?UTF-8?q?=E3=82=92=E3=81=99=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/api/static.py | 4 +- web/@types/axios.d.ts | 8 +++ web/middleware.ts | 116 ++++++++++++++++++++++-------------------- 3 files changed, 72 insertions(+), 56 deletions(-) create mode 100644 web/@types/axios.d.ts diff --git a/api/api/static.py b/api/api/static.py index bbb035e..8ba7bed 100644 --- a/api/api/static.py +++ b/api/api/static.py @@ -1,11 +1,11 @@ import datetime from zoneinfo import ZoneInfo - class Static: """Set the Static variables here.""" ACCESS_TOKEN_EXPIRE_TIME = datetime.timedelta(minutes=15) - REFRESH_TOKEN_EXPIRE_TIME = datetime.timedelta(days=1) + #TODO: Refresh_tokenを無期限化する + REFRESH_TOKEN_EXPIRE_TIME = datetime.timedelta(days=900) TIME_ZONE = ZoneInfo("Asia/Tokyo") static = Static() diff --git a/web/@types/axios.d.ts b/web/@types/axios.d.ts new file mode 100644 index 0000000..28b4d76 --- /dev/null +++ b/web/@types/axios.d.ts @@ -0,0 +1,8 @@ +export type AccessTokenType = string; +export type RefreshTokenType = string; + +export type CredentialsType = { + access_token: AccessTokenType, + refresh_token: RefreshTokenType, +}; + diff --git a/web/middleware.ts b/web/middleware.ts index e1a7242..f503a4d 100644 --- a/web/middleware.ts +++ b/web/middleware.ts @@ -1,20 +1,18 @@ import { NextRequest, NextResponse } from 'next/server'; +import { AccessTokenType, CredentialsType, RefreshTokenType } from './@types/axios'; +import { boolean } from 'yup'; -type RefreshTokenType = string; -type AccessTokenType = string; -const LoginPath = '/'; - -async function refershToken(sessionId: RefreshTokenType): Promise { +const refreshAccessToken = async (refreshToken: RefreshTokenType): Promise => { const token = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/token/refresh`, { method: 'POST', headers: { Accept: 'application/json', - 'Content-Type': 'application/json', + "Content-Type": "application/json" }, body: JSON.stringify({ - session_id: sessionId, + refresh_token: refreshToken, }), - }).then((res) => (res.ok ? res : undefined)); + }).then(res => res.ok ? res : undefined); if (token !== undefined) { const tokenData = await token.json(); @@ -23,7 +21,7 @@ async function refershToken(sessionId: RefreshTokenType): Promise { +const confirmAccessToken = async (accessToken: AccessTokenType): Promise => { const isSucceeded = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/users/me`, { method: 'GET', headers: { @@ -31,72 +29,82 @@ async function checkAuth(accessToken: AccessTokenType): Promise { 'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}`, }, - }).then(async (res) => res.ok); + }).then(async res => res.ok); - return isSucceeded; + return isSucceeded } -function redirectToLoginPage(request: NextRequest): NextResponse { - return NextResponse.redirect(new URL(LoginPath, request.url)); +const createNewUser = async (): Promise => { + const credentials = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/users`, { + method: 'POST', + headers: { + Accept: 'application/json', + "Content-Type": "application/json" + } + }).then(res => res.ok ? res : undefined); + + if (credentials !== undefined) { + const credentialsData = await credentials.json(); + return credentialsData as CredentialsType + } + return undefined; } -function redirectToHomePage(request: NextRequest): NextResponse { - return NextResponse.redirect(new URL('/', request.url)); +const whenNotAuthenticated = async (request: NextRequest ,response: NextResponse): Promise => { + const credentials = await createNewUser() + if (credentials !== undefined) { + console.log(credentials) + response.cookies.set({ + name: 'refresh_token', + value: credentials.refresh_token, + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict', + domain: request.nextUrl.domainLocale?.domain, + }); + } + return response } -async function middleware(request: NextRequest) { + +const middleware = async (request: NextRequest) => { const response = await NextResponse.next(); - const refreshToken: RefreshTokenType | undefined = request.cookies.get('session_id')?.value; - let accessToken: AccessTokenType | undefined = request.cookies.get('access_token')?.value; - console.log(request.nextUrl.pathname); - if (request.nextUrl.pathname === LoginPath && accessToken !== undefined) { - if (await checkAuth(accessToken)) { - return redirectToHomePage(request); + const refreshToken: RefreshTokenType | undefined = request.cookies.get("refresh_token")?.value; + let accessToken: RefreshTokenType | undefined = request.cookies.get("access_token")?.value; + + const isAuthenticated = async () => { + if (accessToken !== undefined && await confirmAccessToken(accessToken)) { + return true; } if (refreshToken !== undefined) { - accessToken = await refershToken(refreshToken); - console.log(accessToken); - if (accessToken !== undefined && (await checkAuth(accessToken))) { - return redirectToHomePage(request); + accessToken = await refreshAccessToken(refreshToken); + + if (accessToken !== undefined && await confirmAccessToken(accessToken)) { + response.cookies.set({ + name: 'access_token', + value: accessToken, + httpOnly: true, + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict', + domain: request.nextUrl.domainLocale?.domain, + }); + return true } } + return false; } - // If accessToken is not validated or null. - if (accessToken === undefined || (accessToken !== undefined && !(await checkAuth(accessToken)))) { - if (refreshToken !== undefined) { - accessToken = await refershToken(refreshToken); - - // Check the accessToken was refreshed - if ( - accessToken === undefined || - (accessToken !== undefined && !(await checkAuth(accessToken))) - ) { - return redirectToLoginPage(request); - } - response.cookies.set({ - name: 'access_token', - value: accessToken, - httpOnly: true, - secure: process.env.NODE_ENV === 'production', - sameSite: 'strict', - domain: request.nextUrl.domainLocale?.domain, - }); - } - - if (request.nextUrl.pathname !== LoginPath) { - return redirectToLoginPage(request); - } - return response; + if (!(await isAuthenticated())) { + return await whenNotAuthenticated(request, response) } - return response; + return response } export const config = { - matcher: [], + matcher: ['/((?!api|_next/static|favicon.ico).*)',], }; export default middleware; \ No newline at end of file From d5c0e0a0ef864d040d49179db2acfd5518dc5fff Mon Sep 17 00:00:00 2001 From: HEKUCHAN Date: Fri, 2 Feb 2024 19:24:13 +0900 Subject: [PATCH 06/20] Fix to response cookie --- web/middleware.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/middleware.ts b/web/middleware.ts index f503a4d..f0d3c61 100644 --- a/web/middleware.ts +++ b/web/middleware.ts @@ -90,7 +90,7 @@ const middleware = async (request: NextRequest) => { sameSite: 'strict', domain: request.nextUrl.domainLocale?.domain, }); - return true + return response } } return false; From c12a1510dc0c40c61f1ce741b03e8924e980259f Mon Sep 17 00:00:00 2001 From: HEKUCHAN Date: Fri, 2 Feb 2024 19:25:46 +0900 Subject: [PATCH 07/20] Delete console.log function --- web/middleware.ts | 1 - web/pages/index.tsx | 2 -- web/pages/join/[id].tsx | 1 - 3 files changed, 4 deletions(-) diff --git a/web/middleware.ts b/web/middleware.ts index f0d3c61..311dfa4 100644 --- a/web/middleware.ts +++ b/web/middleware.ts @@ -53,7 +53,6 @@ const createNewUser = async (): Promise => { const whenNotAuthenticated = async (request: NextRequest ,response: NextResponse): Promise => { const credentials = await createNewUser() if (credentials !== undefined) { - console.log(credentials) response.cookies.set({ name: 'refresh_token', value: credentials.refresh_token, diff --git a/web/pages/index.tsx b/web/pages/index.tsx index 243a8c2..754dde2 100644 --- a/web/pages/index.tsx +++ b/web/pages/index.tsx @@ -49,11 +49,9 @@ const Home = () => { switch (error.response.status) { case 404: case 422: - console.log(error.response.status) invalidRoomToast('notFound') break default: - console.log(error) invalidRoomToast('error') break } diff --git a/web/pages/join/[id].tsx b/web/pages/join/[id].tsx index 352006b..6100c27 100644 --- a/web/pages/join/[id].tsx +++ b/web/pages/join/[id].tsx @@ -34,7 +34,6 @@ const Join = () => { router.push('/') break; default: - console.log(error) break; } } From 9715cf7ee2c8872a3dedeccde135924d134cc0ee Mon Sep 17 00:00:00 2001 From: HEKUCHAN Date: Fri, 2 Feb 2024 19:37:59 +0900 Subject: [PATCH 08/20] #24 Delete unused import --- web/pages/room/[id].tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/web/pages/room/[id].tsx b/web/pages/room/[id].tsx index 16f482d..8e00fb1 100644 --- a/web/pages/room/[id].tsx +++ b/web/pages/room/[id].tsx @@ -1,13 +1,6 @@ import SelfCamera from "@/components/Camera/SelfCamera"; import { useEffect, useRef, useState } from "react"; import { videoMinimumSize } from '@/utils/static'; -import MicrophoneButton from "@/components/Camera/Controlls/MicrophoneButton"; -import VideoButton from "@/components/Camera/Controlls/VideoButton"; -import ConfigButton from "@/components/Camera/Controlls/ConfigButton"; -import LeaveButton from "@/components/Camera/Controlls/LeaveButton"; -import ConfigModal from "@/components/Modal/ConfigModal"; -import ChatModal from "@/components/Modal/ChatModal"; -import ChatButton from "@/components/Camera/Controlls/ChatButton"; import VideoControlls from "@/components/Camera/Controlls/VideoControlls"; const Room = () => { From 3aa18af29dcbebb7182b3a8f58eab4fadaefe05c Mon Sep 17 00:00:00 2001 From: HEKUCHAN Date: Fri, 2 Feb 2024 19:38:21 +0900 Subject: [PATCH 09/20] =?UTF-8?q?#24=20=E9=80=9A=E8=A9=B1=E3=81=AB?= =?UTF-8?q?=E5=85=A5=E3=81=A3=E3=81=A6=E3=82=8B=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E3=82=92=E7=AE=A1=E7=90=86=E3=81=99=E3=82=8BRecoil?= =?UTF-8?q?=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/@types/state.d.ts | 14 +++++++++++--- web/atoms/{userState.ts => userProfileState.ts} | 13 +++++++------ web/atoms/usersState.ts | 7 +++++++ .../Modal/ConfigModal/GeneralContent.tsx | 11 +++++------ web/pages/join/[id].tsx | 10 +++++----- 5 files changed, 35 insertions(+), 20 deletions(-) rename web/atoms/{userState.ts => userProfileState.ts} (58%) create mode 100644 web/atoms/usersState.ts diff --git a/web/@types/state.d.ts b/web/@types/state.d.ts index 5fbdc3e..26dd86a 100644 --- a/web/@types/state.d.ts +++ b/web/@types/state.d.ts @@ -1,14 +1,22 @@ -type muteControlsType = { +import { UUID } from "crypto"; + +export type muteControlsType = { camera: boolean; microphone: boolean; } -type DefaultDevicesType = { +export type DefaultDevicesType = { microphone?: MediaDeviceInfo; speaker?: MediaDeviceInfo; camera?: MediaDeviceInfo; } -type UserType = { +export type UserProfileType = { username?: string; } + +export type UserType = { + userId: string; + username: string; + type: "offer" | "answer" +} diff --git a/web/atoms/userState.ts b/web/atoms/userProfileState.ts similarity index 58% rename from web/atoms/userState.ts rename to web/atoms/userProfileState.ts index 7f32011..71c86fc 100644 --- a/web/atoms/userState.ts +++ b/web/atoms/userProfileState.ts @@ -1,17 +1,18 @@ +import { UserProfileType } from '@/@types/state'; import { atom } from 'recoil'; -const LOCAL_STORAGE_NAME = 'userInfo'; +const LOCAL_STORAGE_NAME = 'userProfile'; const getUserInfo = () => { if (typeof window === 'undefined') { - return {} as UserType; + return {} as UserProfileType; } const user = window.localStorage.getItem(LOCAL_STORAGE_NAME); - return user ? JSON.parse(user) as UserType : {} as UserType; + return user ? JSON.parse(user) as UserProfileType : {} as UserProfileType; } -const saveItemToLocalStorage = (value: UserType) => { +const saveItemToLocalStorage = (value: UserProfileType) => { if (typeof window === 'undefined') { return; } @@ -19,8 +20,8 @@ const saveItemToLocalStorage = (value: UserType) => { window.localStorage.setItem(LOCAL_STORAGE_NAME, JSON.stringify(value)); } -export const userState = atom({ - key: 'userState', +export const userProfileState = atom({ + key: 'userProfileState', default: getUserInfo(), effects: [ ({ onSet }) => { diff --git a/web/atoms/usersState.ts b/web/atoms/usersState.ts new file mode 100644 index 0000000..80052ae --- /dev/null +++ b/web/atoms/usersState.ts @@ -0,0 +1,7 @@ +import { UserType } from '@/@types/state'; +import { atom } from 'recoil'; + +export const usersState = atom({ + key: 'usersState', + default: [], +}) \ No newline at end of file diff --git a/web/components/Modal/ConfigModal/GeneralContent.tsx b/web/components/Modal/ConfigModal/GeneralContent.tsx index 6215e35..b70d6a1 100644 --- a/web/components/Modal/ConfigModal/GeneralContent.tsx +++ b/web/components/Modal/ConfigModal/GeneralContent.tsx @@ -1,20 +1,19 @@ -import { userState } from "@/atoms/userState"; +import { userProfileState } from "@/atoms/userProfileState"; import { useState } from "react"; import { useRecoilState } from "recoil"; const GeneralContent = () => { - const [user, setUser] = useRecoilState(userState); - const [userName, setUserName] = useState(user.username || ''); + const [userProfile, setUserProfile] = useRecoilState(userProfileState); + const [userName, setUserName] = useState(userProfile.username || ''); const onBlur = () => { if (userName === '' || userName.length > 20) { - return setUser({...user, username: 'ゲスト'}) + return setUserProfile({...userProfile, username: 'ゲスト'}) } - setUser({...user, username: userName}) + setUserProfile({...userProfile, username: userName}) }; - return (