Complete refractor - we do sexy code only

This commit is contained in:
2024-05-09 04:00:05 +02:00
parent ca88c085e6
commit 8356a83431
28 changed files with 449 additions and 492 deletions

View File

@ -0,0 +1,7 @@
import { Socket, Server } from "socket.io";
export default function user_connect(socket: Socket, io: Server) {
socket.on('disconnect', (data) => {
console.log(`---User ${socket.id} has disconnected.`);
});
}

View File

@ -0,0 +1,7 @@
import { Socket, Server } from "socket.io";
export default function player_connect(socket: Socket, io: Server) {
socket.on('player:connect', (data) => {
console.log(`---User ${socket.id} has joined.`);
});
}

View File

@ -0,0 +1,7 @@
import { Socket, Server } from "socket.io";
export default function player_map_load(socket: Socket, io: Server) {
socket.on('player:map:load', (data) => {
console.log(`---User ${socket.id} has requested map.`);
});
}

6
src/app/logo.txt Normal file
View File

@ -0,0 +1,6 @@
███╗░░██╗███████╗░██╗░░░░░░░██╗  ░██████╗░██╗░░░██╗███████╗░██████╗████████╗
████╗░██║██╔════╝░██║░░██╗░░██║  ██╔═══██╗██║░░░██║██╔════╝██╔════╝╚══██╔══╝
██╔██╗██║█████╗░░░╚██╗████╗██╔╝  ██║██╗██║██║░░░██║█████╗░░╚█████╗░░░░██║░░░
██║╚████║██╔══╝░░░░████╔═████║░  ╚██████╔╝██║░░░██║██╔══╝░░░╚═══██╗░░░██║░░░
██║░╚███║███████╗░░╚██╔╝░╚██╔╝░  ░╚═██╔═╝░╚██████╔╝███████╗██████╔╝░░░██║░░░
╚═╝░░╚══╝╚══════╝░░░╚═╝░░░╚═╝░░  ░░░╚═╝░░░░╚═════╝░╚══════╝╚═════╝░░░░╚═╝░░░

View File

@ -1,15 +1,15 @@
import { Map } from '@prisma/client';
import prisma from '../helpers/prisma'; // Import the global Prisma instance
import prisma from '../utilities/prisma'; // Import the global Prisma instance
class MapRepository {
async getFirst(): Promise<Map | null> {
try {
return await prisma.map.findFirst();
} catch (error) {
} catch (error: any) {
// Handle error
throw new Error(`Failed to get first map: ${error.message}`);
}
}
}
export default new MapRepository;
export default MapRepository;

View File

@ -0,0 +1,33 @@
import prisma from '../utilities/prisma'; // Import the global Prisma instance
import { User } from '@prisma/client';
class UserRepository {
async getByUsername(username: string): Promise<User | null> {
try {
return await prisma.user.findUnique({
where: {
username,
},
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to get user by username: ${error.message}`);
}
}
async create(username: string, password: string): Promise<User> {
try {
return await prisma.user.create({
data: {
username,
password,
},
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to create user: ${error.message}`);
}
}
}
export default new UserRepository;

81
src/app/server.ts Normal file
View File

@ -0,0 +1,81 @@
import fs from "fs";
import path from "path";
import express from 'express';
import http from 'http';
import cors from 'cors';
import {Server as HttpServer, Socket} from 'socket.io';
import config from './utilities/config';
import prisma from './utilities/prisma';
import api from "./utilities/api";
export class Server
{
private readonly app: express.Application;
private readonly server: any;
private readonly io: HttpServer;
/**
* Creates an instance of GameServer.
*/
constructor() {
this.app = express();
this.app.use(cors());
this.app.use(express.json());
this.app.use(express.urlencoded({ extended: true }));
this.server = http.createServer(this.app)
this.io = new HttpServer(this.server);
}
/**
* Start the server
*/
public async start() {
// Print logo
const art = fs.readFileSync(path.join(__dirname, 'logo.txt'), 'utf8');
console.log('\x1b[31m%s\x1b[0m', art + '\n');
// Check prisma connection
try {
await prisma.$connect();
console.log('[✅] Database connected');
} catch (error: any) {
throw new Error(`[❌] Database connection failed: ${error.message}`);
}
// Start the server
try {
await this.server.listen(config.PORT);
console.log('[✅] Socket.IO running on port', config.PORT);
} catch (error: any) {
throw new Error(`[❌] Socket.IO failed to start: ${error.message}`);
}
// Add API routes
await api.addAuthRoutes(this.app);
// Add socket events
this.io.on('connection', this.handleConnection.bind(this));
}
/**
* Handle socket connection
* @param socket
* @private
*/
private async handleConnection(socket: Socket) {
const eventsPath = path.join(__dirname, 'events');
try {
const files = await fs.promises.readdir(eventsPath);
for (const file of files) {
if (!file.endsWith('.ts')) {
continue;
}
const module = await import(path.join(eventsPath, file));
module.default(socket, this.io);
}
} catch (error: any) {
throw new Error('[❌] Failed to load event handlers: ' + error.message);
}
}
}

30
src/app/services/user.ts Normal file
View File

@ -0,0 +1,30 @@
import bcrypt from "bcryptjs";
import UserRepository from "../repositories/user";
class UserService {
async login(username: string, password: string): Promise<boolean | any> {
const user = await UserRepository.getByUsername(username);
if (!user) {
return false;
}
const passwordMatch = await bcrypt.compare(password, user.password);
if (!passwordMatch) {
return false;
}
return user;
}
async register(username: string, password: string): Promise<boolean | any> {
const user = await UserRepository.getByUsername(username);
if (user) {
return false;
}
const hashedPassword = await bcrypt.hash(password, 10);
return await UserRepository.create(username, hashedPassword);
}
}
export default UserService;

32
src/app/utilities/api.ts Normal file
View File

@ -0,0 +1,32 @@
import { Request, Response } from 'express';
import UserService from '../services/user';
async function addAuthRoutes(app: any) {
app.post('/login', async (req: Request, res: Response) => {
const { username, password } = req.body;
const userService = new UserService();
const user = await userService.login(username, password);
if (user) {
return res.status(200).json(user);
}
return res.status(401).json({ message: 'Invalid credentials' });
});
app.post('/register', async (req: Request, res: Response) => {
const { username, password } = req.body;
const userService = new UserService();
const user = await userService.register(username, password);
if (user) {
return res.status(201).json(user);
}
return res.status(400).json({ message: 'Failed to register user' });
});
console.log('[🌎] Auth routes added');
}
export default { addAuthRoutes };

View File

@ -0,0 +1,10 @@
import dotenv from "dotenv";
dotenv.config();
class config {
static ENV: string = process.env.ENV || "prod";
static PORT: number = process.env.PORT ? parseInt(process.env.PORT) : 5000;
}
export default config;

9
src/application.ts Normal file
View File

@ -0,0 +1,9 @@
/**
* Entry point of the application
* Made by Dennis Postma
* for New Quest
*/
import { Server } from './app/server';
new Server().start();

View File

@ -1,28 +0,0 @@
import { Request, Response } from 'express';
import UserService from '../services/User';
export async function registerUser(req: Request, res: Response): Promise<void> {
const { username, password } = req.body;
try {
await UserService.createUser(username, password);
res.status(201).send('User registered');
} catch (error) {
console.error('Error registering user:', error);
res.status(500).send('Error registering user');
}
}
export async function loginUser(req: Request, res: Response): Promise<void> {
const { username, password } = req.body;
try {
const isValid = await UserService.validateUserCredentials(username, password);
if (isValid) {
res.send('Login successful');
} else {
res.status(401).send('Invalid credentials');
}
} catch (error) {
console.error('Error validating credentials:', error);
res.status(500).send('Error validating credentials');
}
}

View File

@ -1,17 +0,0 @@
export interface IUser {
id?: number;
username: string;
password: string;
}
export interface IMap {
name: string;
tiles: any;
width: number;
height: number;
players: {
id: number;
x: number;
y: number;
}[];
}

View File

@ -1,20 +0,0 @@
import prisma from '../helpers/prisma'; // Import the global Prisma instance
import { User } from '@prisma/client';
import bcrypt from 'bcryptjs';
class UserRepository {
async getByUsername(username: string): Promise<User | null> {
try {
return await prisma.user.findUnique({
where: {
username,
},
});
} catch (error) {
// Handle error
throw new Error(`Failed to get user by username: ${error.message}`);
}
}
}
export default new UserRepository;

View File

@ -1,122 +0,0 @@
import express from 'express';
import {Server} from 'socket.io';
import http from 'http';
import cors from 'cors';
import UserRepository from "./repositories/User";
import UserService from "./services/User";
import MapRepository from "./repositories/Map";
import {loginUser, registerUser} from './helpers/http';
import {IUser, IMap} from "./helpers/interfaces";
const app = express();
const server = http.createServer(app);
const io = new Server(server, { cors: { origin: '*' } });
app.use(cors());
app.use(express.json());
app.post('/login', loginUser);
app.post('/register', registerUser);
let world_players: any = {};
async function handleSocketConnection(socket: any) {
const { username, password } = socket.handshake.query;
try {
await authenticateUser(socket, username, password);
await initializeUser(socket, username);
await setupMap(socket);
await startTickEmitter(socket);
listenForMoveEvents(socket);
listenForDisconnect(socket, username);
} catch (error) {
console.error('Error handling socket connection:', error.message);
socket.disconnect(true);
}
}
async function authenticateUser(socket: any, username: string, password: string) {
if (!username || !password) {
throw new Error('Username or password missing.');
}
const user = await UserRepository.getByUsername(username);
if (!user || !(await UserService.validateUserCredentials(username, password))) {
throw new Error('Invalid username or password.');
}
console.log('User connected:', username);
socket.user = user;
world_players[user.id] = {
id: user.id,
username: user.username,
coords: {
x: 0,
y: 0
}
};
}
async function initializeUser(socket: any, username: string) {
socket.emit('message', 'Welcome to the server!');
}
async function setupMap(socket: any) {
const map = await MapRepository.getFirst();
socket.join(map.name);
socket.emit('map', {
name: map.name,
tiles: map.tiles,
width: map.width,
height: map.height,
players: world_players
});
socket.emit('message', 'You have joined the room: ' + map.name);
// list world players
socket.emit('message', 'World players: ' + JSON.stringify(world_players));
// let the room know a new player has joined
io.to(map.name).emit('player_join', world_players[socket.user.id]);
return map;
}
async function startTickEmitter(socket: any) {
setInterval(async () => {
const users = await listConnectedUsers();
socket.emit('ping', users);
}, 3000);
}
function listenForMoveEvents(socket: any) {
socket.on('move', (coords: any) => {
console.log('Player moved:', socket.user.id, coords)
const user = socket.user as IUser;
world_players[user.id].coords = coords;
io.in('Test Map').emit('player_moved', {
id: user.id,
coords
});
});
}
//r
function listenForDisconnect(socket: any, username: string) {
socket.on('disconnect', () => {
console.log('User disconnected:', username);
});
}
// function list all connected users
// function list all connected users
async function listConnectedUsers() {
const sockets = await io.in('Test Map').fetchSockets();
// @ts-ignore
return sockets.map(socket => socket.user);
}
io.on('connection', handleSocketConnection);
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => console.log(`Server running on port ${PORT}`));

View File

@ -1,6 +0,0 @@
class MapService {
}
export default MapService;

View File

@ -1,35 +0,0 @@
import bcrypt from "bcryptjs";
import prisma from "../helpers/prisma";
import UserRepository from "../repositories/User";
class UserService {
async createUser(username: string, password: string): Promise<void> {
try {
const hashedPassword = await bcrypt.hash(password, 10);
await prisma.user.create({
data: {
username,
password: hashedPassword,
},
});
} catch (error) {
// Handle error
throw new Error(`Failed to create user: ${error.message}`);
}
}
async validateUserCredentials(username: string, password: string): Promise<boolean> {
try {
const user = await UserRepository.getByUsername(username);
if (!user) return false;
return bcrypt.compareSync(password, user.password);
} catch (error) {
// Handle error
throw new Error(`Failed to validate user credentials: ${error.message}`);
}
}
}
export default new UserService;