From 9f8f9ba4d4a9bc0bc40c7a39664861d888fd4836 Mon Sep 17 00:00:00 2001 From: Sean-mn Date: Wed, 1 Apr 2026 20:24:54 +0900 Subject: [PATCH 1/7] =?UTF-8?q?fix:=20JoinRoom=20=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EA=B2=80=EC=A6=9D=20=EC=A1=B0=EA=B1=B4?= =?UTF-8?q?=EC=9D=84=20IsPrivate=20=EA=B8=B0=EC=A4=80=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Service/Room/JoinRoomServiceTests.cs | 29 +++++++++++++++++++ .../Domain/Room/Service/JoinRoomService.cs | 6 ++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/PushAndPull/PushAndPull.Test/Service/Room/JoinRoomServiceTests.cs b/PushAndPull/PushAndPull.Test/Service/Room/JoinRoomServiceTests.cs index a44a53a..dcdc9a4 100644 --- a/PushAndPull/PushAndPull.Test/Service/Room/JoinRoomServiceTests.cs +++ b/PushAndPull/PushAndPull.Test/Service/Room/JoinRoomServiceTests.cs @@ -63,6 +63,35 @@ await Assert.ThrowsAsync( } } + public class WhenAPrivateRoomIsJoinedWithoutAPassword + { + private readonly Mock _roomRepositoryMock = new(); + private readonly Mock _passwordHasherMock = new(); + private readonly JoinRoomService _sut; + + private const string RoomCode = "PRIV02"; + + public WhenAPrivateRoomIsJoinedWithoutAPassword() + { + var privateRoom = new EntityRoom(RoomCode, "Private Room", 222UL, 76561198000000001UL, true, "some-hash"); + + _roomRepositoryMock + .Setup(r => r.GetAsync(RoomCode)) + .ReturnsAsync(privateRoom); + + _sut = new JoinRoomService(_roomRepositoryMock.Object, _passwordHasherMock.Object); + } + + [Fact] + public async Task It_ThrowsInvalidOperationExceptionWithPasswordRequiredMessage() + { + var ex = await Assert.ThrowsAsync( + () => _sut.ExecuteAsync(new JoinRoomCommand(RoomCode, null))); + + Assert.Equal("PASSWORD_REQUIRED", ex.Message); + } + } + public class WhenTheWrongPasswordIsProvidedForAPrivateRoom { private readonly Mock _roomRepositoryMock = new(); diff --git a/PushAndPull/PushAndPull/Domain/Room/Service/JoinRoomService.cs b/PushAndPull/PushAndPull/Domain/Room/Service/JoinRoomService.cs index e834deb..96835ff 100644 --- a/PushAndPull/PushAndPull/Domain/Room/Service/JoinRoomService.cs +++ b/PushAndPull/PushAndPull/Domain/Room/Service/JoinRoomService.cs @@ -28,12 +28,12 @@ public async Task ExecuteAsync(JoinRoomCommand request) if (room.Status != RoomStatus.Active) throw new RoomNotActiveException(request.RoomCode); - if (request.Password != null) + if (room.IsPrivate) { - if (string.IsNullOrWhiteSpace(request.Password)) + if (request.Password == null) throw new InvalidOperationException("PASSWORD_REQUIRED"); - if (!_passwordHasher.Verify( request.Password, room.PasswordHash!)) + if (!_passwordHasher.Verify(request.Password, room.PasswordHash!)) throw new InvalidOperationException("INVALID_PASSWORD"); } From 10b47454f6e97073c7054d974970183c8e2e9282 Mon Sep 17 00:00:00 2001 From: Sean-mn Date: Wed, 1 Apr 2026 20:24:58 +0900 Subject: [PATCH 2/7] =?UTF-8?q?update:=20Auth=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EC=9D=91=EB=8B=B5=20=ED=98=95=EC=8B=9D?= =?UTF-8?q?=EC=9D=84=20CommonApiResponse=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Auth/Controller/AuthController.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/PushAndPull/PushAndPull/Domain/Auth/Controller/AuthController.cs b/PushAndPull/PushAndPull/Domain/Auth/Controller/AuthController.cs index cd1801a..ed083e9 100644 --- a/PushAndPull/PushAndPull/Domain/Auth/Controller/AuthController.cs +++ b/PushAndPull/PushAndPull/Domain/Auth/Controller/AuthController.cs @@ -24,27 +24,26 @@ ILogoutService logoutService } [HttpPost("login")] - public async Task Login( + public async Task> Login( [FromBody] LoginRequest request ) { var result = await _loginService.ExecuteAsync(new LoginCommand( request.SteamTicket, request.Nickname - ) - ); + )); - return new LoginResponse(result.SessionId); + return CommonApiResponse.Success("로그인되었습니다.", new LoginResponse(result.SessionId)); } [SessionAuthorize] [HttpPost("logout")] - public async Task Logout() + public async Task Logout() { var sessionId = User.GetSessionId(); - await _logoutService.ExecuteAsync( - new LogoutCommand(sessionId) - ); + await _logoutService.ExecuteAsync(new LogoutCommand(sessionId)); + + return CommonApiResponse.Success("로그아웃되었습니다."); } } From c2d27c2fcad3e2f37371bc32c601e25ea4da3a60 Mon Sep 17 00:00:00 2001 From: Sean-mn Date: Wed, 1 Apr 2026 20:25:01 +0900 Subject: [PATCH 3/7] =?UTF-8?q?fix:=20UserRepository=20=EC=8B=A0=EA=B7=9C?= =?UTF-8?q?=20=EC=82=AC=EC=9A=A9=EC=9E=90=20=EC=83=9D=EC=84=B1=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=EC=9D=84=20EF=20Core=20AddAsync=EB=A1=9C=20=EA=B5=90?= =?UTF-8?q?=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Auth/Repository/UserRepository.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/PushAndPull/PushAndPull/Domain/Auth/Repository/UserRepository.cs b/PushAndPull/PushAndPull/Domain/Auth/Repository/UserRepository.cs index 4f2800a..37e444f 100644 --- a/PushAndPull/PushAndPull/Domain/Auth/Repository/UserRepository.cs +++ b/PushAndPull/PushAndPull/Domain/Auth/Repository/UserRepository.cs @@ -23,17 +23,8 @@ public UserRepository(AppDbContext context) public async Task CreateAsync(User user, CancellationToken ct = default) { - await _context.Database.ExecuteSqlRawAsync( - """ - INSERT INTO game_user."user" (steam_id, nickname, created_at, last_login_at) - VALUES ({0}, {1}, {2}, {3}) - ON CONFLICT (steam_id) DO UPDATE - SET nickname = EXCLUDED.nickname, - last_login_at = EXCLUDED.last_login_at - """, - [user.SteamId, user.Nickname, user.CreatedAt, user.LastLoginAt], - ct - ); + await _context.Users.AddAsync(user, ct); + await _context.SaveChangesAsync(ct); } public async Task UpdateAsync(ulong steamId, string nickname, DateTime lastLoginAt, CancellationToken ct = default) From 37936094267d39130835b53b1d6a7cb195b4fedd Mon Sep 17 00:00:00 2001 From: Sean-mn Date: Wed, 1 Apr 2026 20:25:05 +0900 Subject: [PATCH 4/7] =?UTF-8?q?update:=20RoomSummary=EB=A5=BC=20GetRoomRes?= =?UTF-8?q?ponse=EB=A1=9C=20=EB=8C=80=EC=B2=B4=ED=95=98=EA=B3=A0=20MaxPlay?= =?UTF-8?q?ers=20=EC=83=81=EC=88=98=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PushAndPull/PushAndPull/Domain/Room/Entity/Room.cs | 4 +++- .../PushAndPull/Domain/Room/Service/GetAllRoomService.cs | 9 +++++---- .../Domain/Room/Service/Interface/IGetAllRoomService.cs | 6 ++++-- .../PushAndPull/Domain/Room/Service/RoomSummary.cs | 8 -------- 4 files changed, 12 insertions(+), 15 deletions(-) delete mode 100644 PushAndPull/PushAndPull/Domain/Room/Service/RoomSummary.cs diff --git a/PushAndPull/PushAndPull/Domain/Room/Entity/Room.cs b/PushAndPull/PushAndPull/Domain/Room/Entity/Room.cs index df2ba23..3cb9fab 100644 --- a/PushAndPull/PushAndPull/Domain/Room/Entity/Room.cs +++ b/PushAndPull/PushAndPull/Domain/Room/Entity/Room.cs @@ -22,6 +22,8 @@ public class Room public DateTimeOffset CreatedAt { get; set; } public DateTimeOffset? ExpiresAt { get; set; } + private const int DefaultMaxPlayers = 2; + protected Room() { } public Room( @@ -38,7 +40,7 @@ public Room( SteamLobbyId = steamLobbyId; HostSteamId = hostSteamId; CurrentPlayers = 1; - MaxPlayers = 2; + MaxPlayers = DefaultMaxPlayers; IsPrivate = isPrivate; PasswordHash = passwordHash; Status = RoomStatus.Active; diff --git a/PushAndPull/PushAndPull/Domain/Room/Service/GetAllRoomService.cs b/PushAndPull/PushAndPull/Domain/Room/Service/GetAllRoomService.cs index 2e7fb35..87be1c6 100644 --- a/PushAndPull/PushAndPull/Domain/Room/Service/GetAllRoomService.cs +++ b/PushAndPull/PushAndPull/Domain/Room/Service/GetAllRoomService.cs @@ -1,3 +1,4 @@ +using PushAndPull.Domain.Room.Dto.Response; using PushAndPull.Domain.Room.Repository.Interface; using PushAndPull.Domain.Room.Service.Interface; @@ -16,15 +17,15 @@ public async Task ExecuteAsync(CancellationToken ct = default) { var rooms = await _roomRepository.GetAllAsync(ct); - var summaries = rooms - .Select(room => new RoomSummary( - room.RoomName, + var responses = rooms + .Select(room => new GetRoomResponse( room.RoomCode, + room.RoomName, room.CurrentPlayers, room.IsPrivate )) .ToList(); - return new GetAllRoomResult(summaries); + return new GetAllRoomResult(responses); } } diff --git a/PushAndPull/PushAndPull/Domain/Room/Service/Interface/IGetAllRoomService.cs b/PushAndPull/PushAndPull/Domain/Room/Service/Interface/IGetAllRoomService.cs index e6af738..1135e7c 100644 --- a/PushAndPull/PushAndPull/Domain/Room/Service/Interface/IGetAllRoomService.cs +++ b/PushAndPull/PushAndPull/Domain/Room/Service/Interface/IGetAllRoomService.cs @@ -1,3 +1,5 @@ +using PushAndPull.Domain.Room.Dto.Response; + namespace PushAndPull.Domain.Room.Service.Interface; public interface IGetAllRoomService @@ -6,5 +8,5 @@ public interface IGetAllRoomService } public record GetAllRoomResult( - IReadOnlyList Rooms - ); + IReadOnlyList Rooms +); diff --git a/PushAndPull/PushAndPull/Domain/Room/Service/RoomSummary.cs b/PushAndPull/PushAndPull/Domain/Room/Service/RoomSummary.cs deleted file mode 100644 index 67b2348..0000000 --- a/PushAndPull/PushAndPull/Domain/Room/Service/RoomSummary.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace PushAndPull.Domain.Room.Service; - -public record RoomSummary( - string RoomName, - string RoomCode, - int CurrentPlayers, - bool IsPrivate - ); From 1de9df3e3f0f28f1d778a33a24e6f812bece57f1 Mon Sep 17 00:00:00 2001 From: Sean-mn Date: Wed, 1 Apr 2026 20:25:08 +0900 Subject: [PATCH 5/7] =?UTF-8?q?update:=20Room=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EC=9D=91=EB=8B=B5=20=ED=98=95=EC=8B=9D?= =?UTF-8?q?=EC=9D=84=20CommonApiResponse=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Room/Controller/RoomController.cs | 33 +++++++------------ 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/PushAndPull/PushAndPull/Domain/Room/Controller/RoomController.cs b/PushAndPull/PushAndPull/Domain/Room/Controller/RoomController.cs index d308742..59019c8 100644 --- a/PushAndPull/PushAndPull/Domain/Room/Controller/RoomController.cs +++ b/PushAndPull/PushAndPull/Domain/Room/Controller/RoomController.cs @@ -43,54 +43,43 @@ [FromBody] CreateRoomRequest request request.IsPrivate, request.Password, hostSteamId - ) - ); + )); return CommonApiResponse.Created("방이 생성되었습니다.", new CreateRoomResponse(result.RoomCode)); } [HttpGet("{roomCode}")] - public async Task GetRoom( + public async Task> GetRoom( [FromRoute] string roomCode ) { - var result = await _getRoomService.ExecuteAsync( - new GetRoomCommand(roomCode) - ); + var result = await _getRoomService.ExecuteAsync(new GetRoomCommand(roomCode)); - return new GetRoomResponse( + return CommonApiResponse.Success("방 조회 성공.", new GetRoomResponse( result.RoomCode, result.RoomName, result.CurrentPlayers, result.IsPrivate - ); + )); } [HttpGet("all")] - public async Task GetAllRoom(CancellationToken ct) + public async Task> GetAllRoom(CancellationToken ct) { var result = await _getAllRoomService.ExecuteAsync(ct); - return new GetAllRoomResponse( - result.Rooms.Select(r => new GetRoomResponse( - r.RoomCode, - r.RoomName, - r.CurrentPlayers, - r.IsPrivate - )).ToList() - ); + return CommonApiResponse.Success("방 목록 조회 성공.", new GetAllRoomResponse(result.Rooms)); } [SessionAuthorize] [HttpPost("{roomCode}/join")] - public async Task JoinRoom( + public async Task JoinRoom( [FromRoute] string roomCode, [FromBody] JoinRoomRequest request ) { - await _joinRoomService.ExecuteAsync(new JoinRoomCommand( - roomCode, - request.Password) - ); + await _joinRoomService.ExecuteAsync(new JoinRoomCommand(roomCode, request.Password)); + + return CommonApiResponse.Success("방에 참여했습니다."); } } From b871a4c78b5081cbc369c10078d91fb9f3018f48 Mon Sep 17 00:00:00 2001 From: Sean-mn Date: Wed, 1 Apr 2026 20:38:12 +0900 Subject: [PATCH 6/7] =?UTF-8?q?fix:=20JoinRoom=20=EB=B9=84=EB=B0=80?= =?UTF-8?q?=EB=B2=88=ED=98=B8=20=EA=B2=80=EC=A6=9D=EC=9D=84=20IsNullOrWhit?= =?UTF-8?q?eSpace=EB=A1=9C=20=EA=B0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PushAndPull/PushAndPull/Domain/Room/Service/JoinRoomService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PushAndPull/PushAndPull/Domain/Room/Service/JoinRoomService.cs b/PushAndPull/PushAndPull/Domain/Room/Service/JoinRoomService.cs index 96835ff..514276e 100644 --- a/PushAndPull/PushAndPull/Domain/Room/Service/JoinRoomService.cs +++ b/PushAndPull/PushAndPull/Domain/Room/Service/JoinRoomService.cs @@ -30,7 +30,7 @@ public async Task ExecuteAsync(JoinRoomCommand request) if (room.IsPrivate) { - if (request.Password == null) + if (string.IsNullOrWhiteSpace(request.Password)) throw new InvalidOperationException("PASSWORD_REQUIRED"); if (!_passwordHasher.Verify(request.Password, room.PasswordHash!)) From a3514f1b9f786cf61a83bc0127d69c98f62ce829 Mon Sep 17 00:00:00 2001 From: Sean-mn Date: Wed, 1 Apr 2026 20:38:21 +0900 Subject: [PATCH 7/7] =?UTF-8?q?update:=20pr=20=EC=8A=A4=ED=82=AC=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EB=B0=8F=20=ED=97=88=EC=9A=A9=20=EB=8F=84?= =?UTF-8?q?=EA=B5=AC=20=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PushAndPull/.claude/settings.local.json | 3 ++- PushAndPull/.claude/skills/pr/SKILL.md | 15 +++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/PushAndPull/.claude/settings.local.json b/PushAndPull/.claude/settings.local.json index f50f1d1..09b37a3 100644 --- a/PushAndPull/.claude/settings.local.json +++ b/PushAndPull/.claude/settings.local.json @@ -26,7 +26,8 @@ "Skill(commit)", "Skill(db-migrate)", "WebFetch(domain:github.com)", - "WebFetch(domain:raw.githubusercontent.com)" + "WebFetch(domain:raw.githubusercontent.com)", + "Skill(pr)" ] } } diff --git a/PushAndPull/.claude/skills/pr/SKILL.md b/PushAndPull/.claude/skills/pr/SKILL.md index e9412b0..5fbc92f 100644 --- a/PushAndPull/.claude/skills/pr/SKILL.md +++ b/PushAndPull/.claude/skills/pr/SKILL.md @@ -1,7 +1,7 @@ --- name: pr description: Generates a PR title suggestion and body based on the current branch, then creates a GitHub PR. Supports develop/release/feature branches. -allowed-tools: Bash(git log:*), Bash(git diff:*), Bash(git branch:*), Bash(git tag:*), Bash(git checkout:*), Bash(gh pr create:*), Bash(rm:*), Write, AskUserQuestion +allowed-tools: Bash(git log:*), Bash(git diff:*), Bash(git branch:*), Bash(git tag:*), Bash(git checkout:*), Bash(gh pr create:*), Bash(rm:*), Write, Read, AskUserQuestion context: fork --- @@ -114,20 +114,15 @@ rm PR_BODY.md **Step 4. Output** in this format: ``` -## 추천 PR 제목 - -1. [title1] -2. [title2] -3. [title3] - ## PR 본문 (PR_BODY.md에 저장됨) [full body preview] ``` -**Step 5. Ask the user** using AskUserQuestion with a `choices` array: -- Options: the 3 generated titles + "직접 입력" as the last option -- If the user selects "직접 입력", ask a follow-up AskUserQuestion for the custom title +**Step 5. Ask the user** using AskUserQuestion — you MUST call this tool, do NOT print a text prompt: +- `question`: "PR 제목을 선택해주세요." +- `choices`: the 3 generated titles + "직접 입력" as the last option +- If the user selects "직접 입력", immediately call AskUserQuestion again with `question`: "PR 제목을 입력해주세요." **Step 6. Create PR to `{Base Branch}`**