From 657a5d44e08309d7a5e413efe95a61015397fcdf Mon Sep 17 00:00:00 2001 From: Italo A Date: Sun, 26 Nov 2023 14:29:14 -0300 Subject: [PATCH 1/8] feat: allow owner update and remove password from chat --- backend/src/chat/chat.controller.ts | 25 ++- backend/src/chat/chat.service.ts | 69 +++++- .../components/Chat/ChangeChatPassword.tsx | 202 ++++++++++++++++++ frontend/src/components/Chat/OpenChannel.tsx | 29 ++- 4 files changed, 315 insertions(+), 10 deletions(-) create mode 100644 frontend/src/components/Chat/ChangeChatPassword.tsx diff --git a/backend/src/chat/chat.controller.ts b/backend/src/chat/chat.controller.ts index 93ef93fe..a0fc0fa5 100644 --- a/backend/src/chat/chat.controller.ts +++ b/backend/src/chat/chat.controller.ts @@ -6,11 +6,15 @@ import { HttpStatus, Logger, ParseIntPipe, + Patch, Post, Query, + Req, + UseGuards, } from '@nestjs/common'; import { ChatService } from './chat.service'; -import { chatMemberRole, chatType } from '@prisma/client'; +import { User, chatMemberRole, chatType } from '@prisma/client'; +import { AccessTokenGuard } from 'src/auth/jwt/jwt.guard'; // create a new chat in the database using post request // get all chats from the database using get request @@ -27,11 +31,13 @@ export class ChatController { ): Promise { const you = await this.chatService.getMemberFromChat(chatId, login); const member = await this.chatService.getMemberFromChat(chatId, user); + // Me and him must exist in the database if (!you || !member) { this.logger.error('Unable to find user or member'); return true; } + // I cannot be the member I want to mute, I cannot be member to mute, I cannot mute an admin nor the owner if (you === member || you.role === 'MEMBER' || member.role !== 'MEMBER') { this.logger.error('You are not allowed to mute this user'); @@ -189,4 +195,21 @@ export class ChatController { throw new HttpException({ error }, HttpStatus.FORBIDDEN); } } + + // Patch to update a chat password if the user is the owner + @UseGuards(AccessTokenGuard) + @Patch('/password') + async updateChatPassword( + @Req() request: Request & { user: User }, + @Body() body: { chatId: number; password: string }, + ) { + const { chatId, password } = body; + const { login } = request.user; + const updatedChat = await this.chatService.updateChatPassword( + chatId, + password, + login, + ); + return updatedChat; + } } diff --git a/backend/src/chat/chat.service.ts b/backend/src/chat/chat.service.ts index b07d2c50..c04ea692 100644 --- a/backend/src/chat/chat.service.ts +++ b/backend/src/chat/chat.service.ts @@ -1,4 +1,9 @@ -import { Injectable } from '@nestjs/common'; +import { + HttpException, + Injectable, + NotFoundException, + UnauthorizedException, +} from '@nestjs/common'; import { Chat, ChatMember, @@ -665,4 +670,66 @@ export class ChatService { return isPasswordValid; } + + async updateChatPassword( + chatId: number, + password: string, + userLogin: string, + ): Promise { + // check if user is the owner of the chat + const chat = await this.prisma.chat.findUnique({ + where: { + id: chatId, + }, + }); + + if (!chat) { + throw new NotFoundException('Chat not found'); + } + + if (chat.owner !== userLogin) { + throw new UnauthorizedException('You are not the owner of this chat'); + } + + if (chat.chatType !== 'PROTECTED' || !chat.password) { + throw new HttpException( + { + status: 403, + error: 'Chat is not protected', + }, + 403, + ); + } + + // if password is empty, remove password and set chat type to public + if (!password) { + const updatedChat = await this.prisma.chat.update({ + where: { + id: chatId, + }, + data: { + password: null, + chatType: 'PUBLIC', + }, + }); + + return updatedChat; + } + + try { + const updatedChat = await this.prisma.chat.update({ + where: { + id: chatId, + }, + data: { + password: await argon2.hash(password), + }, + }); + + return updatedChat; + } catch (error) { + console.log(error); + return null; + } + } } diff --git a/frontend/src/components/Chat/ChangeChatPassword.tsx b/frontend/src/components/Chat/ChangeChatPassword.tsx new file mode 100644 index 00000000..ac42789f --- /dev/null +++ b/frontend/src/components/Chat/ChangeChatPassword.tsx @@ -0,0 +1,202 @@ +import { AuthContext } from "@/contexts/AuthContext"; +import { api } from "@/services/apiClient"; +import { queryClient } from "@/services/queryClient"; +import { Dialog, Transition } from "@headlessui/react"; +import { Lock } from "@phosphor-icons/react"; +import { Fragment, useContext, useState } from "react"; +import { useForm } from "react-hook-form"; +import toast from "react-hot-toast"; + +type ChangeChatPasswordProps = { + chatId: number; +}; + +export default function ChangeChatPassword({ + chatId, +}: ChangeChatPasswordProps) { + const [isOpen, setIsOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + + function closeModal() { + setIsOpen(false); + } + + function openModal() { + setIsOpen(true); + } + + const onSubmit = async (data: any) => { + // create a new form data and append the data, send to api + if (data.password) { + setIsLoading(true); + await api + .patch("/chat/password", { + chatId, + password: data.password, + }) + .then(() => { + toast.success("Senha atualizada!"); + }) + .catch((error) => { + toast.error( + `Erro ao atualizar senha: ${error.response.data.message}` + ); + }) + .finally(() => { + setIsLoading(false); + setIsOpen(false); + }); + } + }; + + const handleRemovePassword = async () => { + if (window.confirm("Tem certeza que deseja remover a senha do chat?")) { + // Handle password removal here + setIsLoading(true); + await api + .patch("/chat/password", { + chatId, + password: "", + }) + .then(() => { + toast.success("Senha removida!"); + }) + .catch((error) => { + toast.error(`Erro ao remover senha: ${error.response.data.message}`); + }) + .finally(() => { + setIsLoading(false); + setIsOpen(false); + }); + } + }; + + return ( + <> + + + + + +
+ + +
+
+ + + + Editando senha do chat + +
+
+ + {errors.password && ( + + Campo obrigatório + + )} +
+ +
+ +
+
+
+
+
+
+ + ); +} diff --git a/frontend/src/components/Chat/OpenChannel.tsx b/frontend/src/components/Chat/OpenChannel.tsx index a908db99..1df8b475 100644 --- a/frontend/src/components/Chat/OpenChannel.tsx +++ b/frontend/src/components/Chat/OpenChannel.tsx @@ -10,6 +10,7 @@ import ChatUsersChannelPopOver, { ChatMember } from "./ChatUsersChannelPopOver"; import chatService from "@/services/chatClient"; import { useForm } from "react-hook-form"; import Link from "next/link"; +import ChangeChatPassword from "./ChangeChatPassword"; interface Message { id: number; content: string; @@ -32,8 +33,7 @@ export function OpenChannel() { const [numberOfUsersInChat, setNumberOfUsersInChat] = useState(0); const [users, setUsers] = useState([]); const [isLoading, setIsLoading] = useState(true); - const myUserList = users.filter((usr) => usr.userLogin === user.login); - const myUser = myUserList[0] || null; + const myUser = users.find((chatUser) => chatUser.userLogin === user.login); chatService.socket?.on("listMessages", (messages: Message[]) => { setMessages(() => messages); @@ -167,12 +167,25 @@ export function OpenChannel() {

{selectedChat.name}

- handleCloseChat(selectedChat.id)} - /> +
+ {selectedChat.chatType === "PROTECTED" && + myUser?.role === "OWNER" && ( + + )} + handleCloseChat(selectedChat.id)} + /> +
Date: Sun, 26 Nov 2023 16:28:25 -0300 Subject: [PATCH 2/8] fix: wrong html tree --- frontend/src/app/(private)/layout.tsx | 14 ++++++-------- frontend/src/app/auth/layout.tsx | 8 +++----- frontend/src/app/layout.tsx | 2 +- frontend/src/app/login/layout.tsx | 6 +----- 4 files changed, 11 insertions(+), 19 deletions(-) diff --git a/frontend/src/app/(private)/layout.tsx b/frontend/src/app/(private)/layout.tsx index aa5696ae..77a533a4 100644 --- a/frontend/src/app/(private)/layout.tsx +++ b/frontend/src/app/(private)/layout.tsx @@ -19,14 +19,12 @@ export default function RootPrivateLayout({ children: React.ReactNode; }) { return ( - - - -
{children}
- -
+ <> + +
{children}
- - +
+ + ); } diff --git a/frontend/src/app/auth/layout.tsx b/frontend/src/app/auth/layout.tsx index 2e501e4f..4c3ea74f 100644 --- a/frontend/src/app/auth/layout.tsx +++ b/frontend/src/app/auth/layout.tsx @@ -12,10 +12,8 @@ export default function RootLoginPublicLayout({ children: React.ReactNode; }) { return ( - - - {children} - - + <> + {children} + ); } diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index f0fcded5..e7c7c31e 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -14,7 +14,7 @@ export default function RootPublicLayout({ children: React.ReactNode; }) { return ( - + {children} ); diff --git a/frontend/src/app/login/layout.tsx b/frontend/src/app/login/layout.tsx index fce5f5e1..026182b9 100644 --- a/frontend/src/app/login/layout.tsx +++ b/frontend/src/app/login/layout.tsx @@ -10,9 +10,5 @@ export default function RootLoginPublicLayout({ }: { children: React.ReactNode; }) { - return ( - - {children} - - ); + return <>{children}; } From 36c12e115e8fde76fa0f883810634359d67255cc Mon Sep 17 00:00:00 2001 From: Italo A Date: Sun, 26 Nov 2023 16:28:40 -0300 Subject: [PATCH 3/8] feat: update chat listing on render list --- frontend/src/components/Chat/ListChannels.tsx | 9 +++++++-- frontend/src/contexts/ChatContext.tsx | 6 ++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Chat/ListChannels.tsx b/frontend/src/components/Chat/ListChannels.tsx index dc71be99..c77f07a2 100644 --- a/frontend/src/components/Chat/ListChannels.tsx +++ b/frontend/src/components/Chat/ListChannels.tsx @@ -1,14 +1,19 @@ import { Plus } from "@phosphor-icons/react"; import ChannelCard from "./ChannelCard"; import { Chat, ChatContext } from "@/contexts/ChatContext"; -import { useContext } from "react"; +import { use, useContext, useEffect } from "react"; type ListChannelsProps = { handleShowCreateChannel: () => void; }; export function ListChannels({ handleShowCreateChannel }: ListChannelsProps) { - const { isLoading, chatList } = useContext(ChatContext); + const { isLoading, chatList, handleUpdateListChats } = + useContext(ChatContext); + + useEffect(() => { + handleUpdateListChats(); + }, []); return (
diff --git a/frontend/src/contexts/ChatContext.tsx b/frontend/src/contexts/ChatContext.tsx index 2b6d0215..731e89a8 100644 --- a/frontend/src/contexts/ChatContext.tsx +++ b/frontend/src/contexts/ChatContext.tsx @@ -24,6 +24,7 @@ type ChatContextType = { setValidationRequired: React.Dispatch>; validationRequired: boolean; user: User; + handleUpdateListChats: () => void; }; type ChatProviderProps = { @@ -65,6 +66,10 @@ export const ChatProvider = ({ children }: ChatProviderProps) => { setShowElement("showChannelOpen"); }; + const handleUpdateListChats = () => { + chatService.socket?.emit("listChats"); + }; + useEffect(() => { // Connect to the Socket.IO server chatService.connect(); @@ -140,6 +145,7 @@ export const ChatProvider = ({ children }: ChatProviderProps) => { setValidationRequired, validationRequired, user, + handleUpdateListChats, }} > {children} From c4b39681a1b81bd7094d4022c1317e2327640947 Mon Sep 17 00:00:00 2001 From: Italo A Date: Sun, 26 Nov 2023 16:32:27 -0300 Subject: [PATCH 4/8] feat: on chat open goes to the bottom --- frontend/src/components/Chat/OpenChannel.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/Chat/OpenChannel.tsx b/frontend/src/components/Chat/OpenChannel.tsx index 1df8b475..53669791 100644 --- a/frontend/src/components/Chat/OpenChannel.tsx +++ b/frontend/src/components/Chat/OpenChannel.tsx @@ -50,9 +50,16 @@ export function OpenChannel() { ); setNumberOfUsersInChat(currentMembers.length); setUsers(currentMembers); - console.log("listMembers", currentMembers); setIsLoading(false); }); + + // on chat open go to the bottom of the messages + setTimeout(() => { + const messagesContainer = document.getElementById("messages-container"); + if (messagesContainer) { + messagesContainer.scrollTop = messagesContainer.scrollHeight; + } + }, 100); }, []); chatService.socket?.on("verifyPassword", (response: any) => { From aeb5224bfd67f67c9f0dbe9e774af4252e248c35 Mon Sep 17 00:00:00 2001 From: Italo A Date: Sun, 26 Nov 2023 16:35:18 -0300 Subject: [PATCH 5/8] refactor: remove console log --- frontend/src/contexts/ChatContext.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/contexts/ChatContext.tsx b/frontend/src/contexts/ChatContext.tsx index 731e89a8..625ab918 100644 --- a/frontend/src/contexts/ChatContext.tsx +++ b/frontend/src/contexts/ChatContext.tsx @@ -75,7 +75,6 @@ export const ChatProvider = ({ children }: ChatProviderProps) => { chatService.connect(); chatService.socket?.on("userLogin", (user: User) => { - console.log(`Current user login: ${user.login}`, user); setUser(() => user); queryClient.invalidateQueries(["user_status", user.id]); queryClient.invalidateQueries(["friends"]); From 3c194d7672f6e53990f2ec72728836dc98ab590f Mon Sep 17 00:00:00 2001 From: Italo A Date: Sun, 26 Nov 2023 16:39:31 -0300 Subject: [PATCH 6/8] feat: update lock icon --- .../src/components/Chat/ChangeChatPassword.tsx | 3 +++ frontend/src/components/Chat/OpenChannel.tsx | 17 +++++++++++++---- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/frontend/src/components/Chat/ChangeChatPassword.tsx b/frontend/src/components/Chat/ChangeChatPassword.tsx index ac42789f..661374ad 100644 --- a/frontend/src/components/Chat/ChangeChatPassword.tsx +++ b/frontend/src/components/Chat/ChangeChatPassword.tsx @@ -9,10 +9,12 @@ import toast from "react-hot-toast"; type ChangeChatPasswordProps = { chatId: number; + handleHideLock: () => void; }; export default function ChangeChatPassword({ chatId, + handleHideLock, }: ChangeChatPasswordProps) { const [isOpen, setIsOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); @@ -73,6 +75,7 @@ export default function ChangeChatPassword({ .finally(() => { setIsLoading(false); setIsOpen(false); + handleHideLock(); }); } }; diff --git a/frontend/src/components/Chat/OpenChannel.tsx b/frontend/src/components/Chat/OpenChannel.tsx index 53669791..36dea7de 100644 --- a/frontend/src/components/Chat/OpenChannel.tsx +++ b/frontend/src/components/Chat/OpenChannel.tsx @@ -34,6 +34,9 @@ export function OpenChannel() { const [users, setUsers] = useState([]); const [isLoading, setIsLoading] = useState(true); const myUser = users.find((chatUser) => chatUser.userLogin === user.login); + const [showLock, setShowLock] = useState(() => + selectedChat.chatType === "PROTECTED" ? true : false + ); chatService.socket?.on("listMessages", (messages: Message[]) => { setMessages(() => messages); @@ -113,6 +116,10 @@ export function OpenChannel() { }); }; + const handleHideLock = () => { + setShowLock(false); + }; + if (selectedChat.chatType === "PROTECTED" && validationRequired) { return (
@@ -182,10 +189,12 @@ export function OpenChannel() { items-center " > - {selectedChat.chatType === "PROTECTED" && - myUser?.role === "OWNER" && ( - - )} + {showLock && myUser?.role === "OWNER" && ( + + )} Date: Sun, 26 Nov 2023 16:40:52 -0300 Subject: [PATCH 7/8] fix: avoid read undefined --- frontend/src/contexts/ChatContext.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/contexts/ChatContext.tsx b/frontend/src/contexts/ChatContext.tsx index 625ab918..b9881472 100644 --- a/frontend/src/contexts/ChatContext.tsx +++ b/frontend/src/contexts/ChatContext.tsx @@ -59,7 +59,7 @@ export const ChatProvider = ({ children }: ChatProviderProps) => { const handleOpenChannel = (chat: Chat) => { setSelectedChat(chat); // check if chat has an id - if (chat.id) { + if (chat?.id) { chatService.socket?.emit("listMessages", { chatId: chat.id }); chatService.socket?.emit("listMembers", { chatId: chat.id }); } From 646ba083e2917e29d1505f256a129ab3c92bd450 Mon Sep 17 00:00:00 2001 From: Italo A Date: Mon, 27 Nov 2023 20:06:51 -0300 Subject: [PATCH 8/8] fix: missing provider and duplicated toaster --- frontend/src/app/(private)/layout.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/(private)/layout.tsx b/frontend/src/app/(private)/layout.tsx index 049c6311..22ffef06 100644 --- a/frontend/src/app/(private)/layout.tsx +++ b/frontend/src/app/(private)/layout.tsx @@ -19,10 +19,11 @@ export default function RootPrivateLayout({ return ( <> -
{children}
- + +
{children}
+ +
- ); }