Compare commits
No commits in common. "1015ee1643872eb98ebc661d1d0c4b873cee616a" and "3aa4c5b150721c550b5ac389b8f06ebe42a7c56e" have entirely different histories.
1015ee1643
...
3aa4c5b150
@ -453,6 +453,7 @@ export class ChatService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if target user is an admin
|
||||||
const targetMembership = await prisma.chatRoomMember.findUnique({
|
const targetMembership = await prisma.chatRoomMember.findUnique({
|
||||||
where: {
|
where: {
|
||||||
userId_roomId: {
|
userId_roomId: {
|
||||||
@ -478,6 +479,7 @@ export class ChatService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove user from chat room
|
||||||
await prisma.chatRoomMember.delete({
|
await prisma.chatRoomMember.delete({
|
||||||
where: {
|
where: {
|
||||||
userId_roomId: {
|
userId_roomId: {
|
||||||
@ -487,6 +489,7 @@ export class ChatService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Get updated chat room
|
||||||
const updatedRoom = await prisma.chatRoom.findUnique({
|
const updatedRoom = await prisma.chatRoom.findUnique({
|
||||||
where: { id: roomId },
|
where: { id: roomId },
|
||||||
include: {
|
include: {
|
||||||
|
|||||||
@ -2,14 +2,12 @@ import { prisma } from "@/config/database.js";
|
|||||||
import { checkMessageRateLimit } from "@/middleware/rateLimiter.js";
|
import { checkMessageRateLimit } from "@/middleware/rateLimiter.js";
|
||||||
import { ChatService } from "@/services/chatService.js";
|
import { ChatService } from "@/services/chatService.js";
|
||||||
import { MessageService } from "@/services/messageService.js";
|
import { MessageService } from "@/services/messageService.js";
|
||||||
import { AuthenticatedSocket, CreateChatRoomRequest, ReactToMessageRequest, SendMessageRequest } from "@/types/index.js";
|
import { AuthenticatedSocket, ReactToMessageRequest, SendMessageRequest } from "@/types/index.js";
|
||||||
import { Server } from "socket.io";
|
import { Server } from "socket.io";
|
||||||
|
|
||||||
export const handleConnection = async (io: Server, socket: AuthenticatedSocket) => {
|
export const handleConnection = async (io: Server, socket: AuthenticatedSocket) => {
|
||||||
console.log(`User connected: ${socket.user.username} (${socket.user.id})`);
|
console.log(`User connected: ${socket.user.username} (${socket.user.id})`);
|
||||||
|
|
||||||
socket.join(socket.userId);
|
|
||||||
|
|
||||||
await updateUserOnlineStatus(socket.userId, true);
|
await updateUserOnlineStatus(socket.userId, true);
|
||||||
|
|
||||||
const onlineUsers = await prisma.user.findMany({
|
const onlineUsers = await prisma.user.findMany({
|
||||||
@ -35,8 +33,6 @@ export const handleConnection = async (io: Server, socket: AuthenticatedSocket)
|
|||||||
|
|
||||||
socket.on("messages_seen", (data: { roomId: string; messageIds: string[] }) => handleMessagesSeen(io, socket, data));
|
socket.on("messages_seen", (data: { roomId: string; messageIds: string[] }) => handleMessagesSeen(io, socket, data));
|
||||||
|
|
||||||
socket.on("create_chat_room", (data: CreateChatRoomRequest) => handleCreateChatRoom(io, socket, data));
|
|
||||||
|
|
||||||
socket.on("disconnect", () => handleDisconnect(socket));
|
socket.on("disconnect", () => handleDisconnect(socket));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -224,27 +220,6 @@ const handleJoinRoom = async (socket: AuthenticatedSocket, roomId: string) => {
|
|||||||
await socket.join(roomId);
|
await socket.join(roomId);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateChatRoom = async (io: Server, socket: AuthenticatedSocket, data: CreateChatRoomRequest) => {
|
|
||||||
try {
|
|
||||||
const result = await ChatService.createChatRoom(socket.userId, data);
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
const room = result.data;
|
|
||||||
|
|
||||||
room.members.forEach((member: { userId: string | string[] }) => {
|
|
||||||
io.to(member.userId).emit("chat_room_created", room);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.emit("chat_room_created", room);
|
|
||||||
} else {
|
|
||||||
socket.emit("error", { message: result.error });
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error creating chat room: ${error}`);
|
|
||||||
socket.emit("error", { message: "Failed to create chat room" });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLeaveRoom = async (socket: AuthenticatedSocket, roomId: string) => {
|
const handleLeaveRoom = async (socket: AuthenticatedSocket, roomId: string) => {
|
||||||
await socket.leave(roomId);
|
await socket.leave(roomId);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { chatAPI } from '@/lib/api';
|
import { chatAPI } from '@/lib/api';
|
||||||
import { useAuthStore } from '@/stores/authStore';
|
import { useAuthStore } from '@/stores/authStore';
|
||||||
import { useChatStore } from '@/stores/chatStore';
|
import { useChatStore } from '@/stores/chatStore';
|
||||||
import { useSocketStore } from '@/stores/socketStore';
|
|
||||||
import type { ChatRoom, CreateChatRoomRequest } from '@/types';
|
import type { ChatRoom, CreateChatRoomRequest } from '@/types';
|
||||||
import { useQuery } from '@tanstack/react-query';
|
import { useMutation, useQuery } from '@tanstack/react-query';
|
||||||
import {
|
import {
|
||||||
Laptop,
|
Laptop,
|
||||||
LogOut,
|
LogOut,
|
||||||
@ -53,9 +52,8 @@ export const ChatSidebar: FC<ChatSidebarProps> = ({
|
|||||||
memberUsernames: [],
|
memberUsernames: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { chatRooms, onlineUsers, setChatRooms } = useChatStore();
|
const { chatRooms, onlineUsers, setChatRooms, addChatRoom } = useChatStore();
|
||||||
const { user, logout } = useAuthStore();
|
const { user, logout } = useAuthStore();
|
||||||
const { socket } = useSocketStore();
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { theme, setTheme } = useTheme();
|
const { theme, setTheme } = useTheme();
|
||||||
|
|
||||||
@ -71,18 +69,29 @@ export const ChatSidebar: FC<ChatSidebarProps> = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const createRoomMutation = useMutation({
|
||||||
|
mutationKey: ['createChatRoom'],
|
||||||
|
mutationFn: chatAPI.createChatRoom,
|
||||||
|
onSuccess: (response) => {
|
||||||
|
if (response.data.success) {
|
||||||
|
const newRoom = response.data.data;
|
||||||
|
addChatRoom(newRoom);
|
||||||
|
setIsCreateDialogOpen(false);
|
||||||
|
setNewRoomData({ name: '', description: '', memberUsernames: [] });
|
||||||
|
toast.success('Chat room created successfully');
|
||||||
|
|
||||||
|
navigate(`/room/${newRoom.id}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
onError: (error: any) => {
|
||||||
|
toast.error(error.response?.data?.error ?? 'Failed to create chat room');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const handleCreateRoom = (e: FormEvent) => {
|
const handleCreateRoom = (e: FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
createRoomMutation.mutate(newRoomData);
|
||||||
if (!socket) {
|
|
||||||
toast.error('Socket connection is not established');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.emit('create_chat_room', newRoomData);
|
|
||||||
|
|
||||||
setNewRoomData({ name: '', description: '', memberUsernames: [] });
|
|
||||||
setIsCreateDialogOpen(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOnlineMembersCount = (room: ChatRoom) => {
|
const getOnlineMembersCount = (room: ChatRoom) => {
|
||||||
@ -184,8 +193,12 @@ export const ChatSidebar: FC<ChatSidebarProps> = ({
|
|||||||
placeholder="user1, user2, user3"
|
placeholder="user1, user2, user3"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Button type="submit" className="w-full">
|
<Button
|
||||||
Create Room
|
type="submit"
|
||||||
|
className="w-full"
|
||||||
|
disabled={createRoomMutation.isPending}
|
||||||
|
>
|
||||||
|
{createRoomMutation.isPending ? 'Creating...' : 'Create Room'}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
|
|||||||
@ -198,11 +198,11 @@ export const MessageCard: FC<{ message: Message }> = ({ message }) => {
|
|||||||
'flex items-center gap-1 text-xs px-2 py-1 rounded-full',
|
'flex items-center gap-1 text-xs px-2 py-1 rounded-full',
|
||||||
hasUserReacted(message.reactions, type)
|
hasUserReacted(message.reactions, type)
|
||||||
? type === 'like'
|
? type === 'like'
|
||||||
? 'bg-red-100 dark:bg-red-900/20'
|
? 'bg-red-100'
|
||||||
: type === 'thumbs_up'
|
: type === 'thumbs_up'
|
||||||
? 'bg-blue-100 dark:bg-blue-900/20'
|
? 'bg-blue-100'
|
||||||
: 'bg-yellow-100 dark:bg-yellow-900/20'
|
: 'bg-yellow-100'
|
||||||
: 'bg-gray-100 dark:bg-gray-700'
|
: 'bg-gray-100'
|
||||||
)}
|
)}
|
||||||
onClick={() => handleReaction(message.id, type)}
|
onClick={() => handleReaction(message.id, type)}
|
||||||
>
|
>
|
||||||
@ -210,9 +210,8 @@ export const MessageCard: FC<{ message: Message }> = ({ message }) => {
|
|||||||
<Heart
|
<Heart
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-3 w-3',
|
'h-3 w-3',
|
||||||
hasUserReacted(message.reactions, type)
|
hasUserReacted(message.reactions, type) &&
|
||||||
? 'fill-red-500 text-red-500'
|
'fill-red-500 text-red-500'
|
||||||
: 'text-gray-600 dark:text-gray-300'
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -220,9 +219,8 @@ export const MessageCard: FC<{ message: Message }> = ({ message }) => {
|
|||||||
<ThumbsUp
|
<ThumbsUp
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-3 w-3',
|
'h-3 w-3',
|
||||||
hasUserReacted(message.reactions, type)
|
hasUserReacted(message.reactions, type) &&
|
||||||
? 'fill-blue-500 text-blue-500'
|
'fill-blue-500 text-blue-500'
|
||||||
: 'text-gray-600 dark:text-gray-300'
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -230,13 +228,12 @@ export const MessageCard: FC<{ message: Message }> = ({ message }) => {
|
|||||||
<Smile
|
<Smile
|
||||||
className={cn(
|
className={cn(
|
||||||
'h-3 w-3',
|
'h-3 w-3',
|
||||||
hasUserReacted(message.reactions, type)
|
hasUserReacted(message.reactions, type) &&
|
||||||
? 'text-yellow-500'
|
'fill-yellow-500 text-yellow-500'
|
||||||
: 'text-gray-600 dark:text-gray-300'
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<span className="text-gray-600 dark:text-gray-300">{count}</span>
|
<span className="text-gray-600 dark:text-black">{count}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { useAuthStore } from '@/stores/authStore';
|
|
||||||
import { useChatStore } from '@/stores/chatStore';
|
import { useChatStore } from '@/stores/chatStore';
|
||||||
import { Settings, Users } from 'lucide-react';
|
import { Settings, Users } from 'lucide-react';
|
||||||
import { useState, type FC } from 'react';
|
import { useState, type FC } from 'react';
|
||||||
@ -11,7 +10,7 @@ export const NavBar: FC = () => {
|
|||||||
const { chatRooms, onlineUsers } = useChatStore();
|
const { chatRooms, onlineUsers } = useChatStore();
|
||||||
const { roomId } = useParams<{ roomId: string }>();
|
const { roomId } = useParams<{ roomId: string }>();
|
||||||
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
|
||||||
const { user } = useAuthStore();
|
|
||||||
const currentRoom = chatRooms.find((room) => room.id === roomId);
|
const currentRoom = chatRooms.find((room) => room.id === roomId);
|
||||||
|
|
||||||
const getOnlineMembersCount = () => {
|
const getOnlineMembersCount = () => {
|
||||||
@ -46,10 +45,7 @@ export const NavBar: FC = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{currentRoom &&
|
{currentRoom && (
|
||||||
currentRoom.members.some(
|
|
||||||
(member) => member.userId === user?.id && member.role === 'admin'
|
|
||||||
) && (
|
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
|
|||||||
@ -55,6 +55,7 @@ export const chatAPI = {
|
|||||||
limit = 50
|
limit = 50
|
||||||
): Promise<AxiosResponse<ApiResponse>> =>
|
): Promise<AxiosResponse<ApiResponse>> =>
|
||||||
api.get(`/messages/${roomId}?page=${page}&limit=${limit}`),
|
api.get(`/messages/${roomId}?page=${page}&limit=${limit}`),
|
||||||
|
// Add these new endpoints
|
||||||
updateChatRoom: (
|
updateChatRoom: (
|
||||||
roomId: string,
|
roomId: string,
|
||||||
data: UpdateChatRoomRequest
|
data: UpdateChatRoomRequest
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useChatStore } from '@/stores/chatStore';
|
import { useChatStore } from '@/stores/chatStore';
|
||||||
import { useSocketStore } from '@/stores/socketStore';
|
import { useSocketStore } from '@/stores/socketStore';
|
||||||
import type { ChatRoom, Message, MessageReaction } from '@/types';
|
import type { Message, MessageReaction } from '@/types';
|
||||||
import { io, type Socket } from 'socket.io-client';
|
import { io, type Socket } from 'socket.io-client';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
@ -116,23 +116,13 @@ class SocketService {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
this.socket.on('chat_room_created', (room: ChatRoom) => {
|
|
||||||
const { chatRooms, addChatRoom } = useChatStore.getState();
|
|
||||||
|
|
||||||
if (!chatRooms.some((r) => r.id === room.id)) {
|
|
||||||
addChatRoom(room);
|
|
||||||
toast.success('Chat room created successfully', {
|
|
||||||
id: 'creating-room',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.socket.on(
|
this.socket.on(
|
||||||
'rate_limit_exceeded',
|
'rate_limit_exceeded',
|
||||||
(data: { message: string; remainingTime?: number }) => {
|
(data: { message: string; remainingTime?: number }) => {
|
||||||
console.warn('Rate limit exceeded:', data.message);
|
console.warn('Rate limit exceeded:', data.message);
|
||||||
toast.error(`Rate limit exceeded: ${data.message}`, {
|
toast.error(`Rate limit exceeded: ${data.message}`, {
|
||||||
duration: data.remainingTime ? data.remainingTime * 1000 : 5000,
|
duration: data.remainingTime ? data.remainingTime * 1000 : 5000,
|
||||||
|
position: 'top-right',
|
||||||
style: {
|
style: {
|
||||||
background: '#f8d7da',
|
background: '#f8d7da',
|
||||||
color: '#721c24',
|
color: '#721c24',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user