diff --git a/src/http/assets.ts b/src/http/assets.ts new file mode 100644 index 0000000..dc360c9 --- /dev/null +++ b/src/http/assets.ts @@ -0,0 +1,113 @@ +import { Router, Request, Response } from 'express' +import fs from 'fs' +import { httpLogger } from '../utilities/logger' +import { getPublicPath } from '../utilities/storage' +import TileRepository from '../repositories/tileRepository' +import ZoneRepository from '../repositories/zoneRepository' +import SpriteRepository from '../repositories/spriteRepository' +import { AssetData } from '../utilities/types' + +const router = Router() + +// Get all tiles +router.get('/list_tiles', async (req: Request, res: Response) => { + let assets: AssetData[] = [] + const tiles = await TileRepository.getAll() + for (const tile of tiles) { + assets.push({ + key: tile.id, + data: '/assets/tiles/' + tile.id + '.png', + group: 'tiles', + updatedAt: tile.updatedAt + } as AssetData) + } + res.json(assets) +}) + +// Get tiles by zone +router.get('/list_tiles/:zoneId', async (req: Request, res: Response) => { + const zoneId = req.params.zoneId + + if (!zoneId || parseInt(zoneId) === 0) { + return res.status(400).json({ message: 'Invalid zone ID' }) + } + + const zone = await ZoneRepository.getById(parseInt(zoneId)) + if (!zone) { + return res.status(404).json({ message: 'Zone not found' }) + } + + let assets: AssetData[] = [] + const tiles = await TileRepository.getByZoneId(parseInt(zoneId)) + for (const tile of tiles) { + assets.push({ + key: tile.id, + data: '/assets/tiles/' + tile.id + '.png', + group: 'tiles', + updatedAt: tile.updatedAt + } as AssetData) + } + + res.json(assets) +}) + +// Get sprite actions +router.get('/list_sprite_actions/:spriteId', async (req: Request, res: Response) => { + const spriteId = req.params.spriteId + + if (!spriteId || parseInt(spriteId) === 0) { + return res.status(400).json({ message: 'Invalid sprite ID' }) + } + + const sprite = await SpriteRepository.getById(spriteId) + if (!sprite) { + return res.status(404).json({ message: 'Sprite not found' }) + } + + let assets: AssetData[] = [] + sprite.spriteActions.forEach((spriteAction) => { + assets.push({ + key: sprite.id + '-' + spriteAction.action, + data: '/assets/sprites/' + sprite.id + '/' + spriteAction.action + '.png', + group: spriteAction.isAnimated ? 'sprite_animations' : 'sprites', + updatedAt: sprite.updatedAt, + originX: Number(spriteAction.originX.toString()), + originY: Number(spriteAction.originY.toString()), + isAnimated: spriteAction.isAnimated, + frameCount: JSON.parse(JSON.stringify(spriteAction.sprites)).length, + frameWidth: spriteAction.frameWidth, + frameHeight: spriteAction.frameHeight, + frameRate: spriteAction.frameRate + }) + }) + + res.json(assets) +}) + +// Download asset file +router.get('/assets/:type/:spriteId?/:file', (req: Request, res: Response) => { + const assetType = req.params.type + const spriteId = req.params.spriteId + const fileName = req.params.file + + let assetPath + if (assetType === 'sprites' && spriteId) { + assetPath = getPublicPath(assetType, spriteId, fileName) + } else { + assetPath = getPublicPath(assetType, fileName) + } + + if (!fs.existsSync(assetPath)) { + httpLogger.error(`File not found: ${assetPath}`) + return res.status(404).send('Asset not found') + } + + res.sendFile(assetPath, (err) => { + if (err) { + httpLogger.error('Error sending file:', err) + res.status(500).send('Error downloading the asset') + } + }) +}) + +export default router \ No newline at end of file diff --git a/src/http/auth.ts b/src/http/auth.ts new file mode 100644 index 0000000..8585f19 --- /dev/null +++ b/src/http/auth.ts @@ -0,0 +1,90 @@ +import { Router, Request, Response } from 'express' +import UserService from '../services/userService' +import jwt from 'jsonwebtoken' +import config from '../utilities/config' +import { loginAccountSchema, registerAccountSchema, resetPasswordSchema, newPasswordSchema } from '../utilities/zodTypes' + +const router = Router() + +// Login endpoint +router.post('/login', async (req: Request, res: Response) => { + const { username, password } = req.body + + try { + loginAccountSchema.parse({ username, password }) + } catch (error: any) { + return res.status(400).json({ message: error.errors[0]?.message }) + } + + const userService = new UserService() + const user = await userService.login(username, password) + + if (user && typeof user !== 'boolean') { + const token = jwt.sign({ id: user.id }, config.JWT_SECRET, { expiresIn: '4h' }) + return res.status(200).json({ token }) + } + + return res.status(400).json({ message: 'Failed to login' }) +}) + +// Register endpoint +router.post('/register', async (req: Request, res: Response) => { + const { username, email, password } = req.body + + try { + registerAccountSchema.parse({ username, email, password }) + } catch (error: any) { + return res.status(400).json({ message: error.errors[0]?.message }) + } + + const userService = new UserService() + const user = await userService.register(username, email, password) + + if (user) { + return res.status(200).json({ message: 'User registered' }) + } + + return res.status(400).json({ message: 'Failed to register user' }) +}) + +// Reset password endpoint +router.post('/reset-password', async (req: Request, res: Response) => { + const { email } = req.body + + try { + resetPasswordSchema.parse({ email }) + } catch (error: any) { + return res.status(400).json({ message: error.errors[0]?.message }) + } + + const userService = new UserService() + const sentEmail = await userService.requestPasswordReset(email) + + if (sentEmail) { + return res.status(200).json({ message: 'Email has been sent' }) + } + + return res.status(400).json({ message: 'Failed to send password reset request. Perhaps one has already been sent recently, check your spam folder.' }) +}) + +// New password endpoint +router.post('/new-password', async (req: Request, res: Response) => { + const { urlToken, password } = req.body + + try { + newPasswordSchema.parse({ urlToken, password }) + } catch (error: any) { + return res.status(400).json({ message: error.errors[0]?.message }) + } + + const userService = new UserService() + const resetPassword = await userService.resetPassword(urlToken, password) + + if (resetPassword) { + return res.status(200).json({ message: 'Password has been reset' }) + } + + return res.status(400).json({ message: 'Failed to set new password' }) +}) + +export default router \ No newline at end of file diff --git a/src/http/index.ts b/src/http/index.ts new file mode 100644 index 0000000..585ed6b --- /dev/null +++ b/src/http/index.ts @@ -0,0 +1,24 @@ +import { Application } from 'express' +import { httpLogger } from '../utilities/logger' +import fs from 'fs' +import path from 'path' + +async function addHttpRoutes(app: Application) { + const routeFiles = fs.readdirSync(__dirname) + .filter(file => { + return file !== 'index.ts' && + file !== 'index.js' && + (file.endsWith('.ts') || file.endsWith('.js')) + }) + + for (const file of routeFiles) { + const route = await import(path.join(__dirname, file)) + // Use the router directly without additional path prefix + app.use('/', route.default) + httpLogger.info(`Loaded routes from ${file}`) + } + + httpLogger.info('Web routes added') +} + +export { addHttpRoutes } \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 334235f..8354301 100644 --- a/src/server.ts +++ b/src/server.ts @@ -3,7 +3,7 @@ import express, { Application } from 'express' import config from './utilities/config' import { getAppPath } from './utilities/storage' import { createServer as httpServer, Server as HTTPServer } from 'http' -import { addHttpRoutes } from './utilities/http' +import { addHttpRoutes } from './http' import cors from 'cors' import { Server as SocketServer } from 'socket.io' import { Authentication } from './middleware/authentication' diff --git a/src/utilities/http.ts b/src/utilities/http.ts deleted file mode 100644 index 48a3d9e..0000000 --- a/src/utilities/http.ts +++ /dev/null @@ -1,235 +0,0 @@ -import { Application, Request, Response } from 'express' -import UserService from '../services/userService' -import jwt from 'jsonwebtoken' -import config from './config' -import { loginAccountSchema, registerAccountSchema, resetPasswordSchema, newPasswordSchema } from './zodTypes' -import fs from 'fs' -import { httpLogger } from './logger' -import { getPublicPath } from './storage' -import TileRepository from '../repositories/tileRepository' -import { AssetData } from './types' -import ZoneRepository from '../repositories/zoneRepository' -import SpriteRepository from '../repositories/spriteRepository' - -async function addHttpRoutes(app: Application) { - /** - * Login - * @param req - * @param res - */ - app.post('/login', async (req: Request, res: Response) => { - const { username, password } = req.body - - try { - loginAccountSchema.parse({ username, password }) - } catch (error: any) { - return res.status(400).json({ message: error.errors[0]?.message }) - } - - const userService = new UserService() - const user = await userService.login(username, password) - - if (user && typeof user !== 'boolean') { - const token = jwt.sign({ id: user.id }, config.JWT_SECRET, { expiresIn: '4h' }) - return res.status(200).json({ token }) - } - - return res.status(400).json({ message: 'Failed to login' }) - }) - - /** - * Register - * @param req - * @param res - */ - app.post('/register', async (req: Request, res: Response) => { - const { username, email, password } = req.body - - try { - registerAccountSchema.parse({ username, email, password }) - } catch (error: any) { - return res.status(400).json({ message: error.errors[0]?.message }) - } - - const userService = new UserService() - const user = await userService.register(username, email, password) - - if (user) { - return res.status(200).json({ message: 'User registered' }) - } - - return res.status(400).json({ message: 'Failed to register user' }) - }) - - /** - * Reset password - * @param req - * @param res - */ - app.post('/reset-password', async (req: Request, res: Response) => { - const { email } = req.body - - try { - resetPasswordSchema.parse({ email }) - } catch (error: any) { - return res.status(400).json({ message: error.errors[0]?.message }) - } - - const userService = new UserService() - const sentEmail = await userService.requestPasswordReset(email) - - if (sentEmail) { - return res.status(200).json({ message: 'Email has been sent' }) - } - - return res.status(400).json({ message: 'Failed to send password reset request. Perhaps one has already been sent recently, check your spam folder.' }) - }) - - /** - * New password - * @param req - * @param res - */ - app.post('/new-password', async (req: Request, res: Response) => { - const { urlToken, password } = req.body - - try { - newPasswordSchema.parse({ urlToken, password }) - } catch (error: any) { - return res.status(400).json({ message: error.errors[0]?.message }) - } - - const userService = new UserService() - const resetPassword = await userService.resetPassword(urlToken, password) - - if (resetPassword) { - return res.status(200).json({ message: 'Password has been reset' }) - } - - return res.status(400).json({ message: 'Failed to set new password' }) - }) - - /** - * Get all tiles from a zone as an array of ids - * @param req - * @param res - */ - app.get('/assets/list_tiles', async (req: Request, res: Response) => { - // Get all tiles - let assets: AssetData[] = [] - const tiles = await TileRepository.getAll() - for (const tile of tiles) { - assets.push({ - key: tile.id, - data: '/assets/tiles/' + tile.id + '.png', - group: 'tiles', - updatedAt: tile.updatedAt - } as AssetData) - } - - // Return the array - res.json(assets) - }) - - /** - * Get all tiles from a zone and serve as AssetData array - * @param req - * @param res - */ - app.get('/assets/list_tiles/:zoneId', async (req: Request, res: Response) => { - const zoneId = req.params.zoneId - - // Check if zoneId is valid number - if (!zoneId || parseInt(zoneId) === 0) { - return res.status(400).json({ message: 'Invalid zone ID' }) - } - - // Get zone by id - const zone = await ZoneRepository.getById(parseInt(zoneId)) - if (!zone) { - return res.status(404).json({ message: 'Zone not found' }) - } - - // Get all tiles - let assets: AssetData[] = [] - const tiles = await TileRepository.getByZoneId(parseInt(zoneId)) - for (const tile of tiles) { - assets.push({ - key: tile.id, - data: '/assets/tiles/' + tile.id + '.png', - group: 'tiles', - updatedAt: tile.updatedAt - } as AssetData) - } - - // Return the array - res.json(assets) - }) - - app.get('/assets/list_sprite_actions/:spriteId', async (req: Request, res: Response) => { - const spriteId = req.params.spriteId - // Check if spriteId is valid number - if (!spriteId || parseInt(spriteId) === 0) { - return res.status(400).json({ message: 'Invalid sprite ID' }) - } - // Get sprite by id - const sprite = await SpriteRepository.getById(spriteId) - if (!sprite) { - return res.status(404).json({ message: 'Sprite not found' }) - } - - let assets: AssetData[] = [] - sprite.spriteActions.forEach((spriteAction) => { - assets.push({ - key: sprite.id + '-' + spriteAction.action, - data: '/assets/sprites/' + sprite.id + '/' + spriteAction.action + '.png', - group: spriteAction.isAnimated ? 'sprite_animations' : 'sprites', - updatedAt: sprite.updatedAt, - originX: Number(spriteAction.originX.toString()), - originY: Number(spriteAction.originY.toString()), - isAnimated: spriteAction.isAnimated, - frameCount: JSON.parse(JSON.stringify(spriteAction.sprites)).length, - frameWidth: spriteAction.frameWidth, - frameHeight: spriteAction.frameHeight, - frameRate: spriteAction.frameRate - }) - }) - - // Return the array - res.json(assets) - }) - - /** - * Download asset file - * @param req - * @param res - */ - app.get('/assets/:type/:spriteId?/:file', (req: Request, res: Response) => { - const assetType = req.params.type - const spriteId = req.params.spriteId - const fileName = req.params.file - - let assetPath - if (assetType === 'sprites' && spriteId) { - assetPath = getPublicPath(assetType, spriteId, fileName) - } else { - assetPath = getPublicPath(assetType, fileName) - } - - if (!fs.existsSync(assetPath)) { - httpLogger.error(`File not found: ${assetPath}`) - return res.status(404).send('Asset not found') - } - - res.sendFile(assetPath, (err) => { - if (err) { - httpLogger.error('Error sending file:', err) - res.status(500).send('Error downloading the asset') - } - }) - }) - - httpLogger.info('Web routes added') -} - -export { addHttpRoutes }