Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

채팅방 버그 이슈 및 프로필 사진 변경 모달 UI 수정 #139

Merged
merged 5 commits into from
Nov 26, 2021
Merged
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
10 changes: 6 additions & 4 deletions client/src/components/chat/chat-new-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@ const DoneBtn = ({ selectedUserList }: any) => {
pathname: `/chat-rooms/${res.chatDocumentId}`,
state: { participantsInfo: selectedUserList },
});
chatSocket.emit('chat:makeChat', {
chatDocumentId: res.chatDocumentId,
participantsInfo: [...selectedUserList, { userDocumentId: user.userDocumentId, userName: user.userName, profileUrl: user.profileUrl }],
});
if (res.isNew) {
chatSocket.emit('chat:makeChat', {
chatDocumentId: res.chatDocumentId,
participantsInfo: [...selectedUserList, { userDocumentId: user.userDocumentId, userName: user.userName, profileUrl: user.profileUrl }],
});
}
});
};

Expand Down
20 changes: 13 additions & 7 deletions client/src/components/profile/change-profile-image-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ const PreviewProfileImage = styled.img`
width: 150px;
height: 150px;
min-height: 150px;
object-fit: contain;
margin: 20px;
object-fit: cover;
border-radius: 30px;
border: 0.1px solid #b8b8b8;

Expand All @@ -48,9 +47,13 @@ const ButtonsLayout = styled.div`
`;

const CustomInput = styled.input`
padding:0;
margin-bottom: 20px;
width : 200px;
display: none;

& + label {
width: 200x;
height: 200px;
cursor: pointer;
}
`;

function ShareModal() {
Expand Down Expand Up @@ -95,9 +98,12 @@ function ShareModal() {
/>
<ModalBox>
<ChangePofileImageLayout>
<PreviewProfileImage src={previewImageURL} />
<h2>Change your photo</h2>
<CustomForm>
<CustomInput type="file" accept="image/gif, image/jpeg, image/png" onChange={inputOnChange} />
<CustomInput id="c2" type="file" accept="image/gif, image/jpeg, image/png" onChange={inputOnChange} />
<label htmlFor={'c2' as string}>
<PreviewProfileImage src={previewImageURL} />
</label>
<ButtonsLayout>
<DefaultButton buttonType="secondary" size="medium" onClick={() => changeImageHandler()}>Change</DefaultButton>
<DefaultButton buttonType="thirdly" size="medium" onClick={() => setIsOpenChangeProfileImageModalState(!isOpenChangeProfileImageModal)}>
Expand Down
1 change: 0 additions & 1 deletion client/src/recoil/selectors/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion client/src/tests/index.tsx

This file was deleted.

1 change: 0 additions & 1 deletion client/src/utils/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import React from 'react';
import { Link } from 'react-router-dom';

import { IconAndLink } from '@interfaces/index';
Expand Down
51 changes: 3 additions & 48 deletions client/src/views/chat-rooms-new-view.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,17 @@
/* eslint-disable no-underscore-dangle */
import React, { useEffect, useState, useRef } from 'react';
import { useRecoilValue } from 'recoil';
import React, { useRef } from 'react';

import NewChatRoomHeader from '@src/components/chat/chat-new-header';
import { ChatRoomsLayout, NewChatRoomBody } from '@components/chat/style';
import UserCardList from '@components/common/user-card-list';
import { findUsersByIdList } from '@api/index';
import followState from '@atoms/following-list';
import {
SelectDiv, SelectInputBar, SelectedUserDiv, SelectUserComponent,
} from '@common/select';
import useSelectUser from '@hooks/useSelectUser';

function ChatRoomsNewView() {
const followingList = useRecoilValue(followState);
const [selectedUsers, setSelectedUsers] = useState<any>([]);
const [allUserList, setAllUserList] = useState([]);
const [filteredUserList, setFilteredUserList] = useState([]);
const inputBarRef = useRef<HTMLInputElement>(null);

const addSelectedUser = (e: any) => {
const userCardDiv = e.target.closest('.userCard');
const profileUrl = userCardDiv.querySelector('img');
if (!userCardDiv || !profileUrl) return;
const userName = userCardDiv?.getAttribute('data-username');
inputBarRef.current!.value = '';
setSelectedUsers([...selectedUsers,
{
userDocumentId: userCardDiv?.getAttribute('data-id'),
userName,
profileUrl: profileUrl.getAttribute('src'),
}]);
};

const deleteUser = (e: any) => {
setSelectedUsers(selectedUsers.filter((user: any) => user.userDocumentId !== e.target.getAttribute('data-id')));
inputBarRef.current!.value = '';
};

const searchUser = () => {
const searchWord = inputBarRef.current!.value;
const selectedUserIds = selectedUsers.map((user: any) => user.userDocumentId);
setFilteredUserList(allUserList
.filter((user: any) => (user.userId.indexOf(searchWord) > -1 || user.userName.indexOf(searchWord) > -1)
&& selectedUserIds.indexOf(user._id) === -1));
};

useEffect(() => {
findUsersByIdList(followingList).then((res: any) => {
const selectedUserIds = selectedUsers.map((user: any) => user.userDocumentId);
setAllUserList(res.userList);
setFilteredUserList(res.userList.filter((user: any) => selectedUserIds.indexOf(user._id) === -1));
});
}, [followingList]);

useEffect(() => {
const selectedUserIds = selectedUsers.map((user: any) => user.userDocumentId);
setFilteredUserList(allUserList.filter((user: any) => selectedUserIds.indexOf(user._id) === -1));
}, [selectedUsers]);
const [selectedUsers, filteredUserList, searchUser, deleteUser, addSelectedUser] = useSelectUser(inputBarRef);

Comment on lines -56 to 15
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

커스텀훅을 사용해서 view의 역할에 더 알맞게 코드가 바뀐 것 같아서 아주 좋네요!

return (
<ChatRoomsLayout>
Expand Down
4 changes: 2 additions & 2 deletions server/src/api/routes/chats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export default (app: Router) => {
try {
const { participants } = req.body;
participants.sort();
const { chatDocumentId } = await chatService.makeChatRoom(participants);
const { chatDocumentId, isNew } = await chatService.makeChatRoom(participants);

res.status(200).json({ chatDocumentId });
res.status(200).json({ chatDocumentId, isNew });
} catch (error) {
res.json({ ok: false });
}
Expand Down
101 changes: 101 additions & 0 deletions server/src/services/user/activity-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* eslint-disable no-underscore-dangle */
import Users, { IActivity } from '@models/users';
import Events from '@models/events';
import { activeUser } from '@src/sockets/user';
import { userNamespace } from '@src/sockets';

let instance: any = null;
class UserService {
constructor() {
if (instance) return instance;
instance = this;
}

Comment on lines +6 to +13
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

activity 부분을 따로 서비스로직을 빼주셨군요! class 명을 ActivityService라고 해야할 것 같아요

async getActivityList(userDocumentId: string, count: number) {
const user = await Users.findById(userDocumentId, ['activity']);
const newActivityList = await Promise.all(user!.activity.slice(count, count + 10).map(async (activity: IActivity) => {
const detailFrom = await this.findUserByDocumentId(activity.from);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this.findUserByDocumentId 여기서 this는 UserService의 메서드라서 아마 수정이 필요하지 않을까요?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users.findById 로 바꾸면 될 것 같습니다

const newFrom = { userId: detailFrom!.userId, userName: detailFrom!.userName, profileUrl: detailFrom!.profileUrl };
return { ...activity, from: newFrom };
}));
await Users.findByIdAndUpdate(userDocumentId, { activity: user!.activity.map((el) => ({ ...el, isChecked: true })) });
return newActivityList;
}

async isActivityChecked(userDocumentId: string) {
try {
const { activity } : any = await Users.findOne({ _id: userDocumentId }, ['activity']);
if (!activity) return false;
return activity.some((item: IActivity) => !item.isChecked);
} catch (e) {
return false;
}
}

async addActivityTypeFollow(userDocumentId: string, targetUserDocumentId: string) {
try {
const newActivity = {
type: 'follow',
clickDocumentId: userDocumentId,
from: userDocumentId,
date: new Date(),
isChecked: false,
};
await Users.findByIdAndUpdate(targetUserDocumentId, { $push: { activity: { $each: [newActivity], $position: 0 } } });
this.emitToUserGetActivity(targetUserDocumentId);
return true;
} catch (e) {
return false;
}
}

async addActivityTypeRoom(userDocumentId: string, roomDocumentId: string) {
try {
const user = await Users.findById(userDocumentId, ['followers']);
const newActivity = {
type: 'room',
clickDocumentId: String(roomDocumentId),
from: userDocumentId,
date: new Date(),
isChecked: false,
};
await Promise.all(user!.followers.map(async (userDocId: string) => {
await Users.findByIdAndUpdate(userDocId, { $push: { activity: { $each: [newActivity], $position: 0 } } });
this.emitToUserGetActivity(userDocId);
return true;
}));
return true;
} catch (e) {
return false;
}
}

async addActivityTypeEvent(userDocumentId: string, eventDocumentId: string) {
try {
const event = await Events.findById(eventDocumentId, ['participants']);
const newActivity = {
type: 'event',
clickDocumentId: String(eventDocumentId),
from: userDocumentId,
date: new Date(),
isChecked: false,
};
await Promise.all(event!.participants.map(async (userId: string) => {
const user = await Users.findOneAndUpdate({ userId }, { $push: { activity: { $each: [newActivity], $position: 0 } } });
const userDocId = user!._id;
this.emitToUserGetActivity(String(userDocId));
return true;
}));
return true;
} catch (e) {
return false;
}
}

emitToUserGetActivity(userDocumentId: string) {
const socketUser = activeUser.get(userDocumentId);
if (socketUser) userNamespace.to(socketUser.socketId).emit('user:getActivity');
}
}

export default new UserService();
120 changes: 120 additions & 0 deletions server/src/services/user/auth-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/* eslint-disable no-underscore-dangle */
import jwt from 'jsonwebtoken';

import Users from '@models/users';
import RefreshTokens from '@models/refresh-token';
import jwtUtils from '@utils/jwt-util';

interface ISignupUserInfo {
loginType: string,
userId: string,
password: string,
userName: string,
userEmail: string,
}

let instance: any = null;
class AuthService {
constructor() {
if (instance) return instance;
instance = this;
}

async signIn(email: string, password: string) {
const user = await Users.findOne({ userEmail: email });

if (!user) {
return { ok: false, msg: 'there is no user' };
}

const isMatch = user.checkPassword(password);

if (isMatch) {
const accessToken = jwtUtils.sign(user._id, user.userEmail);
const refreshToken = jwtUtils.refresh();

const existingRefreshToken = await RefreshTokens.findOneAndUpdate(
{ userDocumentId: user._id },
{ token: refreshToken },
);

if (!existingRefreshToken) {
const usersRefreshTokens = new RefreshTokens({
userDocumentId: user._id,
token: refreshToken,
});
await usersRefreshTokens.save();
}

return {
accessToken,
ok: true,
msg: 'ok',
};
}
return { ok: false, msg: 'wrong password' };
}

async signup(info: ISignupUserInfo) {
try {
await Users.insertMany([info]);
} catch (error) {
console.error(error);
}
}

async verifyAccessToken(token: string) {
const result = jwtUtils.verify(token);
if (!result.ok) {
const newToken = await this.tokenRefresh(token);
return newToken;
}
return result;
}

async getRefreshTokens(userDocumentId: string) {
const refreshToken = await RefreshTokens.findOne({ userDocumentId });
if (!refreshToken) {
return null;
}
return refreshToken.token;
}

async tokenRefresh(accessToken: string) {
const accessResult = jwtUtils.verify(accessToken);
const decoded = jwt.decode(accessToken) as jwt.JwtPayload;

if (decoded === null) {
return {
ok: false,
msg: 'No authorized!',
};
}

const refreshToken = (await this.getRefreshTokens(decoded.id)) as string;
const refreshResult = await jwtUtils.refreshVerify(refreshToken);

if (accessResult.ok === false && accessResult.message === 'jwt expired') {
if (refreshResult === false) {
return {
ok: false,
msg: 'No authorized!',
};
}
const newAccessToken = jwtUtils.sign(decoded.id, decoded.email);

return {
ok: true,
accessToken: newAccessToken,
userDocumentId: decoded.id,
};
}

return {
ok: true,
msg: 'Acess token is not expired!',
};
}
}

export default new AuthService();
Loading