강동현
Builds for 1 pipeline passed in 8 minutes 6 seconds

Merge branch 'feature/login-with-nickname' into develop

...@@ -14,7 +14,8 @@ ...@@ -14,7 +14,8 @@
14 14
15 ## 로그인 15 ## 로그인
16 16
17 -서버에 `login`을 보냅니다. 아직 로그인 로직이 구현되지 않았으므로, 임의로 사용할 `username`만을 포함하여 보내면 됩니다. 17 +서버에 `login`을 보냅니다. 본인이 사용하고 싶은 닉네임을 `nickname`속성에 담아 보내면 됩니다. 닉네임은 공백이 허용되지 않고 최대 12글자입니다. 만약 이 조건을 만족하지 않는다면 요청에 실패하고 사유가 전달됩니다. 요청에 성공한다면 문자열이 반환되는데, 이는 본인의 고유한 `uuid`값입니다. 앞으로 사용되는 모든 `username` 속성에 이 값을 사용해주세요. **`username`은 유저의 이름이 아니라 고유한 `uuid`를 의미하고, `nickname`은 고유하지 않을 수 있습니다!**
18 +상대방의 닉네임은 방 접속시에 얻을 수 있습니다. 오직 UI에 보여주기 위한 용도로만 사용되며, 실제 처리는 모두 `username`을 통해 이뤄집니다.
18 19
19 ## 로비 20 ## 로비
20 21
...@@ -24,6 +25,8 @@ ...@@ -24,6 +25,8 @@
24 25
25 새로운 방을 만들고 싶다면 `createRoom`을 보내면 됩니다. 방의 이름을 `name` 속성에 담아 보내야 합니다. 방의 이름은 공백일 수 없고, 최대 30자까지 가능합니다. 방을 만드는데 성공했다면 방에 자동으로 접속되고 방장을 맡게 됩니다. 이때 반환값은 `joinRoom`과 동일한 `RoomInfo`를 받습니다. 26 새로운 방을 만들고 싶다면 `createRoom`을 보내면 됩니다. 방의 이름을 `name` 속성에 담아 보내야 합니다. 방의 이름은 공백일 수 없고, 최대 30자까지 가능합니다. 방을 만드는데 성공했다면 방에 자동으로 접속되고 방장을 맡게 됩니다. 이때 반환값은 `joinRoom`과 동일한 `RoomInfo`를 받습니다.
26 27
28 +`RoomInfo`에는 각 유저의 닉네임을 `nickname` 속성으로 담고 있습니다.
29 +
27 ### 방 30 ### 방
28 31
29 방에 접속중인 유저의 목록에 변화가 생기면, `updateRoomUser`가 수신됩니다. `state`는 다음 3가지 값 중 하나의 값을 가집니다. 32 방에 접속중인 유저의 목록에 변화가 생기면, `updateRoomUser`가 수신됩니다. `state`는 다음 3가지 값 중 하나의 값을 가집니다.
...@@ -32,6 +35,8 @@ ...@@ -32,6 +35,8 @@
32 - `updated`: 기존 유저의 정보가 업데이트되었습니다. 해당 유저의 방장, 준비 여부가 변경되면 수신됩니다. 35 - `updated`: 기존 유저의 정보가 업데이트되었습니다. 해당 유저의 방장, 준비 여부가 변경되면 수신됩니다.
33 - `removed`: 유저가 방에서 퇴장하였습니다. 36 - `removed`: 유저가 방에서 퇴장하였습니다.
34 37
38 +해당 메세지에는 변화된 유저를 특정하기 위한 `username`이 포함되어 있고, 그 유저의 닉네임인 `nickname`, 방장 여부인 `admin`, 준비 여부인 `ready`를 포함하고 있습니다.
39 +
35 유저가 채팅을 입력했다면 `chat`을 보내면 됩니다. 다른 사람이 채팅을 입력한 경우에도 `chat`이 수신됩니다(`ServerInboundMessage<"chat">``ServerOutboundMessage<"chat">`이 다르게 정의되었다는 점에 유의하세요). 자신이 보낸 채팅에 대해서는 수신되지 않으므로 이 경우 오프라인으로 메세지를 추가하여야 합니다. 채팅 문자열의 양 끝 공백을 제거했을 때 문자열이 빈 문자열이거나, 채팅 문자열의 길이가 300을 초과하는 경우 요청이 실패 처리됩니다. 40 유저가 채팅을 입력했다면 `chat`을 보내면 됩니다. 다른 사람이 채팅을 입력한 경우에도 `chat`이 수신됩니다(`ServerInboundMessage<"chat">``ServerOutboundMessage<"chat">`이 다르게 정의되었다는 점에 유의하세요). 자신이 보낸 채팅에 대해서는 수신되지 않으므로 이 경우 오프라인으로 메세지를 추가하여야 합니다. 채팅 문자열의 양 끝 공백을 제거했을 때 문자열이 빈 문자열이거나, 채팅 문자열의 길이가 300을 초과하는 경우 요청이 실패 처리됩니다.
36 41
37 방을 나가고 싶으면 `leaveRoom`을 보내면 됩니다. 42 방을 나가고 싶으면 `leaveRoom`을 보내면 됩니다.
......
...@@ -38,6 +38,7 @@ export const RoomInfoRecord = Record({ ...@@ -38,6 +38,7 @@ export const RoomInfoRecord = Record({
38 users: Array( 38 users: Array(
39 Record({ 39 Record({
40 username: String, 40 username: String,
41 + nickname: String,
41 admin: Boolean, 42 admin: Boolean,
42 ready: Boolean, 43 ready: Boolean,
43 }) 44 })
......
...@@ -22,7 +22,7 @@ import { ...@@ -22,7 +22,7 @@ import {
22 // 'result' 속성은 서버 요청 결과에만 포함되는 특별한 속성입니다. 22 // 'result' 속성은 서버 요청 결과에만 포함되는 특별한 속성입니다.
23 export class ServerInboundMessageRecordMap { 23 export class ServerInboundMessageRecordMap {
24 // 로그인을 시도합니다. 24 // 로그인을 시도합니다.
25 - login = Record({ username: String }); 25 + login = Record({ nickname: String, result: String });
26 26
27 // 방 목록을 요청합니다. 27 // 방 목록을 요청합니다.
28 roomList = Record({ 28 roomList = Record({
...@@ -94,6 +94,7 @@ interface ServerOutboundMessageMap { ...@@ -94,6 +94,7 @@ interface ServerOutboundMessageMap {
94 state: "added" | "updated" | "removed"; 94 state: "added" | "updated" | "removed";
95 user: { 95 user: {
96 username: string; 96 username: string;
97 + nickname: string;
97 admin: boolean; 98 admin: boolean;
98 ready: boolean; 99 ready: boolean;
99 }; 100 };
......
...@@ -77,7 +77,11 @@ export class Connection { ...@@ -77,7 +77,11 @@ export class Connection {
77 private handleLogin( 77 private handleLogin(
78 message: ServerInboundMessage<"login"> 78 message: ServerInboundMessage<"login">
79 ): ServerResponse<"login"> { 79 ): ServerResponse<"login"> {
80 - this.user = new User(message.username, this); 80 + if (message.nickname.length > 12 || message.nickname.trim().length === 0) {
81 + return { ok: false };
82 + }
83 +
84 + this.user = new User(message.nickname, this);
81 // console.log(`User ${message.username} has logged in!`); 85 // console.log(`User ${message.username} has logged in!`);
82 86
83 return { ok: true }; 87 return { ok: true };
......
...@@ -106,6 +106,7 @@ export class Room { ...@@ -106,6 +106,7 @@ export class Room {
106 state: "added", 106 state: "added",
107 user: { 107 user: {
108 username: user.username, 108 username: user.username,
109 + nickname: user.nickname,
109 admin: user === this.admin, 110 admin: user === this.admin,
110 ready: this.usersReady.includes(user), 111 ready: this.usersReady.includes(user),
111 }, 112 },
...@@ -128,6 +129,7 @@ export class Room { ...@@ -128,6 +129,7 @@ export class Room {
128 state: "removed", 129 state: "removed",
129 user: { 130 user: {
130 username: user.username, 131 username: user.username,
132 + nickname: user.nickname,
131 admin: user === this.admin, 133 admin: user === this.admin,
132 ready: this.usersReady.includes(user), 134 ready: this.usersReady.includes(user),
133 }, 135 },
...@@ -221,6 +223,7 @@ export class Room { ...@@ -221,6 +223,7 @@ export class Room {
221 state: "updated", 223 state: "updated",
222 user: { 224 user: {
223 username: user.username, 225 username: user.username,
226 + nickname: user.nickname,
224 admin: this.isAdmin(user), 227 admin: this.isAdmin(user),
225 ready: this.isReady(user), 228 ready: this.isReady(user),
226 }, 229 },
...@@ -244,6 +247,7 @@ export class Room { ...@@ -244,6 +247,7 @@ export class Room {
244 users: this.users.map((u) => { 247 users: this.users.map((u) => {
245 return { 248 return {
246 username: u.username, 249 username: u.username,
250 + nickname: u.nickname,
247 admin: this.isAdmin(u), 251 admin: this.isAdmin(u),
248 ready: this.usersReady.includes(u), 252 ready: this.usersReady.includes(u),
249 }; 253 };
......
...@@ -24,16 +24,14 @@ describe("방장", () => { ...@@ -24,16 +24,14 @@ describe("방장", () => {
24 const response = socket2.test("joinRoom", { uuid: room.uuid }); 24 const response = socket2.test("joinRoom", { uuid: room.uuid });
25 expect(response.ok).eq(true); 25 expect(response.ok).eq(true);
26 expect(room.admin).eq(user1); 26 expect(room.admin).eq(user1);
27 - expect(response.result?.users[0]).deep.eq({ 27 +
28 - username: user1.username, 28 + expect(response.result?.users[0].nickname).eq(user1.nickname);
29 - admin: true, 29 + expect(response.result?.users[0].admin).eq(true);
30 - ready: false, 30 + expect(response.result?.users[0].ready).eq(false);
31 - }); 31 +
32 - expect(response.result?.users[1]).deep.eq({ 32 + expect(response.result?.users[1].nickname).eq(user2.nickname);
33 - username: user2.username, 33 + expect(response.result?.users[1].admin).eq(false);
34 - admin: false, 34 + expect(response.result?.users[1].ready).eq(false);
35 - ready: false,
36 - });
37 }); 35 });
38 it("방장이 나가면 방장이 인계됩니다", () => { 36 it("방장이 나가면 방장이 인계됩니다", () => {
39 const { 37 const {
......
...@@ -14,6 +14,7 @@ describe("방 입장", () => { ...@@ -14,6 +14,7 @@ describe("방 입장", () => {
14 it("방에 입장합니다", () => { 14 it("방에 입장합니다", () => {
15 const { 15 const {
16 sockets: [socket], 16 sockets: [socket],
17 + users: [user],
17 rooms: [room], 18 rooms: [room],
18 } = prepareUsersEmptyRooms(1, 1); 19 } = prepareUsersEmptyRooms(1, 1);
19 20
...@@ -23,16 +24,17 @@ describe("방 입장", () => { ...@@ -23,16 +24,17 @@ describe("방 입장", () => {
23 expect(response.result?.name).eq(room.name); 24 expect(response.result?.name).eq(room.name);
24 expect(response.result?.maxUsers).eq(room.maxUsers); 25 expect(response.result?.maxUsers).eq(room.maxUsers);
25 expect(response.result?.users?.length).eq(1); 26 expect(response.result?.users?.length).eq(1);
26 - expect(response.result?.users[0]?.username).eq("guest1"); 27 + expect(response.result?.users[0]?.nickname).eq(user.nickname);
27 }); 28 });
28 it("방에 입장하면 유저 목록이 업데이트 됩니다", () => { 29 it("방에 입장하면 유저 목록이 업데이트 됩니다", () => {
29 const { 30 const {
30 sockets: [socket1, socket2], 31 sockets: [socket1, socket2],
32 + users: [user1, user2],
31 } = prepareJoinedRoom(2); 33 } = prepareJoinedRoom(2);
32 34
33 const updated = socket1.socket.received("updateRoomUser"); 35 const updated = socket1.socket.received("updateRoomUser");
34 expect(updated.state).eq("added"); 36 expect(updated.state).eq("added");
35 - expect(updated.user.username).eq("guest2"); 37 + expect(updated.user.nickname).eq(user2.nickname);
36 }); 38 });
37 it("방에 이미 입장한 상태에서 다른 방에 입장할 수 없습니다", () => { 39 it("방에 이미 입장한 상태에서 다른 방에 입장할 수 없습니다", () => {
38 const { 40 const {
......
...@@ -3,10 +3,22 @@ import { RoomManager } from "../room/RoomManager"; ...@@ -3,10 +3,22 @@ import { RoomManager } from "../room/RoomManager";
3 import { SocketTester } from "./util/SocketTester"; 3 import { SocketTester } from "./util/SocketTester";
4 4
5 describe("로그인", () => { 5 describe("로그인", () => {
6 - it("로그인합니다", () => { 6 + it("닉네임으로 로그인합니다", () => {
7 const roomManager = new RoomManager(); 7 const roomManager = new RoomManager();
8 const socket = new SocketTester(roomManager); 8 const socket = new SocketTester(roomManager);
9 - socket.testOk("login", { username: "guest" }); 9 + socket.testOk("login", { nickname: "guest" });
10 - expect(socket.connection.user?.username).eq("guest"); 10 + expect(socket.connection.user?.nickname).eq("guest");
11 + });
12 + it("빈 닉네임으로 로그인할 수 없습니다", () => {
13 + const roomManager = new RoomManager();
14 + const socket = new SocketTester(roomManager);
15 + socket.testNotOk("login", { nickname: "" });
16 + socket.testNotOk("login", { nickname: " " });
17 + });
18 + it("닉네임은 최대 12자입니다", () => {
19 + const roomManager = new RoomManager();
20 + const socket = new SocketTester(roomManager);
21 + socket.testNotOk("login", { nickname: "a".repeat(13) });
22 + socket.testNotOk("login", { nickname: "가".repeat(13) });
11 }); 23 });
12 }); 24 });
......
...@@ -7,12 +7,13 @@ describe("준비", () => { ...@@ -7,12 +7,13 @@ describe("준비", () => {
7 it("방에 입장하면 준비되지 않은 상태입니다", () => { 7 it("방에 입장하면 준비되지 않은 상태입니다", () => {
8 const { 8 const {
9 sockets: [socket], 9 sockets: [socket],
10 + users: [user],
10 rooms: [room], 11 rooms: [room],
11 } = prepareUsersEmptyRooms(1, 1); 12 } = prepareUsersEmptyRooms(1, 1);
12 13
13 const response = socket.test("joinRoom", { uuid: room.uuid }); 14 const response = socket.test("joinRoom", { uuid: room.uuid });
14 expect(response.ok).eq(true); 15 expect(response.ok).eq(true);
15 - expect(response.result?.users[0]?.username).eq("guest1"); 16 + expect(response.result?.users[0]?.nickname).eq(user.nickname);
16 expect(response.result?.users[0]?.ready).eq(false); 17 expect(response.result?.users[0]?.ready).eq(false);
17 }); 18 });
18 it("준비 상태를 설정합니다", () => { 19 it("준비 상태를 설정합니다", () => {
...@@ -26,25 +27,19 @@ describe("준비", () => { ...@@ -26,25 +27,19 @@ describe("준비", () => {
26 27
27 socket.testOk("ready", { ready: true }); 28 socket.testOk("ready", { ready: true });
28 expect(room.isReady(user)).eq(true); 29 expect(room.isReady(user)).eq(true);
29 - expect(socket.socket.received("updateRoomUser")).deep.eq({ 30 +
30 - state: "updated", 31 + let response = socket.socket.received("updateRoomUser");
31 - user: { 32 + expect(response.state).eq("updated");
32 - username: user.username, 33 + expect(response.user.nickname).eq(user.nickname);
33 - admin: false, 34 + expect(response.user.ready).eq(true);
34 - ready: true,
35 - },
36 - });
37 35
38 socket.testOk("ready", { ready: false }); 36 socket.testOk("ready", { ready: false });
39 expect(room.isReady(user)).eq(false); 37 expect(room.isReady(user)).eq(false);
40 - expect(socket.socket.received("updateRoomUser")).deep.eq({ 38 +
41 - state: "updated", 39 + response = socket.socket.received("updateRoomUser");
42 - user: { 40 + expect(response.state).eq("updated");
43 - username: user.username, 41 + expect(response.user.nickname).eq(user.nickname);
44 - admin: false, 42 + expect(response.user.ready).eq(false);
45 - ready: false,
46 - },
47 - });
48 }); 43 });
49 it("방장은 준비할 수 없습니다", () => { 44 it("방장은 준비할 수 없습니다", () => {
50 const { 45 const {
......
...@@ -49,8 +49,8 @@ export class SocketTester { ...@@ -49,8 +49,8 @@ export class SocketTester {
49 return this.connection.handleRaw(obj); 49 return this.connection.handleRaw(obj);
50 } 50 }
51 51
52 - public login(username: string): void { 52 + public login(nickname: string): void {
53 - this.testOk("login", { username }); 53 + this.testOk("login", { nickname });
54 expect(this.connection.user !== undefined).eq(true); 54 expect(this.connection.user !== undefined).eq(true);
55 } 55 }
56 56
......
...@@ -3,9 +3,11 @@ import { Connection } from "../connection/Connection"; ...@@ -3,9 +3,11 @@ import { Connection } from "../connection/Connection";
3 import { MessageHandler } from "../message/MessageHandler"; 3 import { MessageHandler } from "../message/MessageHandler";
4 import { Room } from "../room/Room"; 4 import { Room } from "../room/Room";
5 import { RoomManager } from "../room/RoomManager"; 5 import { RoomManager } from "../room/RoomManager";
6 +import { v4 as uuidv4 } from "uuid";
6 7
7 export class User { 8 export class User {
8 - public readonly username: string; 9 + public readonly username: string; // TODO: 실제 역할은 uuid임
10 + public readonly nickname: string;
9 11
10 public readonly connection: Connection; 12 public readonly connection: Connection;
11 13
...@@ -13,8 +15,9 @@ export class User { ...@@ -13,8 +15,9 @@ export class User {
13 15
14 public handler: MessageHandler; 16 public handler: MessageHandler;
15 17
16 - constructor(username: string, connection: Connection) { 18 + constructor(nickname: string, connection: Connection) {
17 - this.username = username; 19 + this.username = uuidv4();
20 + this.nickname = nickname;
18 this.connection = connection; 21 this.connection = connection;
19 this.handler = new MessageHandler({ 22 this.handler = new MessageHandler({
20 roomList: (user, message) => { 23 roomList: (user, message) => {
......