Skip to content

Commit

Permalink
cowork/room: 룸 구현 밑 프론트 서버 연동 완료 (#148)
Browse files Browse the repository at this point in the history
* refactor(Message): Message color가 client의 npm run build를 방해함

* socket cors

* winston 남은 것들 제거

* room, user many to many로 시험적 변경

* feat(room): room 생성 성공. user id가 존재할 경우 방을 만들어줌

* with credential always

* mock user 로그인으로 방 만들기 성공

* user room join 로직 협업 중

* room join

Co-authored-by: Kiwon Kim <[email protected]>
Co-authored-by: Daehyun Kim <[email protected]>

* refactor: axios 부분 수정

axios 요청 하는 부분을 파일로 빼고 try catch를 다 적용 시켰습니다.

* feat(exceptions): 모든 server exception 로깅 for debugging

* 프리티어 성능 향상

Co-authored-by: Kiwon Kim <[email protected]>
Co-authored-by: Seongwoo_Lukaid <[email protected]>

* feat(Room): Room exit 로직 작성 중

* feat: room exit api 컨트롤러

* feat: 모든 익셉션 서버에 로깅하기

* OAuth로 로그인한 유저를 createOrUpdateUser를 통해 1. 디비에 넣거나 2. 이미 존재하면 불러온다.

* feat(tsconfig): 🚑 STRICT typescript

tsconfig strictNullChecks = true

* refactor: chat에서 socket연결 로직 분리 (#142)

* feat: 프론트 룸 exit 로직

* feat(room): exit room + api

* fix(joinRoom): joinroom api 수정

* fix(ts): server - strict nullcheck 이전에 작성된 성주님의 problem api 수정

* 성주님 피드백대로 findUserByProviderWithRooms 만들기

* soft-remove cascade로 성공

* 이걸 안 해주면 infinite recursion으로 jsonify

* bugfix: 다대다 관계 오류로 안 만들어지는 버그 수정

---------

Co-authored-by: Seongwoo_Lukaid <[email protected]>
Co-authored-by: Kiwon Kim <[email protected]>
Co-authored-by: kiuuon <[email protected]>
Co-authored-by: Seongwoo_Lukaid <[email protected]>
Co-authored-by: Kiwon Kim <[email protected]>
  • Loading branch information
6 people authored Dec 5, 2023
1 parent efc0d1f commit 1e2a6b3
Show file tree
Hide file tree
Showing 35 changed files with 549 additions and 274 deletions.
19 changes: 11 additions & 8 deletions client/src/apis/Auth.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import axios from 'axios';
import { CreateUser } from '../types/CreateUserType';

// const config = {
// baseUrl: import.meta.env.VITE_API_BASE_URL as string,
// };

export async function login(): Promise<CreateUser> {
// const response = await axios.post(`${config.baseUrl}/auth/login`, {});
// mock data from /mocks/User.json
const response = await axios.get('/mocks/User.json');
const VITE_BASE_URL = import.meta.env.VITE_BASE_URL as string;

export async function getSession(): Promise<CreateUser> {
const response = await axios.get(`${VITE_BASE_URL}/session`, {
withCredentials: true,
});

return response.data;
}

export async function logout() {
axios.get(`${VITE_BASE_URL}/auth/logout`, { withCredentials: true });
}
19 changes: 8 additions & 11 deletions client/src/apis/createRoom.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import axios from 'axios';
import { RoomCreateType } from '../types/RoomCreateType';

const VITE_BASE_URL = import.meta.env.VITE_BASE_URL as string;

export async function createRoom(): Promise<RoomCreateType | undefined> {
const VITE_BASE_URL = import.meta.env.VITE_BASE_URL as string;
const response = await axios.post(
`${VITE_BASE_URL}/room`,
{},
{ withCredentials: true },
);

return await axios
.post(`${VITE_BASE_URL}/room`, {
userId: 1,
})
.then((response) => {
return response.data;
})
.catch((error) => {
console.log(error);
});
return response.data;
}
7 changes: 7 additions & 0 deletions client/src/apis/exitRoom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import axios from 'axios';

const VITE_BASE_URL = import.meta.env.VITE_BASE_URL as string;

export async function exitRoom() {
await axios.post(`${VITE_BASE_URL}/room/exit`, {}, { withCredentials: true });
}
13 changes: 13 additions & 0 deletions client/src/apis/joinRoom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import axios from 'axios';

const VITE_BASE_URL = import.meta.env.VITE_BASE_URL as string;

export async function joinRoom(roomCode: string) {
await axios.post(
`${VITE_BASE_URL}/room/join`,
{ code: roomCode },
{
withCredentials: true,
},
);
}
85 changes: 13 additions & 72 deletions client/src/components/Chat.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
import { io, Socket } from 'socket.io-client';
import { useEffect, useRef, useState } from 'react';

import {
ChatEvent,
MessageInterface,
RoomMessagesLocalStorage,
} from '../types/Message';
import { FaArrowRight } from 'react-icons/fa6';
import Message from './Message';
import { ChatEvent, MessageInterface } from '../types/Message';
import { useAuthContext } from '../contexts/AuthContext';
import { useParams } from 'react-router-dom';
import { Socket } from 'socket.io-client';

// TODO: userColor -> 서버에서 설정
export default function Chat() {
const roomId = useParams<{ roomId: string }>().roomId;
export default function Chat({
messages,
inputRef,
messagesRef,
socketRef,
}: {
messages: MessageInterface[];
inputRef: React.RefObject<HTMLInputElement>;
messagesRef: React.RefObject<HTMLUListElement>;
socketRef: React.MutableRefObject<Socket | null>;
}) {
const { user } = useAuthContext();

const serverUrl = import.meta.env.VITE_BASE_URL;

const inputRef = useRef<HTMLInputElement>(null);
const messagesRef = useRef<HTMLUListElement>(null);
const [messages, setMessages] = useState<MessageInterface[]>([]);
const socketRef = useRef<Socket | null>(null);

function handleSubmitMessage(event: React.SyntheticEvent) {
event.preventDefault();

Expand All @@ -49,60 +44,6 @@ export default function Chat() {
inputRef.current.value = '';
}

useEffect(() => {
const socket: Socket = io(serverUrl, {
transports: ['websocket', 'polling'],
});

const storedRoomMessagesString = localStorage.getItem('leetRoomsMessages');
if (storedRoomMessagesString) {
const storedRoomMessages: RoomMessagesLocalStorage = JSON.parse(
storedRoomMessagesString,
);
if (storedRoomMessages) {
setMessages(storedRoomMessages.messages);
}
}

socket.on('chat-message', (newMessage) => {
setMessages((prevMessages) => {
const newMessages = [...prevMessages, newMessage];

localStorage.setItem(
'leetRoomsMessages',
JSON.stringify({
messages: newMessages,
}),
);
return newMessages;
});
});

// socket.on('keep-alive', () => {
// socket.emit('keep-alive', 'keep-alive-message-client');
// });

socketRef.current = socket;
return () => {
socket.disconnect();
};
}, [roomId, serverUrl]);

useEffect(() => {
function autoScrollToLatestMessage() {
if (!messagesRef.current) {
return;
}

const latestMessage = messagesRef.current.lastElementChild;
latestMessage?.scrollIntoView({
behavior: 'smooth',
});
}

autoScrollToLatestMessage();
}, [messages]);

return (
<div className="flex w-full flex-1 flex-col overflow-hidden">
<div className="mx-2 flex-1 overflow-auto px-2 py-2">
Expand Down
19 changes: 18 additions & 1 deletion client/src/components/Message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,24 @@ import { ChatEvent, MessageInterface } from '../types/Message';

// This just needs to be here so that these colors get bundled in the final distribution.
// The userColor is actually assigned on the server.
const colorChoices = ['cyan', 'blue', 'green', 'rose', 'orange', 'red', 'gold'];
// const colorChoices = [
// 'text-red-400',
// 'text-orange-400',
// 'text-amber-400',
// 'text-yellow-400',
// 'text-green-400',
// 'text-emerald-400',
// 'text-teal-400',
// 'text-cyan-400',
// 'text-sky-400',
// 'text-blue-400',
// 'text-indigo-400',
// 'text-violet-400',
// 'text-purple-400',
// 'text-fuchsia-400',
// 'text-pink-400',
// 'text-rose-400',
// ];

export default function Message({
message,
Expand Down
11 changes: 9 additions & 2 deletions client/src/components/buttons/ExitButton.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import { RxExit } from 'react-icons/rx';
import { useNavigate } from 'react-router-dom';
import { exitRoom } from '../../apis/exitRoom';

export default function ExitButton() {
const navigate = useNavigate();

const exit = () => {
navigate(`/lobby`);
try {
exitRoom();
navigate(`/lobby`);
} catch (err) {
console.error(err);
throw err;
}
};
return (
<button
id="room-exit-button"
className="bg-accent text-default_white flex flex-row items-center gap-x-2 rounded-lg px-2.5 py-1 hover:opacity-80"
className="flex flex-row items-center gap-x-2 rounded-lg bg-accent px-2.5 py-1 text-default_white hover:opacity-80"
onClick={exit}>
<RxExit
style={{
Expand Down
29 changes: 17 additions & 12 deletions client/src/components/buttons/RoomCreateButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,25 @@ export default function RoomCreateButton() {
const navigate = useNavigate();
const onClick = async () => {
setIsLoading(true);
const roomInfo: RoomCreateType | undefined = await createRoom();
if (roomInfo === undefined) {
setTimeout(() => {
console.log(roomInfo);
alert('방 생성에 실패했습니다.');
setIsLoading(false);
}, 1000);
try {
const roomInfo: RoomCreateType | undefined = await createRoom();
if (roomInfo === undefined) {
setTimeout(() => {
console.log(roomInfo);
alert('방 생성에 실패했습니다.');
setIsLoading(false);
}, 1000);

return;
return;
}
const roomCode = roomInfo.code;
navigate(`/room/${roomCode}`, {
state: { isHost: true, roomCode: roomCode },
});
} catch (err) {
console.error(err);
throw err;
}
const roomCode = roomInfo.code;
navigate(`/room/${roomCode}`, {
state: { isHost: true, roomCode: roomCode },
});
};

return (
Expand Down
31 changes: 26 additions & 5 deletions client/src/components/buttons/RoomJoinButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,53 @@ import { useNavigate } from 'react-router-dom';

import { useState } from 'react';

import axios from 'axios';
import { joinRoom } from '../../apis/joinRoom';

export default function RoomJoinButton() {
const [roomCode, setRoomCode] = useState<string>('');

const navigate = useNavigate();

const onClick = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
const onClick = async (
e: React.MouseEvent<HTMLButtonElement, MouseEvent>,
) => {
e.preventDefault();

if (roomCode === '') {
alert('방 코드를 입력해주세요.');
return;
}

// TODO: 여기도 서버 로직 나오면 수정
navigate(`/room/${'q1w2e3'}`, { state: { isHost: false } });
try {
await joinRoom(roomCode);

navigate(`/room/${roomCode}`, {
state: { roomCode: roomCode, isHost: false },
});
return;
} catch (err: unknown) {
if (axios.isAxiosError(err) && err.response) {
console.error(err.response.data.message);
alert(err.response.data.message);
setRoomCode('');
} else {
console.error(err);
throw err;
}
}
};

return (
<form className="flex items-center justify-between gap-2">
<input
className="bg-default_white rounded-lg px-2 py-1"
className="rounded-lg bg-default_white px-2 py-1"
placeholder="Room code"
value={roomCode}
onChange={(e) => setRoomCode(e.target.value)}
/>
<button
className="bg-accent text-default_white h-[33px] w-[60px] items-center justify-center rounded-lg font-medium hover:opacity-80"
className="h-[33px] w-[60px] items-center justify-center rounded-lg bg-accent font-medium text-default_white hover:opacity-80"
onClick={onClick}>
Join
</button>
Expand Down
41 changes: 21 additions & 20 deletions client/src/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useNavigate } from 'react-router-dom';
import { createContext, useContext, useEffect, useState } from 'react';
import { CreateUser } from '../types/CreateUserType';
import axios from 'axios';
import { getSession, logout } from '../apis/Auth';

interface AuthContextType {
user: CreateUser | null;
Expand All @@ -11,8 +11,6 @@ interface AuthUpdateType {
onLogout: () => void;
}

const baseURL = import.meta.env.VITE_BASE_URL;

const AuthContext = createContext<AuthContextType>({} as AuthContextType);
const AuthUpdateContext = createContext<AuthUpdateType>({} as AuthUpdateType);

Expand All @@ -22,27 +20,30 @@ export const AuthProvider = ({ children }: { children: JSX.Element }) => {
const [user, setUser] = useState<CreateUser | null>(null);

const onLogout = () => {
// TODO: waiting for server api
axios.get(`${baseURL}/auth/logout`, { withCredentials: true });
setUser(null);
navigate('/');
try {
logout();
setUser(null);
navigate('/');
} catch (err) {
console.error(err);
throw err;
}
};

useEffect(() => {
async function getSession() {
const response = await axios.get(`${baseURL}/session`, {
withCredentials: true,
});
return response;
}

getSession().then((session) => {
if (session) {
setUser(session.data);
} else {
setUser(null);
(async () => {
try {
const user = await getSession();

if (user) {
setUser(user);
} else {
setUser(null);
}
} catch (error) {
console.error(error);
}
});
})();
}, []);

return (
Expand Down
Loading

0 comments on commit 1e2a6b3

Please sign in to comment.