diff options
author | Logan Hunt <loganhunt@simponic.xyz> | 2022-04-01 16:04:00 -0600 |
---|---|---|
committer | Logan Hunt <loganhunt@simponic.xyz> | 2022-04-01 16:04:00 -0600 |
commit | dbb9eea25f80e7984a112409863be5191af5bf5e (patch) | |
tree | cca24e23afc5e7bb4b92a0582aaec48f6a08af72 | |
parent | 1108970a6aeb98a2f113383c6437dd4d862dae10 (diff) | |
download | locchat-dbb9eea25f80e7984a112409863be5191af5bf5e.tar.gz locchat-dbb9eea25f80e7984a112409863be5191af5bf5e.zip |
Added way too much stuff
-rw-r--r-- | client/app.css | 19 | ||||
-rw-r--r-- | client/components/chatroom/_chat_room.jsx | 32 | ||||
-rw-r--r-- | client/components/home/_home.jsx | 3 | ||||
-rw-r--r-- | client/components/map/chat_room_geoman.jsx | 8 | ||||
-rw-r--r-- | client/utils/use_chat.js (renamed from client/utils/use_messages.js) | 10 | ||||
-rw-r--r-- | server/controllers/chat_room.controller.ts | 11 | ||||
-rw-r--r-- | server/database/migrations/1648605030863-AddChatRoom.ts | 5 | ||||
-rw-r--r-- | server/database/migrations/1648844808010-AddConnectedUsers.ts | 54 | ||||
-rw-r--r-- | server/entities/chat_room.entity.ts | 9 | ||||
-rw-r--r-- | server/entities/chat_room_connection.entity.ts | 15 | ||||
-rw-r--r-- | server/modules/chat_room.module.ts | 3 | ||||
-rw-r--r-- | server/providers/gateways/chat_room.gateway.ts | 40 | ||||
-rw-r--r-- | server/providers/services/chat_room.service.ts | 44 |
13 files changed, 219 insertions, 34 deletions
diff --git a/client/app.css b/client/app.css index 016c256..09fb38e 100644 --- a/client/app.css +++ b/client/app.css @@ -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; } * { @@ -116,4 +128,5 @@ body { color: #8ec07c; border-radius: 8px; display: inline-block; -}
\ No newline at end of file +} + diff --git a/client/components/chatroom/_chat_room.jsx b/client/components/chatroom/_chat_room.jsx index e70715c..22fb399 100644 --- a/client/components/chatroom/_chat_room.jsx +++ b/client/components/chatroom/_chat_room.jsx @@ -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 diff --git a/client/components/home/_home.jsx b/client/components/home/_home.jsx index a31fd63..f275d3d 100644 --- a/client/components/home/_home.jsx +++ b/client/components/home/_home.jsx @@ -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'); } }; diff --git a/client/components/map/chat_room_geoman.jsx b/client/components/map/chat_room_geoman.jsx index a39b45a..07d0a4c 100644 --- a/client/components/map/chat_room_geoman.jsx +++ b/client/components/map/chat_room_geoman.jsx @@ -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(); } }); diff --git a/client/utils/use_messages.js b/client/utils/use_chat.js index e3a501e..d5250bf 100644 --- a/client/utils/use_messages.js +++ b/client/utils/use_chat.js @@ -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]; }; diff --git a/server/controllers/chat_room.controller.ts b/server/controllers/chat_room.controller.ts index 3917e54..9b75c1f 100644 --- a/server/controllers/chat_room.controller.ts +++ b/server/controllers/chat_room.controller.ts @@ -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); } diff --git a/server/database/migrations/1648605030863-AddChatRoom.ts b/server/database/migrations/1648605030863-AddChatRoom.ts index 747cdd3..e50f049 100644 --- a/server/database/migrations/1648605030863-AddChatRoom.ts +++ b/server/database/migrations/1648605030863-AddChatRoom.ts @@ -43,6 +43,11 @@ export class AddChatRoom1648605030863 implements MigrationInterface { type: 'varchar', isNullable: true, }, + { + name: 'lastConnection', + type: 'timestamp', + default: 'now()', + }, ], }), true, diff --git a/server/database/migrations/1648844808010-AddConnectedUsers.ts b/server/database/migrations/1648844808010-AddConnectedUsers.ts new file mode 100644 index 0000000..e6d16fe --- /dev/null +++ b/server/database/migrations/1648844808010-AddConnectedUsers.ts @@ -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'); + } +} diff --git a/server/entities/chat_room.entity.ts b/server/entities/chat_room.entity.ts index 2981589..36ce585 100644 --- a/server/entities/chat_room.entity.ts +++ b/server/entities/chat_room.entity.ts @@ -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[]; } diff --git a/server/entities/chat_room_connection.entity.ts b/server/entities/chat_room_connection.entity.ts new file mode 100644 index 0000000..5691df1 --- /dev/null +++ b/server/entities/chat_room_connection.entity.ts @@ -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; +} diff --git a/server/modules/chat_room.module.ts b/server/modules/chat_room.module.ts index 7acc672..4a19947 100644 --- a/server/modules/chat_room.module.ts +++ b/server/modules/chat_room.module.ts @@ -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], diff --git a/server/providers/gateways/chat_room.gateway.ts b/server/providers/gateways/chat_room.gateway.ts index b565d40..bee83c5 100644 --- a/server/providers/gateways/chat_room.gateway.ts +++ b/server/providers/gateways/chat_room.gateway.ts @@ -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, diff --git a/server/providers/services/chat_room.service.ts b/server/providers/services/chat_room.service.ts index 6d804e6..148ff19 100644 --- a/server/providers/services/chat_room.service.ts +++ b/server/providers/services/chat_room.service.ts @@ -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); } |