Added way too much stuff

This commit is contained in:
Logan Hunt 2022-04-01 16:04:00 -06:00
parent 1108970a6a
commit dbb9eea25f
Signed by untrusted user who does not match committer: simponic
GPG Key ID: 52B3774857EB24B1
13 changed files with 219 additions and 34 deletions

View File

@ -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;
}
}

View File

@ -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

View File

@ -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');
}
};

View File

@ -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();
}
});

View File

@ -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];
};

View File

@ -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);
}

View File

@ -43,6 +43,11 @@ export class AddChatRoom1648605030863 implements MigrationInterface {
type: 'varchar',
isNullable: true,
},
{
name: 'lastConnection',
type: 'timestamp',
default: 'now()',
},
],
}),
true,

View File

@ -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');
}
}

View File

@ -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[];
}

View 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;
}

View File

@ -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],

View File

@ -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,

View File

@ -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);
}