Skip to content
Open
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
46 changes: 36 additions & 10 deletions colyseus-server/test/MyRoom_test.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,57 @@
import assert from "assert";
import { generateKeyPairSync } from "crypto";
import jwt from "jsonwebtoken";
import { ColyseusTestServer, boot } from "@colyseus/testing";

// import your "app.config.ts" file here.
import appConfig from "../src/app.config";
import appConfig, { ServerGlobal } from "../src/app.config";
import { MyRoomState } from "../src/rooms/schema/MyRoomState";

describe("testing your Colyseus app", () => {
let colyseus: ColyseusTestServer;

before(async () => colyseus = await boot(appConfig));
after(async () => colyseus.shutdown());
before(async () => {
const { privateKey, publicKey } = generateKeyPairSync("rsa", { modulusLength: 2048 });

ServerGlobal.publicKey = publicKey.export({ type: "pkcs1", format: "pem" }).toString();

const token = jwt.sign(
{ nickname: "tester", role: "player" },
privateKey.export({ type: "pkcs1", format: "pem" }).toString(),
{
algorithm: "RS256",
subject: "user-1",
expiresIn: "1h",
}
);

colyseus = await boot(appConfig);

// keep token for test scope
(global as any).__TEST_TOKEN__ = token;
});

after(async () => {
delete (global as any).__TEST_TOKEN__;
await colyseus.shutdown();
});

beforeEach(async () => await colyseus.cleanup());

it("connecting into a room", async () => {
// `room` is the server-side Room instance reference.
const room = await colyseus.createRoom<MyRoomState>("my_room", {});

// `client1` is the client-side `Room` instance reference (same as JavaScript SDK)
const client1 = await colyseus.connectTo(room);
const client1 = await colyseus.connectTo(room, {
token: (global as any).__TEST_TOKEN__,
});

// make your assertions
assert.strictEqual(client1.sessionId, room.clients[0].sessionId);

// wait for state sync
await room.waitForNextPatch();

assert.deepStrictEqual({ mySynchronizedProperty: "Hello world" }, client1.state.toJSON());
const player = client1.state.players[client1.sessionId];
assert.ok(player, "player should be added to room state");
assert.strictEqual(player.x, 0);
assert.strictEqual(player.y, 0);
assert.strictEqual(player.speed, 200);
});
});
3 changes: 2 additions & 1 deletion fastapi-backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from starlette.middleware.sessions import SessionMiddleware

from app.core.config import settings
from app.routers import auth, user, team
from app.routers import auth, user, team, room

app = FastAPI(title=settings.PROJECT_NAME)

Expand All @@ -26,6 +26,7 @@
# Router 등록
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
app.include_router(user.router, prefix="/api", tags=["users"])
app.include_router(room.router, prefix="/api", tags=["rooms"])
app.include_router(team.router, prefix="/api", tags=["team"])


Expand Down
234 changes: 234 additions & 0 deletions fastapi-backend/app/routers/room.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session

from app.db.database import get_db
from app.db.models import User
from app.dependencies.auth import get_current_user
from app.schemas.room_role import RoomRoleListResponse, RoomRoleResponse, RoomRoleUpdateRequest
from app.schemas.room_layout import (
PatchObjectsRequest,
PatchObjectsResponse,
PatchPortalsRequest,
PatchPortalsResponse,
PatchTilesRequest,
PatchTilesResponse,
ReplaceObjectsRequest,
ReplaceObjectsResponse,
ReplacePortalsRequest,
ReplacePortalsResponse,
ReplaceTilesRequest,
ReplaceTilesResponse,
RoomLayoutResponse,
)
from app.schemas.room import RoomCreate, RoomListResponse, RoomResponse, RoomUpdate
from app.services.room_layout_service import RoomLayoutService
from app.services.room_role_service import RoomRoleService
from app.services.room_service import RoomService

router = APIRouter(prefix="/rooms", tags=["rooms"])


@router.get("", response_model=RoomListResponse)
# 룸 목록 조회 API
async def list_rooms(
mine_only: bool = False,
limit: int = 20,
offset: int = 0,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
if limit < 1 or limit > 100:
raise HTTPException(status_code=400, detail="limit must be between 1 and 100")
if offset < 0:
raise HTTPException(status_code=400, detail="offset must be 0 or greater")

room_service = RoomService(db)
total, rooms = room_service.list_rooms(
user=current_user,
limit=limit,
offset=offset,
mine_only=mine_only,
)
return RoomListResponse(total=total, limit=limit, offset=offset, rooms=rooms)


@router.post("", response_model=RoomResponse)
# 룸 생성 API
async def create_room(
room_data: RoomCreate,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
room_service = RoomService(db)
room = room_service.create_room(current_user, room_data)
return room


@router.get("/{room_id}", response_model=RoomResponse)
# 룸 상세 조회 API
async def get_room(
room_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
room_service = RoomService(db)
room = room_service.get_room(room_id, current_user)
return room


@router.patch("/{room_id}", response_model=RoomResponse)
# 룸 수정 API
async def update_room(
room_id: int,
room_data: RoomUpdate,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
if not room_data.model_fields_set:
raise HTTPException(status_code=400, detail="No fields to update")

room_service = RoomService(db)
room = room_service.update_room(room_id, current_user, room_data)
return room


@router.get("/{room_id}/layout", response_model=RoomLayoutResponse)
# 룸 레이아웃 조회 API
async def get_room_layout(
room_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
room_layout_service = RoomLayoutService(db)
return room_layout_service.get_layout(room_id, current_user)


@router.put("/{room_id}/tiles", response_model=ReplaceTilesResponse)
# 룸 타일 일괄 교체 API
async def replace_room_tiles(
room_id: int,
request_data: ReplaceTilesRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
room_layout_service = RoomLayoutService(db)
tiles = room_layout_service.replace_tiles(room_id, current_user, request_data)
return ReplaceTilesResponse(room_id=room_id, tiles=tiles)


@router.patch("/{room_id}/tiles", response_model=PatchTilesResponse)
# 룸 타일 부분 수정 API
async def patch_room_tiles(
room_id: int,
request_data: PatchTilesRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
room_layout_service = RoomLayoutService(db)
tiles = room_layout_service.patch_tiles(room_id, current_user, request_data)
return PatchTilesResponse(room_id=room_id, tiles=tiles)


@router.put("/{room_id}/objects", response_model=ReplaceObjectsResponse)
# 룸 오브젝트 일괄 교체 API
async def replace_room_objects(
room_id: int,
request_data: ReplaceObjectsRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
room_layout_service = RoomLayoutService(db)
objects = room_layout_service.replace_objects(room_id, current_user, request_data)
return ReplaceObjectsResponse(room_id=room_id, objects=objects)


@router.patch("/{room_id}/objects", response_model=PatchObjectsResponse)
# 룸 오브젝트 부분 수정 API
async def patch_room_objects(
room_id: int,
request_data: PatchObjectsRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
room_layout_service = RoomLayoutService(db)
objects = room_layout_service.patch_objects(room_id, current_user, request_data)
return PatchObjectsResponse(room_id=room_id, objects=objects)


@router.put("/{room_id}/portals", response_model=ReplacePortalsResponse)
# 룸 포탈 일괄 교체 API
async def replace_room_portals(
room_id: int,
request_data: ReplacePortalsRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
room_layout_service = RoomLayoutService(db)
portals = room_layout_service.replace_portals(room_id, current_user, request_data)
return ReplacePortalsResponse(room_id=room_id, portals=portals)


@router.patch("/{room_id}/portals", response_model=PatchPortalsResponse)
# 룸 포탈 부분 수정 API
async def patch_room_portals(
room_id: int,
request_data: PatchPortalsRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
room_layout_service = RoomLayoutService(db)
portals = room_layout_service.patch_portals(room_id, current_user, request_data)
return PatchPortalsResponse(room_id=room_id, portals=portals)


@router.get("/{room_id}/roles", response_model=RoomRoleListResponse)
# 룸 역할 목록 조회 API
async def list_room_roles(
room_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
room_role_service = RoomRoleService(db)
roles = room_role_service.list_roles(room_id, current_user)
return RoomRoleListResponse(room_id=room_id, roles=roles)


@router.get("/{room_id}/roles/me", response_model=RoomRoleResponse)
# 내 역할 조회 API
async def get_my_room_role(
room_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
room_role_service = RoomRoleService(db)
return room_role_service.get_role(room_id, current_user.id, current_user)


@router.patch("/{room_id}/roles/{target_user_id}", response_model=RoomRoleResponse)
# 룸 역할 수정 API
async def upsert_room_role(
room_id: int,
target_user_id: int,
request_data: RoomRoleUpdateRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
room_role_service = RoomRoleService(db)
return room_role_service.upsert_role(
room_id=room_id,
target_user_id=target_user_id,
role=request_data.role,
current_user=current_user,
)


@router.get("/{room_id}/roles/{target_user_id}", response_model=RoomRoleResponse)
# 룸 특정 유저 역할 조회 API
async def get_room_role(
room_id: int,
target_user_id: int,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
room_role_service = RoomRoleService(db)
return room_role_service.get_role(room_id, target_user_id, current_user)
47 changes: 47 additions & 0 deletions fastapi-backend/app/schemas/room.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from datetime import datetime
from typing import Optional

from pydantic import BaseModel, Field

from app.db.enums import RoomType, OwnerType


class RoomCreate(BaseModel):
room_type: RoomType
name: str = Field(min_length=1, max_length=50)
owner_type: OwnerType = OwnerType.USER
is_public: bool = False
password: Optional[str] = Field(default=None, min_length=1, max_length=255)
width: int = Field(ge=1)
height: int = Field(ge=1)


class RoomUpdate(BaseModel):
name: Optional[str] = Field(default=None, min_length=1, max_length=50)
is_public: Optional[bool] = None
password: Optional[str] = Field(default=None, min_length=1, max_length=255)
width: Optional[int] = Field(default=None, ge=1)
height: Optional[int] = Field(default=None, ge=1)


class RoomResponse(BaseModel):
id: int
room_type: RoomType
name: str
owner_type: OwnerType
owner_id: Optional[int]
is_public: bool
width: int
height: int
created_at: datetime
updated_at: datetime

class Config:
from_attributes = True


class RoomListResponse(BaseModel):
total: int
limit: int
offset: int
rooms: list[RoomResponse]
Loading