Complete refractor - we do sexy code only
This commit is contained in:
7
src/app/events/disconnect.ts
Normal file
7
src/app/events/disconnect.ts
Normal 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.`);
|
||||
});
|
||||
}
|
7
src/app/events/player_connect.ts
Normal file
7
src/app/events/player_connect.ts
Normal 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.`);
|
||||
});
|
||||
}
|
7
src/app/events/player_map_load.ts
Normal file
7
src/app/events/player_map_load.ts
Normal 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
6
src/app/logo.txt
Normal file
@ -0,0 +1,6 @@
|
||||
███╗░░██╗███████╗░██╗░░░░░░░██╗ ░██████╗░██╗░░░██╗███████╗░██████╗████████╗
|
||||
████╗░██║██╔════╝░██║░░██╗░░██║ ██╔═══██╗██║░░░██║██╔════╝██╔════╝╚══██╔══╝
|
||||
██╔██╗██║█████╗░░░╚██╗████╗██╔╝ ██║██╗██║██║░░░██║█████╗░░╚█████╗░░░░██║░░░
|
||||
██║╚████║██╔══╝░░░░████╔═████║░ ╚██████╔╝██║░░░██║██╔══╝░░░╚═══██╗░░░██║░░░
|
||||
██║░╚███║███████╗░░╚██╔╝░╚██╔╝░ ░╚═██╔═╝░╚██████╔╝███████╗██████╔╝░░░██║░░░
|
||||
╚═╝░░╚══╝╚══════╝░░░╚═╝░░░╚═╝░░ ░░░╚═╝░░░░╚═════╝░╚══════╝╚═════╝░░░░╚═╝░░░
|
@ -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;
|
33
src/app/repositories/user.ts
Normal file
33
src/app/repositories/user.ts
Normal 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
81
src/app/server.ts
Normal 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
30
src/app/services/user.ts
Normal 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
32
src/app/utilities/api.ts
Normal 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 };
|
10
src/app/utilities/config.ts
Normal file
10
src/app/utilities/config.ts
Normal 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
9
src/application.ts
Normal 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();
|
@ -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');
|
||||
}
|
||||
}
|
@ -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;
|
||||
}[];
|
||||
}
|
@ -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;
|
122
src/server.ts
122
src/server.ts
@ -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}`));
|
@ -1,6 +0,0 @@
|
||||
|
||||
class MapService {
|
||||
|
||||
}
|
||||
|
||||
export default MapService;
|
@ -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;
|
Reference in New Issue
Block a user