Added way too much stuff
This commit is contained in:
parent
1108970a6a
commit
dbb9eea25f
@ -66,11 +66,23 @@ body {
|
||||
box-shadow: rgb( 0, 0, 0, 0.6) 6px 45px 45px -12px;
|
||||
}
|
||||
|
||||
.chat {
|
||||
.chat-container {
|
||||
border-bottom: 1px solid #d65d0e;
|
||||
height: 200px;
|
||||
overflow-y: scroll;
|
||||
padding-bottom: 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: min-content;
|
||||
}
|
||||
|
||||
.chat-box {
|
||||
flex: 3;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
.chat-connections {
|
||||
flex: 1;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
* {
|
||||
@ -117,3 +129,4 @@ body {
|
||||
border-radius: 8px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { useEffect, useState, useContext } from 'react';
|
||||
import { ApiContext } from '../../utils/api_context';
|
||||
import { useMessages } from '../../utils/use_messages';
|
||||
import { useChat } from '../../utils/use_chat';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { generateGruvboxFromString } from '../../utils/generate_gruvbox';
|
||||
|
||||
@ -11,7 +11,7 @@ import { generateGruvboxFromString } from '../../utils/generate_gruvbox';
|
||||
export const ChatRoom = () => {
|
||||
const { id } = useParams();
|
||||
const [chatRoom, setChatRoom] = useState('');
|
||||
const [messages, sendMessage] = useMessages(chatRoom);
|
||||
const [connections, messages, sendMessage] = useChat(chatRoom);
|
||||
const [message, setMessage] = useState('');
|
||||
const [color, setColor] = useState(generateGruvboxFromString('placeholder'));
|
||||
const [user, setUser] = useState({});
|
||||
@ -56,14 +56,26 @@ export const ChatRoom = () => {
|
||||
<div style={{ textAlign: 'center' }}>
|
||||
<h2>{chatRoom?.name || `Chat Room ${chatRoom?.id}`}</h2>
|
||||
</div>
|
||||
<div id="chat" className="chat">
|
||||
<p>Welcome!</p>
|
||||
{messages.map((message) => (
|
||||
<div key={message.id} style={{ lineBreak: 'normal' }}>
|
||||
<span style={{ color: generateGruvboxFromString(message.userName) }}>{message.userName}: </span>
|
||||
<span>{message.content}</span>
|
||||
</div>
|
||||
))}
|
||||
<div className="chat-container">
|
||||
<div id="chat" className="chat-box">
|
||||
{messages.map((message) => (
|
||||
<div key={message.id} style={{ lineBreak: 'normal' }}>
|
||||
<span style={{ color: generateGruvboxFromString(message.userName) }}>{message.userName}: </span>
|
||||
<span>{message.content}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="chat-connections">
|
||||
<h1>Connected Users ({connections.length})</h1>
|
||||
<hr />
|
||||
<ul>
|
||||
{connections.map((user) => (
|
||||
<li key={user.id} style={{ color: generateGruvboxFromString(user.userName) }}>
|
||||
{user.userName}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<textarea
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { ApiContext } from '../../utils/api_context';
|
||||
import { AuthContext } from '../../utils/auth_context';
|
||||
@ -31,6 +32,8 @@ export const Home = () => {
|
||||
const joinable = await api.get(`/chat_rooms/${id}/joinable?lat=${userPosition.lat}&lng=${userPosition.lng}`);
|
||||
if (joinable) {
|
||||
navigate(`/rooms/${id}`);
|
||||
} else {
|
||||
toast.error('Room is not joinable');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { useLeafletContext } from '@react-leaflet/core';
|
||||
import L from 'leaflet';
|
||||
import markerIconPng from 'leaflet/dist/images/marker-icon.png';
|
||||
import { useEffect, useContext, useState } from 'react';
|
||||
import toast from 'react-hot-toast';
|
||||
import { ApiContext } from '../../utils/api_context';
|
||||
|
||||
const userPositionBubble = {
|
||||
@ -162,7 +163,12 @@ export const Geoman = ({ user, userPos, joinRoom }) => {
|
||||
longitude,
|
||||
radius: shape.layer.getRadius(),
|
||||
});
|
||||
console.log(chatRoom);
|
||||
if (chatRoom.error) {
|
||||
toast.error(chatRoom.error);
|
||||
} else if (chatRoom.id) {
|
||||
toast.success('Chat room created');
|
||||
}
|
||||
|
||||
reRender();
|
||||
}
|
||||
});
|
||||
|
@ -2,9 +2,11 @@ import { useState, useContext, useEffect, useRef } from 'react';
|
||||
import { AuthContext } from './auth_context';
|
||||
import { io } from 'socket.io-client';
|
||||
|
||||
export const useMessages = (chatRoom) => {
|
||||
export const useChat = (chatRoom) => {
|
||||
const [messages, setMessages] = useState([]);
|
||||
const [connections, setConnections] = useState([]);
|
||||
const messageRef = useRef([]);
|
||||
const connectionsRef = useRef([]);
|
||||
const [socket, setSocket] = useState({});
|
||||
const [authToken] = useContext(AuthContext);
|
||||
|
||||
@ -25,6 +27,10 @@ export const useMessages = (chatRoom) => {
|
||||
messageRef.current.push(message);
|
||||
setMessages([...messageRef.current]);
|
||||
});
|
||||
socket.on('userlist', ({ users }) => {
|
||||
connectionsRef.current = users;
|
||||
setConnections([...connectionsRef.current]);
|
||||
});
|
||||
return () => {
|
||||
socket.off('new-message');
|
||||
socket.close();
|
||||
@ -38,5 +44,5 @@ export const useMessages = (chatRoom) => {
|
||||
}
|
||||
};
|
||||
|
||||
return [messages, sendMessage];
|
||||
return [connections, messages, sendMessage];
|
||||
};
|
@ -19,7 +19,11 @@ const haversine = (p1, p2) => {
|
||||
};
|
||||
@Controller()
|
||||
export class ChatRoomController {
|
||||
constructor(private chatRoomService: ChatRoomService, private usersService: UsersService) {}
|
||||
constructor(private chatRoomService: ChatRoomService, private usersService: UsersService) {
|
||||
setInterval(() => {
|
||||
console.log('Hello');
|
||||
}, 60 * 1000);
|
||||
}
|
||||
|
||||
@Get('/chat_rooms')
|
||||
async get(@JwtBody() jwtBody: JwtBodyDto, @Query() query: any) {
|
||||
@ -57,6 +61,11 @@ export class ChatRoomController {
|
||||
|
||||
@Post('/chat_rooms')
|
||||
async create(@JwtBody() jwtBody: JwtBodyDto, @Body() chatRoom: any) {
|
||||
if (chatRoom.radius > 1000) {
|
||||
return {
|
||||
error: 'Radius cannot be greater than 1000 meters',
|
||||
};
|
||||
}
|
||||
chatRoom.user = await this.usersService.find(jwtBody.userId);
|
||||
return await this.chatRoomService.create(chatRoom);
|
||||
}
|
||||
|
@ -43,6 +43,11 @@ export class AddChatRoom1648605030863 implements MigrationInterface {
|
||||
type: 'varchar',
|
||||
isNullable: true,
|
||||
},
|
||||
{
|
||||
name: 'lastConnection',
|
||||
type: 'timestamp',
|
||||
default: 'now()',
|
||||
},
|
||||
],
|
||||
}),
|
||||
true,
|
||||
|
@ -0,0 +1,54 @@
|
||||
import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm';
|
||||
|
||||
export class AddConnectedUsers1648844808010 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.createTable(
|
||||
new Table({
|
||||
name: 'chat_room_connection',
|
||||
columns: [
|
||||
{
|
||||
name: 'id',
|
||||
type: 'int',
|
||||
isPrimary: true,
|
||||
isGenerated: true,
|
||||
},
|
||||
{
|
||||
name: 'userId',
|
||||
type: 'int',
|
||||
isNullable: false,
|
||||
},
|
||||
{
|
||||
name: 'chatRoomId',
|
||||
type: 'text',
|
||||
isNullable: false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
true,
|
||||
);
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
'chat_room_connection',
|
||||
new TableForeignKey({
|
||||
columnNames: ['userId'],
|
||||
referencedColumnNames: ['id'],
|
||||
referencedTableName: 'user',
|
||||
onDelete: 'CASCADE',
|
||||
}),
|
||||
);
|
||||
|
||||
await queryRunner.createForeignKey(
|
||||
'chat_room_connection',
|
||||
new TableForeignKey({
|
||||
columnNames: ['chatRoomId'],
|
||||
referencedColumnNames: ['id'],
|
||||
referencedTableName: 'chat_room',
|
||||
onDelete: 'CASCADE',
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.dropTable('connected_users');
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany } from 'typeorm';
|
||||
import { ChatRoomConnection } from './chat_room_connection.entity';
|
||||
import { User } from './user.entity';
|
||||
|
||||
@Entity()
|
||||
@ -18,6 +19,12 @@ export class ChatRoom {
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@Column()
|
||||
lastConnection: Date;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.chatRooms)
|
||||
user: User;
|
||||
|
||||
@OneToMany(() => ChatRoomConnection, (chatRoomConnection) => chatRoomConnection.chatRoom)
|
||||
chatRoomConnections: ChatRoomConnection[];
|
||||
}
|
||||
|
15
server/entities/chat_room_connection.entity.ts
Normal file
15
server/entities/chat_room_connection.entity.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user.entity';
|
||||
import { ChatRoom } from './chat_room.entity';
|
||||
|
||||
@Entity()
|
||||
export class ChatRoomConnection {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: string;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.chatRooms)
|
||||
user: User;
|
||||
|
||||
@ManyToOne(() => ChatRoom, (chatRoom) => chatRoom.chatRoomConnections)
|
||||
chatRoom: ChatRoom;
|
||||
}
|
@ -2,12 +2,13 @@ import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ChatRoomController } from 'server/controllers/chat_room.controller';
|
||||
import { ChatRoom } from 'server/entities/chat_room.entity';
|
||||
import { ChatRoomConnection } from 'server/entities/chat_room_connection.entity';
|
||||
import { ChatRoomService } from 'server/providers/services/chat_room.service';
|
||||
import { UsersService } from 'server/providers/services/users.service';
|
||||
import { UsersModule } from './users.module';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([ChatRoom]), UsersModule],
|
||||
imports: [TypeOrmModule.forFeature([ChatRoom, ChatRoomConnection]), UsersModule],
|
||||
controllers: [ChatRoomController],
|
||||
providers: [ChatRoomService, UsersService],
|
||||
exports: [TypeOrmModule],
|
||||
|
@ -14,6 +14,7 @@ import { GatewayJwtBody } from 'server/decorators/gateway_jwt_body.decorator';
|
||||
import { JwtBodyDto } from 'server/dto/jwt_body.dto';
|
||||
import { Server, Socket } from 'socket.io';
|
||||
import { GatewayAuthGuard } from '../guards/gatewayauth.guard';
|
||||
import { ChatRoomService } from '../services/chat_room.service';
|
||||
import { JwtService } from '../services/jwt.service';
|
||||
import { UsersService } from '../services/users.service';
|
||||
|
||||
@ -23,31 +24,40 @@ export class ChatRoomGateway implements OnGatewayInit, OnGatewayConnection, OnGa
|
||||
@WebSocketServer()
|
||||
server: Server;
|
||||
|
||||
constructor(private jwtService: JwtService, private userService: UsersService) {}
|
||||
constructor(
|
||||
private jwtService: JwtService,
|
||||
private userService: UsersService,
|
||||
private chatRoomService: ChatRoomService,
|
||||
) {}
|
||||
|
||||
afterInit(server: Server) {
|
||||
console.log('Sockets initialized');
|
||||
}
|
||||
|
||||
handleConnection(client: Socket) {
|
||||
// you can do things like add users to rooms
|
||||
// or emit events here.
|
||||
// IMPORTANT! The GatewayAuthGuard doesn't trigger on these handlers
|
||||
// if you need to do anything in this method you need to authenticate the JWT
|
||||
// manually.
|
||||
async handleConnection(client: Socket) {
|
||||
try {
|
||||
const jwt = client.handshake.auth.token;
|
||||
const jwtBody = this.jwtService.parseToken(jwt);
|
||||
const chatRoomId = client.handshake.query.chatRoomId;
|
||||
console.log('Client Connected: ', jwtBody.userId);
|
||||
client.join(chatRoomId);
|
||||
const jwtBody = this.jwtService.parseToken(client.handshake.auth.token);
|
||||
const user = await this.userService.find(jwtBody.userId);
|
||||
const chatRoom = await this.chatRoomService.findById(client.handshake.query.chatRoomId as unknown as string);
|
||||
await this.chatRoomService.connectUser(chatRoom, user);
|
||||
client.join(chatRoom.id);
|
||||
this.server.to(chatRoom.id).emit('userlist', {
|
||||
users: await this.chatRoomService.connectedUsers(chatRoom),
|
||||
});
|
||||
} catch (e) {
|
||||
throw new WsException('Invalid token');
|
||||
throw new WsException(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
handleDisconnect(client: Socket) {
|
||||
async handleDisconnect(client: Socket) {
|
||||
console.log('Client Disconnected');
|
||||
const jwtBody = this.jwtService.parseToken(client.handshake.auth.token);
|
||||
const user = await this.userService.find(jwtBody.userId);
|
||||
const chatRoom = await this.chatRoomService.findById(client.handshake.query.chatRoomId as unknown as string);
|
||||
await this.chatRoomService.disconnectUser(chatRoom, user);
|
||||
this.server.to(chatRoom.id).emit('userlist', {
|
||||
users: await this.chatRoomService.connectedUsers(chatRoom),
|
||||
});
|
||||
}
|
||||
|
||||
@SubscribeMessage('message')
|
||||
@ -58,7 +68,7 @@ export class ChatRoomGateway implements OnGatewayInit, OnGatewayConnection, OnGa
|
||||
) {
|
||||
const user = await this.userService.find(jwtBody.userId);
|
||||
this.server.to(client.handshake.query.chatRoomId).emit('new-message', {
|
||||
id: user.id * Math.random() * 2048 * Date.now(),
|
||||
id: user.id * Math.random() * Math.pow(2, 16) * Date.now(),
|
||||
content: data,
|
||||
userName: `${user.firstName} ${user.lastName}`,
|
||||
userId: user.id,
|
||||
|
@ -2,12 +2,16 @@ import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
import { ChatRoom } from 'server/entities/chat_room.entity';
|
||||
import { User } from 'server/entities/user.entity';
|
||||
import { ChatRoomConnection } from 'server/entities/chat_room_connection.entity';
|
||||
|
||||
@Injectable()
|
||||
export class ChatRoomService {
|
||||
constructor(
|
||||
@InjectRepository(ChatRoom)
|
||||
private chatRoomRepository: Repository<ChatRoom>,
|
||||
@InjectRepository(ChatRoomConnection)
|
||||
private connectedUsersRepository: Repository<ChatRoomConnection>,
|
||||
) {}
|
||||
|
||||
create(chatRoom: ChatRoom) {
|
||||
@ -29,6 +33,46 @@ export class ChatRoomService {
|
||||
return this.chatRoomRepository.findOne(id, { relations });
|
||||
}
|
||||
|
||||
async connectedUsers(chatRoom: ChatRoom) {
|
||||
return this.connectedUsersRepository
|
||||
.find({
|
||||
where: { chatRoom },
|
||||
relations: ['user'],
|
||||
})
|
||||
.then((x) =>
|
||||
x.map((x) => {
|
||||
return {
|
||||
id: x.user.id,
|
||||
userName: `${x.user.firstName} ${x.user.lastName}`,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
connectUser = async function (chatRoom: ChatRoom, user: User) {
|
||||
const connectedUser = await this.connectedUsersRepository.findOne({
|
||||
where: { chatRoom, user },
|
||||
});
|
||||
if (connectedUser) {
|
||||
return connectedUser;
|
||||
}
|
||||
const chatRoomConnection = new ChatRoomConnection();
|
||||
chatRoomConnection.chatRoom = chatRoom;
|
||||
chatRoomConnection.user = user;
|
||||
await this.connectedUsersRepository.save(chatRoomConnection);
|
||||
return this.connectedUsers(chatRoom);
|
||||
};
|
||||
|
||||
disconnectUser = async function (chatRoom: ChatRoom, user: User) {
|
||||
const connectedUser = await this.connectedUsersRepository.findOne({
|
||||
where: { chatRoom, user },
|
||||
});
|
||||
if (connectedUser) {
|
||||
return this.connectedUsersRepository.remove(connectedUser);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
save(chatRoom: ChatRoom) {
|
||||
return this.chatRoomRepository.save(chatRoom);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user