removed combined server software

This commit is contained in:
Robert MacRae 2026-01-24 20:26:14 -04:00
parent 62b676c0d1
commit 08edb2d80d
21 changed files with 0 additions and 9798 deletions

View File

@ -1,18 +0,0 @@
# Server Configuration
PORT=3001
NODE_ENV=development
# Database Configuration
MONGODB_URI=mongodb://localhost:27017/galaxystrikeonline
# JWT Configuration
JWT_SECRET=your_jwt_secret_key_here
# Redis Configuration (optional)
REDIS_URL=redis://localhost:6379
# Client URL
CLIENT_URL=http://localhost:3000
# Logging
LOG_LEVEL=info

View File

@ -1,19 +0,0 @@
const mongoose = require('mongoose');
const logger = require('../utils/logger');
const connectDB = async () => {
try {
const mongoUri = process.env.MONGODB_URI || 'mongodb://localhost:27017/galaxystrikeonline';
const conn = await mongoose.connect(mongoUri, {
// Remove deprecated options for newer MongoDB versions
});
logger.info(`MongoDB Connected: ${conn.connection.host}`);
} catch (error) {
logger.error('Database connection error:', error);
process.exit(1);
}
};
module.exports = connectDB;

View File

@ -1,131 +0,0 @@
const logger = require('../utils/logger');
const productionConfig = {
// Server settings
port: process.env.PORT || 3001,
// Database settings
database: {
uri: process.env.MONGODB_URI || 'mongodb://localhost:27017/galaxystrikeonline',
options: {
useNewUrlParser: true,
useUnifiedTopology: true,
maxPoolSize: 10,
serverSelectionTimeoutMS: 5000,
socketTimeoutMS: 45000,
}
},
// JWT settings
jwt: {
secret: process.env.JWT_SECRET,
expiresIn: '24h',
refreshExpiresIn: '7d'
},
// Redis settings (for sessions and caching)
redis: {
url: process.env.REDIS_URL || 'redis://localhost:6379',
options: {
retryDelayOnFailover: 100,
maxRetriesPerRequest: 3,
}
},
// CORS settings
cors: {
origin: process.env.CLIENT_URL || 'http://localhost:3000',
credentials: true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
},
// Rate limiting
rateLimit: {
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP, please try again later.',
standardHeaders: true,
legacyHeaders: false,
},
// Socket.IO settings
socketio: {
cors: {
origin: process.env.CLIENT_URL || 'http://localhost:3000',
methods: ['GET', 'POST']
},
pingTimeout: 60000,
pingInterval: 25000,
maxHttpBufferSize: 1e8, // 100 MB
},
// Logging settings
logging: {
level: process.env.LOG_LEVEL || 'info',
format: process.env.NODE_ENV === 'production' ? 'json' : 'simple',
file: {
enabled: true,
filename: 'logs/app.log',
maxsize: 10485760, // 10MB
maxFiles: 5,
}
},
// Security settings
security: {
helmet: {
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", "data:", "https:"],
},
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
},
compression: {
level: 6,
threshold: 1024,
}
},
// Game settings
game: {
maxPlayersPerServer: 50,
serverCleanupInterval: 300000, // 5 minutes
inactivePlayerTimeout: 1800000, // 30 minutes
autoSaveInterval: 60000, // 1 minute
}
};
// Validate required environment variables
const validateConfig = () => {
const required = ['JWT_SECRET'];
const missing = required.filter(key => !process.env[key]);
if (missing.length > 0) {
logger.error(`Missing required environment variables: ${missing.join(', ')}`);
process.exit(1);
}
if (process.env.NODE_ENV === 'production') {
const prodRequired = ['MONGODB_URI', 'CLIENT_URL'];
const prodMissing = prodRequired.filter(key => !process.env[key]);
if (prodMissing.length > 0) {
logger.error(`Missing required production environment variables: ${prodMissing.join(', ')}`);
process.exit(1);
}
}
};
module.exports = {
...productionConfig,
validateConfig
};

View File

@ -1,134 +0,0 @@
const logger = require('../utils/logger');
// Custom error classes
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
class ValidationError extends AppError {
constructor(message) {
super(message, 400);
}
}
class AuthenticationError extends AppError {
constructor(message = 'Authentication failed') {
super(message, 401);
}
}
class AuthorizationError extends AppError {
constructor(message = 'Access denied') {
super(message, 403);
}
}
class NotFoundError extends AppError {
constructor(message = 'Resource not found') {
super(message, 404);
}
}
class ConflictError extends AppError {
constructor(message = 'Resource conflict') {
super(message, 409);
}
}
class DatabaseError extends AppError {
constructor(message = 'Database operation failed') {
super(message, 500);
}
}
// Error handling middleware
const errorHandler = (err, req, res, next) => {
let error = { ...err };
error.message = err.message;
// Log error
logger.error({
error: err,
request: {
method: req.method,
url: req.url,
ip: req.ip,
userAgent: req.get('User-Agent'),
userId: req.userId
}
});
// Mongoose validation error
if (err.name === 'ValidationError') {
const message = Object.values(err.errors).map(val => val.message).join(', ');
error = new ValidationError(message);
}
// Mongoose duplicate key error
if (err.code === 11000) {
const field = Object.keys(err.keyValue)[0];
const value = err.keyValue[field];
error = new ConflictError(`${field} '${value}' already exists`);
}
// Mongoose cast error
if (err.name === 'CastError') {
error = new ValidationError(`Invalid ${err.path}: ${err.value}`);
}
// JWT errors
if (err.name === 'JsonWebTokenError') {
error = new AuthenticationError('Invalid token');
}
if (err.name === 'TokenExpiredError') {
error = new AuthenticationError('Token expired');
}
// Default error
if (!error.isOperational) {
error = new AppError('Something went wrong', 500);
}
res.status(error.statusCode || 500).json({
status: error.status || 'error',
message: error.message,
...(process.env.NODE_ENV === 'development' && {
stack: error.stack,
error: err
})
});
};
// Async error wrapper
const catchAsync = (fn) => {
return (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
};
// 404 handler
const notFound = (req, res, next) => {
const error = new NotFoundError(`Route ${req.originalUrl} not found`);
next(error);
};
module.exports = {
AppError,
ValidationError,
AuthenticationError,
AuthorizationError,
NotFoundError,
ConflictError,
DatabaseError,
errorHandler,
catchAsync,
notFound
};

View File

@ -1,306 +0,0 @@
const mongoose = require('mongoose');
const inventorySchema = new mongoose.Schema({
userId: {
type: String,
required: true,
ref: 'Player'
},
// Inventory settings
maxSlots: {
type: Number,
default: 50
},
// Items array
items: [{
id: {
type: String,
required: true
},
name: {
type: String,
required: true
},
type: {
type: String,
required: true,
enum: ['weapon', 'armor', 'material', 'consumable']
},
rarity: {
type: String,
enum: ['common', 'uncommon', 'rare', 'epic', 'legendary'],
default: 'common'
},
quantity: {
type: Number,
default: 1,
min: 1
},
// Item stats (for weapons/armor)
stats: {
attack: { type: Number, default: 0 },
defense: { type: Number, default: 0 },
speed: { type: Number, default: 0 },
criticalChance: { type: Number, default: 0 },
criticalDamage: { type: Number, default: 1.5 },
damage: { type: Number, default: 0 },
fireRate: { type: Number, default: 0 },
range: { type: Number, default: 0 },
energy: { type: Number, default: 0 },
health: { type: Number, default: 0 },
maxHealth: { type: Number, default: 0 },
durability: { type: Number, default: 0 },
weight: { type: Number, default: 0 },
energyShield: { type: Number, default: 0 }
},
// Item properties
description: {
type: String,
default: ''
},
// Equipment properties
equipable: {
type: Boolean,
default: false
},
slot: {
type: String,
enum: ['weapon', 'armor', 'engine', 'shield', 'special'],
default: null
},
isEquipped: {
type: Boolean,
default: false
},
// Consumable properties
consumable: {
type: Boolean,
default: false
},
effect: {
health: { type: Number, default: 0 },
energy: { type: Number, default: 0 },
attack: { type: Number, default: 0 },
defense: { type: Number, default: 0 },
speed: { type: Number, default: 0 },
duration: { type: Number, default: 0 }
},
// Stackable items
stackable: {
type: Boolean,
default: false
},
// Timestamps
acquiredAt: {
type: Date,
default: Date.now
},
lastUsed: {
type: Date,
default: null
}
}],
// Equipped items
equippedItems: {
weapon: { type: String, default: null },
armor: { type: String, default: null },
engine: { type: String, default: null },
shield: { type: String, default: null },
special: { type: String, default: null }
},
// Timestamps
updatedAt: {
type: Date,
default: Date.now
}
}, {
timestamps: true
});
// Indexes for performance
inventorySchema.index({ userId: 1 });
inventorySchema.index({ 'items.id': 1 });
inventorySchema.index({ 'items.type': 1 });
// Methods
inventorySchema.methods.addItem = function(itemData) {
// Check if item already exists and is stackable
const existingItem = this.items.find(item =>
item.id === itemData.id &&
item.type === itemData.type &&
item.stackable
);
if (existingItem) {
existingItem.quantity += itemData.quantity || 1;
} else {
// Add new item
const newItem = {
...itemData,
quantity: itemData.quantity || 1,
acquiredAt: new Date()
};
this.items.push(newItem);
}
this.updatedAt = new Date();
return this.save();
};
inventorySchema.methods.removeItem = function(itemId, quantity = 1) {
const itemIndex = this.items.findIndex(item => item.id === itemId);
if (itemIndex === -1) {
throw new Error('Item not found in inventory');
}
const item = this.items[itemIndex];
if (item.quantity > quantity) {
item.quantity -= quantity;
} else {
// Remove item completely
this.items.splice(itemIndex, 1);
// Unequip if it was equipped
Object.keys(this.equippedItems).forEach(slot => {
if (this.equippedItems[slot] === itemId) {
this.equippedItems[slot] = null;
}
});
}
this.updatedAt = new Date();
return this.save();
};
inventorySchema.methods.hasItem = function(itemId, quantity = 1) {
const item = this.items.find(item => item.id === itemId);
return item && item.quantity >= quantity;
};
inventorySchema.methods.getItemCount = function(itemId) {
const item = this.items.find(item => item.id === itemId);
return item ? item.quantity : 0;
};
inventorySchema.methods.equipItem = function(itemId, slot) {
const item = this.items.find(item => item.id === itemId);
if (!item) {
throw new Error('Item not found in inventory');
}
if (!item.equipable) {
throw new Error('Item is not equipable');
}
if (item.slot !== slot) {
throw new Error('Item cannot be equipped in this slot');
}
// Unequip current item in slot
if (this.equippedItems[slot]) {
const currentItem = this.items.find(item => item.id === this.equippedItems[slot]);
if (currentItem) {
currentItem.isEquipped = false;
}
}
// Equip new item
this.equippedItems[slot] = itemId;
item.isEquipped = true;
item.lastUsed = new Date();
this.updatedAt = new Date();
return this.save();
};
inventorySchema.methods.unequipItem = function(slot) {
const itemId = this.equippedItems[slot];
if (!itemId) {
throw new Error('No item equipped in this slot');
}
const item = this.items.find(item => item.id === itemId);
if (item) {
item.isEquipped = false;
}
this.equippedItems[slot] = null;
this.updatedAt = new Date();
return this.save();
};
inventorySchema.methods.useConsumable = function(itemId) {
const item = this.items.find(item => item.id === itemId);
if (!item) {
throw new Error('Item not found in inventory');
}
if (!item.consumable) {
throw new Error('Item is not consumable');
}
if (item.quantity <= 0) {
throw new Error('No quantity left');
}
// Apply effects
const effects = { ...item.effect };
// Remove one from quantity
item.quantity -= 1;
item.lastUsed = new Date();
// Remove item if quantity is 0
if (item.quantity === 0) {
const itemIndex = this.items.findIndex(item => item.id === itemId);
this.items.splice(itemIndex, 1);
}
this.updatedAt = new Date();
this.save();
return effects;
};
inventorySchema.methods.getInventorySummary = function() {
const summary = {
totalItems: this.items.length,
usedSlots: this.items.length,
maxSlots: this.maxSlots,
itemsByType: {},
equippedItems: this.equippedItems
};
// Count items by type
this.items.forEach(item => {
summary.itemsByType[item.type] = (summary.itemsByType[item.type] || 0) + item.quantity;
});
return summary;
};
inventorySchema.methods.getItemsByType = function(type) {
return this.items.filter(item => item.type === type);
};
inventorySchema.methods.getItemsByRarity = function(rarity) {
return this.items.filter(item => item.rarity === rarity);
};
module.exports = mongoose.model('Inventory', inventorySchema);

View File

@ -1,164 +0,0 @@
const mongoose = require('mongoose');
const playerSchema = new mongoose.Schema({
userId: {
type: String,
required: true,
unique: true
},
username: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
// Authentication
password: {
type: String,
required: true,
select: false // Don't include password in queries by default
},
// Player stats
stats: {
level: { type: Number, default: 1 },
experience: { type: Number, default: 0 },
credits: { type: Number, default: 1000 },
dungeonsCleared: { type: Number, default: 0 },
playTime: { type: Number, default: 0 },
lastLogin: { type: Date, default: Date.now }
},
// Base attributes
attributes: {
health: { type: Number, default: 100 },
maxHealth: { type: Number, default: 100 },
energy: { type: Number, default: 100 },
maxEnergy: { type: Number, default: 100 },
attack: { type: Number, default: 10 },
defense: { type: Number, default: 5 },
speed: { type: Number, default: 10 },
criticalChance: { type: Number, default: 0.05 },
criticalDamage: { type: Number, default: 1.5 }
},
// Player info
info: {
name: { type: String, default: 'Commander' },
title: { type: String, default: 'Rookie Pilot' },
guild: { type: String, default: null },
rank: { type: String, default: 'Cadet' }
},
// Current ship
currentShip: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Ship'
},
// Settings
settings: {
autoSave: { type: Boolean, default: true },
notifications: { type: Boolean, default: true },
soundEffects: { type: Boolean, default: true },
music: { type: Boolean, default: false },
discordIntegration: { type: Boolean, default: false }
},
// Daily rewards
dailyRewards: {
lastClaim: { type: Date, default: null },
consecutiveDays: { type: Number, default: 0 }
},
// Server info
currentServer: { type: String, default: null },
// Timestamps
createdAt: { type: Date, default: Date.now },
updatedAt: { type: Date, default: Date.now }
}, {
timestamps: true
});
// Indexes for performance (only for non-unique fields)
playerSchema.index({ 'stats.level': 1 });
playerSchema.index({ currentServer: 1 });
// Methods
playerSchema.methods.addExperience = function(amount) {
this.stats.experience += amount;
// Level up logic
const expNeeded = this.stats.level * 100;
if (this.stats.experience >= expNeeded) {
this.stats.experience -= expNeeded;
this.stats.level += 1;
return true; // Leveled up
}
return false;
};
playerSchema.methods.addCredits = function(amount) {
this.stats.credits += amount;
return this.stats.credits;
};
playerSchema.methods.canAfford = function(cost) {
return this.stats.credits >= cost;
};
playerSchema.methods.spendCredits = function(cost) {
if (this.canAfford(cost)) {
this.stats.credits -= cost;
return true;
}
return false;
};
playerSchema.methods.updatePlayTime = function(sessionTime) {
this.stats.playTime += sessionTime;
this.stats.lastLogin = new Date();
};
playerSchema.methods.claimDailyReward = function() {
const today = new Date();
const lastClaim = this.dailyRewards.lastClaim;
// Check if already claimed today
if (lastClaim && lastClaim.toDateString() === today.toDateString()) {
return { success: false, message: 'Daily reward already claimed today' };
}
// Check consecutive days
const yesterday = new Date(today);
yesterday.setDate(yesterday.getDate() - 1);
if (lastClaim && lastClaim.toDateString() === yesterday.toDateString()) {
this.dailyRewards.consecutiveDays += 1;
} else {
this.dailyRewards.consecutiveDays = 1;
}
this.dailyRewards.lastClaim = today;
// Calculate reward based on consecutive days
const baseReward = 100;
const consecutiveBonus = (this.dailyRewards.consecutiveDays - 1) * 50;
const totalReward = baseReward + consecutiveBonus;
this.addCredits(totalReward);
return {
success: true,
reward: totalReward,
consecutiveDays: this.dailyRewards.consecutiveDays
};
};
module.exports = mongoose.model('Player', playerSchema);

View File

@ -1,189 +0,0 @@
const mongoose = require('mongoose');
const shipSchema = new mongoose.Schema({
userId: {
type: String,
required: true,
ref: 'Player'
},
// Ship identification
id: {
type: String,
required: true,
unique: true
},
name: {
type: String,
required: true
},
class: {
type: String,
required: true,
enum: ['Fighter', 'Cruiser', 'Battleship', 'Carrier', 'Explorer']
},
level: {
type: Number,
default: 1
},
// Ship stats
stats: {
health: { type: Number, required: true },
maxHealth: { type: Number, required: true },
attack: { type: Number, required: true },
defense: { type: Number, required: true },
speed: { type: Number, required: true },
criticalChance: { type: Number, default: 0.05 },
criticalDamage: { type: Number, default: 1.5 },
hull: { type: Number, required: true }
},
// Ship appearance
texture: {
type: String,
required: true
},
// Ship progression
experience: {
type: Number,
default: 0
},
requiredExp: {
type: Number,
default: 100
},
upgrades: [{
type: String
}],
// Ship status
isEquipped: {
type: Boolean,
default: false
},
isCurrent: {
type: Boolean,
default: false
},
// Shop information (if purchased)
price: {
type: Number,
default: 0
},
rarity: {
type: String,
enum: ['common', 'uncommon', 'rare', 'epic', 'legendary'],
default: 'common'
},
description: {
type: String,
default: ''
},
// Timestamps
acquiredAt: {
type: Date,
default: Date.now
},
lastUsed: {
type: Date,
default: Date.now
}
}, {
timestamps: true
});
// Indexes for performance
shipSchema.index({ userId: 1 });
shipSchema.index({ id: 1 });
shipSchema.index({ isEquipped: 1 });
shipSchema.index({ isCurrent: 1 });
// Methods
shipSchema.methods.addExperience = function(amount) {
this.experience += amount;
// Level up logic
while (this.experience >= this.requiredExp) {
this.experience -= this.requiredExp;
this.level += 1;
this.requiredExp = this.level * 100;
// Increase stats on level up
this.stats.maxHealth += 10;
this.stats.health = this.stats.maxHealth;
this.stats.attack += 2;
this.stats.defense += 1;
this.stats.speed += 1;
}
return this.level;
};
shipSchema.methods.takeDamage = function(damage) {
const actualDamage = Math.max(0, damage - this.stats.defense);
this.stats.health = Math.max(0, this.stats.health - actualDamage);
if (this.stats.health === 0) {
this.isDestroyed = true;
}
return actualDamage;
};
shipSchema.methods.heal = function(amount) {
const healAmount = Math.min(amount, this.stats.maxHealth - this.stats.health);
this.stats.health += healAmount;
this.isDestroyed = false;
return healAmount;
};
shipSchema.methods.isAlive = function() {
return this.stats.health > 0;
};
shipSchema.methods.getStatSummary = function() {
return {
name: this.name,
class: this.class,
level: this.level,
health: `${this.stats.health}/${this.stats.maxHealth}`,
attack: this.stats.attack,
defense: this.stats.defense,
speed: this.stats.speed,
criticalChance: `${(this.stats.criticalChance * 100).toFixed(1)}%`,
criticalDamage: `${this.stats.criticalDamage}x`
};
};
shipSchema.methods.upgrade = function(upgradeType) {
switch (upgradeType) {
case 'health':
this.stats.maxHealth += 20;
this.stats.health = this.stats.maxHealth;
break;
case 'attack':
this.stats.attack += 5;
break;
case 'defense':
this.stats.defense += 3;
break;
case 'speed':
this.stats.speed += 2;
break;
default:
throw new Error('Unknown upgrade type');
}
if (!this.upgrades.includes(upgradeType)) {
this.upgrades.push(upgradeType);
}
this.lastUsed = new Date();
};
module.exports = mongoose.model('Ship', shipSchema);

6068
Server/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +0,0 @@
{
"name": "galaxystrikeonline-server",
"version": "1.0.0",
"description": "Galaxy Strike Online - Server Backend",
"license": "MIT",
"author": "Korvarix Studios",
"type": "commonjs",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"debug": "node --inspect server.js",
"test": "jest",
"migrate": "node scripts/migrate.js",
"seed": "node scripts/seed.js"
},
"keywords": ["game", "server", "mmorpg", "api", "websocket"],
"dependencies": {
"express": "^4.18.2",
"socket.io": "^4.7.4",
"cors": "^2.8.5",
"helmet": "^7.1.0",
"dotenv": "^16.3.1",
"bcryptjs": "^2.4.3",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.0.3",
"redis": "^4.6.11",
"winston": "^3.11.0",
"joi": "^17.11.0",
"rate-limiter-flexible": "^2.4.2",
"compression": "^1.7.4"
},
"devDependencies": {
"nodemon": "^3.0.2",
"jest": "^29.7.0",
"supertest": "^6.3.3"
},
"engines": {
"node": ">=18.0.0"
}
}

View File

@ -1,214 +0,0 @@
const express = require('express');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const Joi = require('joi');
const { RateLimiterMemory } = require('rate-limiter-flexible');
const Player = require('../models/Player');
const logger = require('../utils/logger');
const router = express.Router();
// Rate limiting for auth routes
const authLimiter = new RateLimiterMemory({
keyGenerator: (req) => req.ip,
points: 5, // Number of requests
duration: 900, // Per 15 minutes (900 seconds)
blockDuration: 900, // Block for 15 minutes
message: 'Too many authentication attempts, please try again later.'
});
// Validation schemas
const registerSchema = Joi.object({
username: Joi.string().min(3).max(30).required(),
email: Joi.string().email().required(),
password: Joi.string().min(6).required()
});
const loginSchema = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().required()
});
// Register route
router.post('/register', async (req, res) => {
try {
// Rate limiting check
const resLimiter = await authLimiter.consume(req.ip);
if (!resLimiter.remainingPoints) {
return res.status(429).json({ error: 'Too many authentication attempts, please try again later.' });
}
const { error } = registerSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
const { username, email, password } = req.body;
// Check if user already exists
const existingUser = await Player.findOne({
$or: [{ email }, { username }]
});
if (existingUser) {
return res.status(400).json({
error: 'User with this email or username already exists'
});
}
// Hash password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(password, salt);
// Create new player
const player = new Player({
userId: `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
username,
email,
password: hashedPassword,
createdAt: new Date(),
lastLogin: new Date()
});
await player.save();
// Create JWT token
const token = jwt.sign(
{ userId: player.userId, email: player.email },
process.env.JWT_SECRET || 'fallback_secret',
{ expiresIn: '24h' }
);
logger.info(`New user registered: ${email}`);
res.status(201).json({
message: 'User registered successfully',
token,
user: {
userId: player.userId,
username: player.username,
email: player.email,
stats: player.stats
}
});
} catch (error) {
logger.error('Registration error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Login route
router.post('/login', async (req, res) => {
try {
// Rate limiting check
const resLimiter = await authLimiter.consume(req.ip);
if (!resLimiter.remainingPoints) {
return res.status(429).json({ error: 'Too many authentication attempts, please try again later.' });
}
const { error } = loginSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
const { email, password } = req.body;
// Find user
const player = await Player.findOne({ email }).select('+password');
if (!player) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Check if password exists (for backward compatibility with existing users)
if (!player.password) {
logger.error('Player password field is missing for user:', email);
return res.status(401).json({
error: 'Account migration required. Please re-register your account.',
requiresMigration: true
});
}
// Check password
const isPasswordValid = await bcrypt.compare(password, player.password);
if (!isPasswordValid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Update last login
player.stats.lastLogin = new Date();
await player.save();
// Create JWT token
const token = jwt.sign(
{ userId: player.userId, email: player.email },
process.env.JWT_SECRET || 'fallback_secret',
{ expiresIn: '24h' }
);
logger.info(`User logged in: ${email}`);
res.json({
message: 'Login successful',
token,
user: {
userId: player.userId,
username: player.username,
email: player.email,
stats: player.stats,
info: player.info
}
});
} catch (error) {
logger.error('Login error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Verify token route
router.get('/verify', async (req, res) => {
try {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback_secret');
const player = await Player.findOne({ userId: decoded.userId });
if (!player) {
return res.status(401).json({ error: 'Invalid token' });
}
res.json({
valid: true,
user: {
userId: player.userId,
username: player.username,
email: player.email,
stats: player.stats,
info: player.info
}
});
} catch (error) {
logger.error('Token verification error:', error);
res.status(401).json({ error: 'Invalid token' });
}
});
// Logout route
router.post('/logout', async (req, res) => {
try {
// In a real implementation, you might want to blacklist the token
// For now, we'll just return success
res.json({ message: 'Logout successful' });
} catch (error) {
logger.error('Logout error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
module.exports = router;

View File

@ -1,336 +0,0 @@
const express = require('express');
const jwt = require('jsonwebtoken');
const Player = require('../models/Player');
const Ship = require('../models/Ship');
const Inventory = require('../models/Inventory');
const { getGameSystem } = require('../systems/GameSystem');
const logger = require('../utils/logger');
const router = express.Router();
// Middleware to authenticate JWT token
const authenticateToken = (req, res, next) => {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback_secret');
req.userId = decoded.userId;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};
// Get player data
router.get('/player', authenticateToken, async (req, res) => {
try {
const gameSystem = getGameSystem();
const player = await gameSystem.loadPlayer(req.userId);
if (!player) {
return res.status(404).json({ error: 'Player not found' });
}
res.json({
userId: player.userId,
username: player.username,
stats: player.stats,
attributes: player.attributes,
info: player.info,
settings: player.settings,
dailyRewards: player.dailyRewards
});
} catch (error) {
logger.error('Error getting player data:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Get player's ships
router.get('/ships', authenticateToken, async (req, res) => {
try {
const ships = await Ship.find({ userId: req.userId });
res.json({
ships: ships.map(ship => ({
id: ship.id,
name: ship.name,
class: ship.class,
level: ship.level,
stats: ship.stats,
isEquipped: ship.isEquipped,
isCurrent: ship.isCurrent,
rarity: ship.rarity,
texture: ship.texture,
acquiredAt: ship.acquiredAt
}))
});
} catch (error) {
logger.error('Error getting player ships:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Equip a ship
router.post('/ships/equip', authenticateToken, async (req, res) => {
try {
const { shipId } = req.body;
if (!shipId) {
return res.status(400).json({ error: 'Ship ID required' });
}
const gameSystem = getGameSystem();
const ship = await gameSystem.equipShip(req.userId, shipId);
res.json({
message: 'Ship equipped successfully',
ship: {
id: ship.id,
name: ship.name,
class: ship.class,
level: ship.level,
stats: ship.stats,
isEquipped: ship.isEquipped,
isCurrent: ship.isCurrent
}
});
} catch (error) {
logger.error('Error equipping ship:', error);
res.status(500).json({ error: error.message });
}
});
// Get player's inventory
router.get('/inventory', authenticateToken, async (req, res) => {
try {
let inventory = await Inventory.findOne({ userId: req.userId });
if (!inventory) {
// Create new inventory if it doesn't exist
inventory = new Inventory({ userId: req.userId });
await inventory.save();
}
res.json({
items: inventory.items,
equippedItems: inventory.equippedItems,
summary: inventory.getInventorySummary()
});
} catch (error) {
logger.error('Error getting inventory:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Add item to inventory
router.post('/inventory/add', authenticateToken, async (req, res) => {
try {
const { itemData } = req.body;
if (!itemData) {
return res.status(400).json({ error: 'Item data required' });
}
let inventory = await Inventory.findOne({ userId: req.userId });
if (!inventory) {
inventory = new Inventory({ userId: req.userId });
}
await inventory.addItem(itemData);
res.json({
message: 'Item added to inventory',
item: itemData,
summary: inventory.getInventorySummary()
});
} catch (error) {
logger.error('Error adding item to inventory:', error);
res.status(500).json({ error: error.message });
}
});
// Equip item
router.post('/inventory/equip', authenticateToken, async (req, res) => {
try {
const { itemId, slot } = req.body;
if (!itemId || !slot) {
return res.status(400).json({ error: 'Item ID and slot required' });
}
const inventory = await Inventory.findOne({ userId: req.userId });
if (!inventory) {
return res.status(404).json({ error: 'Inventory not found' });
}
await inventory.equipItem(itemId, slot);
res.json({
message: 'Item equipped successfully',
equippedItems: inventory.equippedItems
});
} catch (error) {
logger.error('Error equipping item:', error);
res.status(500).json({ error: error.message });
}
});
// Use consumable
router.post('/inventory/use', authenticateToken, async (req, res) => {
try {
const { itemId } = req.body;
if (!itemId) {
return res.status(400).json({ error: 'Item ID required' });
}
const inventory = await Inventory.findOne({ userId: req.userId });
if (!inventory) {
return res.status(404).json({ error: 'Inventory not found' });
}
const effects = await inventory.useConsumable(itemId);
res.json({
message: 'Item used successfully',
effects,
summary: inventory.getInventorySummary()
});
} catch (error) {
logger.error('Error using consumable:', error);
res.status(500).json({ error: error.message });
}
});
// Process game action
router.post('/action', authenticateToken, async (req, res) => {
try {
const { actionData } = req.body;
if (!actionData) {
return res.status(400).json({ error: 'Action data required' });
}
const gameSystem = getGameSystem();
const result = await gameSystem.processGameAction(req.userId, actionData);
res.json(result);
} catch (error) {
logger.error('Error processing game action:', error);
res.status(500).json({ error: error.message });
}
});
// Claim daily reward
router.post('/daily-reward', authenticateToken, async (req, res) => {
try {
const player = await Player.findOne({ userId: req.userId });
if (!player) {
return res.status(404).json({ error: 'Player not found' });
}
const rewardResult = player.claimDailyReward();
await player.save();
res.json(rewardResult);
} catch (error) {
logger.error('Error claiming daily reward:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Save player data
router.post('/save', authenticateToken, async (req, res) => {
try {
const gameSystem = getGameSystem();
await gameSystem.savePlayer(req.userId);
res.json({ message: 'Game saved successfully' });
} catch (error) {
logger.error('Error saving game:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Get shop items
router.get('/shop/:category?', authenticateToken, async (req, res) => {
try {
const gameSystem = getGameSystem();
const category = req.params.category;
const items = gameSystem.economy.getShopItems(category);
res.json({ items });
} catch (error) {
logger.error('Error getting shop items:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Purchase item
router.post('/shop/purchase', authenticateToken, async (req, res) => {
try {
const { itemId, quantity = 1 } = req.body;
if (!itemId) {
return res.status(400).json({ error: 'Item ID required' });
}
const gameSystem = getGameSystem();
const purchaseInfo = gameSystem.economy.purchaseItem(req.userId, itemId, quantity);
const player = await Player.findOne({ userId: req.userId });
if (!player.canAfford(purchaseInfo.totalCost)) {
return res.status(400).json({ error: 'Insufficient credits' });
}
// Deduct credits
player.spendCredits(purchaseInfo.totalCost);
await player.save();
// Add item to inventory
const inventory = await Inventory.findOne({ userId: req.userId });
if (!inventory) {
return res.status(404).json({ error: 'Inventory not found' });
}
await inventory.addItem({
...purchaseInfo.item,
quantity
});
res.json({
message: 'Purchase successful',
item: purchaseInfo.item,
quantity,
totalCost: purchaseInfo.totalCost,
remainingCredits: player.stats.credits
});
} catch (error) {
logger.error('Error purchasing item:', error);
res.status(500).json({ error: error.message });
}
});
module.exports = router;

View File

@ -1,341 +0,0 @@
const express = require('express');
const jwt = require('jsonwebtoken');
const { getGameSystem } = require('../systems/GameSystem');
const logger = require('../utils/logger');
const router = express.Router();
// Middleware to authenticate JWT token
const authenticateToken = (req, res, next) => {
const token = req.header('Authorization')?.replace('Bearer ', '');
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback_secret');
req.userId = decoded.userId;
next();
} catch (error) {
res.status(401).json({ error: 'Invalid token' });
}
};
// Get server list
router.get('/', authenticateToken, async (req, res) => {
try {
const gameSystem = getGameSystem();
const serverList = gameSystem.getServerList();
res.json({
servers: serverList,
totalServers: serverList.length
});
} catch (error) {
logger.error('Error getting server list:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Create new server
router.post('/create', authenticateToken, async (req, res) => {
try {
const { name, type = 'public', maxPlayers = 10, region = 'us-east', settings = {} } = req.body;
if (!name) {
return res.status(400).json({ error: 'Server name required' });
}
if (name.length < 3 || name.length > 50) {
return res.status(400).json({ error: 'Server name must be between 3 and 50 characters' });
}
const gameSystem = getGameSystem();
const serverData = {
name,
type,
maxPlayers,
region,
settings,
ownerId: req.userId,
ownerName: req.username || 'Unknown'
};
const server = await gameSystem.createServer(serverData);
// Auto-join the creator to the server
await gameSystem.joinServer(server.id, req.userId);
logger.info(`Server created: ${server.id} by user ${req.userId}`);
res.status(201).json({
message: 'Server created successfully',
server: {
id: server.id,
name: server.name,
type: server.type,
maxPlayers: server.maxPlayers,
currentPlayers: server.players.length,
region: server.region,
status: server.status,
ownerId: server.ownerId,
createdAt: server.createdAt
}
});
} catch (error) {
logger.error('Error creating server:', error);
res.status(500).json({ error: error.message });
}
});
// Join server
router.post('/:serverId/join', authenticateToken, async (req, res) => {
try {
const { serverId } = req.params;
const { password } = req.body; // For private servers
if (!serverId) {
return res.status(400).json({ error: 'Server ID required' });
}
const gameSystem = getGameSystem();
const server = await gameSystem.joinServer(serverId, req.userId);
// Update player's current server
const Player = require('../models/Player');
await Player.findOneAndUpdate(
{ userId: req.userId },
{ currentServer: serverId }
);
logger.info(`User ${req.userId} joined server ${serverId}`);
res.json({
message: 'Joined server successfully',
server: {
id: server.id,
name: server.name,
type: server.type,
maxPlayers: server.maxPlayers,
currentPlayers: server.players.length,
region: server.region
}
});
} catch (error) {
logger.error('Error joining server:', error);
res.status(500).json({ error: error.message });
}
});
// Leave server
router.post('/:serverId/leave', authenticateToken, async (req, res) => {
try {
const { serverId } = req.params;
if (!serverId) {
return res.status(400).json({ error: 'Server ID required' });
}
const gameSystem = getGameSystem();
const server = await gameSystem.leaveServer(serverId, req.userId);
// Update player's current server
const Player = require('../models/Player');
await Player.findOneAndUpdate(
{ userId: req.userId },
{ currentServer: null }
);
logger.info(`User ${req.userId} left server ${serverId}`);
res.json({
message: 'Left server successfully',
server: server || null
});
} catch (error) {
logger.error('Error leaving server:', error);
res.status(500).json({ error: error.message });
}
});
// Get server details
router.get('/:serverId', authenticateToken, async (req, res) => {
try {
const { serverId } = req.params;
if (!serverId) {
return res.status(400).json({ error: 'Server ID required' });
}
const gameSystem = getGameSystem();
const server = gameSystem.servers.get(serverId);
if (!server) {
return res.status(404).json({ error: 'Server not found' });
}
// Get player details for all players in the server
const Player = require('../models/Player');
const players = await Player.find({
userId: { $in: server.players }
}).select('userId username info stats');
res.json({
server: {
id: server.id,
name: server.name,
type: server.type,
maxPlayers: server.maxPlayers,
currentPlayers: server.players.length,
region: server.region,
status: server.status,
ownerId: server.ownerId,
createdAt: server.createdAt,
settings: server.settings
},
players: players.map(player => ({
userId: player.userId,
username: player.username,
level: player.stats.level,
title: player.info.title,
rank: player.info.rank
}))
});
} catch (error) {
logger.error('Error getting server details:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Get user's current server
router.get('/current', authenticateToken, async (req, res) => {
try {
const Player = require('../models/Player');
const player = await Player.findOne({ userId: req.userId });
if (!player || !player.currentServer) {
return res.json({ currentServer: null });
}
const gameSystem = getGameSystem();
const server = gameSystem.servers.get(player.currentServer);
if (!server) {
// Clear the invalid server reference
player.currentServer = null;
await player.save();
return res.json({ currentServer: null });
}
res.json({
currentServer: {
id: server.id,
name: server.name,
type: server.type,
maxPlayers: server.maxPlayers,
currentPlayers: server.players.length,
region: server.region,
status: server.status
}
});
} catch (error) {
logger.error('Error getting current server:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Update server settings (owner only)
router.put('/:serverId/settings', authenticateToken, async (req, res) => {
try {
const { serverId } = req.params;
const { settings } = req.body;
if (!serverId) {
return res.status(400).json({ error: 'Server ID required' });
}
const gameSystem = getGameSystem();
const server = gameSystem.servers.get(serverId);
if (!server) {
return res.status(404).json({ error: 'Server not found' });
}
// Check if user is the server owner
if (server.ownerId !== req.userId) {
return res.status(403).json({ error: 'Only server owner can update settings' });
}
// Update server settings
server.settings = { ...server.settings, ...settings };
logger.info(`Server settings updated: ${serverId} by user ${req.userId}`);
res.json({
message: 'Server settings updated successfully',
settings: server.settings
});
} catch (error) {
logger.error('Error updating server settings:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// Delete server (owner only)
router.delete('/:serverId', authenticateToken, async (req, res) => {
try {
const { serverId } = req.params;
if (!serverId) {
return res.status(400).json({ error: 'Server ID required' });
}
const gameSystem = getGameSystem();
const server = gameSystem.servers.get(serverId);
if (!server) {
return res.status(404).json({ error: 'Server not found' });
}
// Check if user is the server owner
if (server.ownerId !== req.userId) {
return res.status(403).json({ error: 'Only server owner can delete server' });
}
// Remove all players from the server
for (const playerId of server.players) {
await gameSystem.leaveServer(serverId, playerId);
// Update player's current server
const Player = require('../models/Player');
await Player.findOneAndUpdate(
{ userId: playerId },
{ currentServer: null }
);
}
// Delete the server
gameSystem.servers.delete(serverId);
logger.info(`Server deleted: ${serverId} by user ${req.userId}`);
res.json({
message: 'Server deleted successfully'
});
} catch (error) {
logger.error('Error deleting server:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
module.exports = router;

View File

@ -1,50 +0,0 @@
const mongoose = require('mongoose');
const logger = require('../utils/logger');
require('dotenv').config();
async function migrate() {
try {
logger.info('Starting database migration...');
// Connect to database
await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/galaxystrikeonline');
logger.info('Connected to database');
// Create indexes for performance
const db = mongoose.connection.db;
// Player indexes
await db.collection('players').createIndex({ userId: 1 }, { unique: true });
await db.collection('players').createIndex({ email: 1 }, { unique: true });
await db.collection('players').createIndex({ 'stats.level': 1 });
await db.collection('players').createIndex({ currentServer: 1 });
// Ship indexes
await db.collection('ships').createIndex({ userId: 1 });
await db.collection('ships').createIndex({ id: 1 }, { unique: true });
await db.collection('ships').createIndex({ isEquipped: 1 });
await db.collection('ships').createIndex({ isCurrent: 1 });
// Inventory indexes
await db.collection('inventories').createIndex({ userId: 1 }, { unique: true });
await db.collection('inventories').createIndex({ 'items.id': 1 });
await db.collection('inventories').createIndex({ 'items.type': 1 });
logger.info('Database migration completed successfully');
// Close connection
await mongoose.connection.close();
logger.info('Database connection closed');
} catch (error) {
logger.error('Migration failed:', error);
process.exit(1);
}
}
if (require.main === module) {
migrate();
}
module.exports = migrate;

View File

@ -1,71 +0,0 @@
/**
* Password Migration Script
* Updates existing users to have password fields
*/
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const Player = require('../models/Player');
const logger = require('../utils/logger');
require('dotenv').config();
async function migratePasswords() {
try {
// Connect to database
await mongoose.connect(process.env.MONGODB_URI);
logger.info('Connected to database for password migration');
// Find all users without passwords
const usersWithoutPasswords = await Player.find({
password: { $exists: false }
});
logger.info(`Found ${usersWithoutPasswords.length} users without passwords`);
if (usersWithoutPasswords.length === 0) {
logger.info('No users need password migration');
return;
}
// Update each user with a default password
for (const user of usersWithoutPasswords) {
// Generate a default password (you might want to use a different approach)
const defaultPassword = 'tempPassword123!';
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(defaultPassword, salt);
await Player.updateOne(
{ _id: user._id },
{
$set: {
password: hashedPassword,
'stats.lastLogin': new Date()
}
}
);
logger.info(`Migrated user: ${user.email} with default password`);
}
logger.info('Password migration completed successfully');
// Output the default password for users to change
console.log('\n=== MIGRATION COMPLETE ===');
console.log(`Updated ${usersWithoutPasswords.length} users`);
console.log('Default password for all migrated users: tempPassword123!');
console.log('Users should change their password after first login\n');
} catch (error) {
logger.error('Password migration error:', error);
console.error('Migration failed:', error);
} finally {
await mongoose.disconnect();
}
}
// Run the migration
if (require.main === module) {
migratePasswords();
}
module.exports = migratePasswords;

View File

@ -1,196 +0,0 @@
const mongoose = require('mongoose');
const logger = require('../utils/logger');
const Player = require('../models/Player');
const Ship = require('../models/Ship');
const Inventory = require('../models/Inventory');
require('dotenv').config();
async function seed() {
try {
logger.info('Starting database seeding...');
// Connect to database
await mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/galaxystrikeonline');
logger.info('Connected to database');
// Clear existing data (optional - remove if you want to preserve data)
logger.info('Clearing existing data...');
await Player.deleteMany({});
await Ship.deleteMany({});
await Inventory.deleteMany({});
// Create a test user
const testUser = new Player({
userId: 'test_user_001',
username: 'TestPlayer',
email: 'test@example.com',
password: '$2a$10$example_hashed_password_here',
stats: {
level: 1,
experience: 0,
credits: 5000,
dungeonsCleared: 0,
playTime: 0,
lastLogin: new Date()
},
attributes: {
health: 100,
maxHealth: 100,
energy: 100,
maxEnergy: 100,
attack: 10,
defense: 5,
speed: 10,
criticalChance: 0.05,
criticalDamage: 1.5
},
info: {
name: 'Commander',
title: 'Rookie Pilot',
guild: null,
rank: 'Cadet'
},
settings: {
autoSave: true,
notifications: true,
soundEffects: true,
music: false,
discordIntegration: false
},
dailyRewards: {
lastClaim: null,
consecutiveDays: 0
}
});
await testUser.save();
logger.info('Created test user');
// Create starter ship for test user
const starterShip = new Ship({
userId: testUser.userId,
id: 'starter_cruiser_001',
name: 'Starter Cruiser',
class: 'Cruiser',
level: 1,
stats: {
health: 100,
maxHealth: 100,
attack: 15,
defense: 12,
speed: 10,
criticalChance: 0.05,
criticalDamage: 1.5,
hull: 100
},
texture: 'assets/textures/ships/starter_cruiser.png',
experience: 0,
requiredExp: 100,
upgrades: [],
isEquipped: true,
isCurrent: true,
price: 5000,
rarity: 'common',
description: 'Reliable starter cruiser for new pilots',
acquiredAt: new Date(),
lastUsed: new Date()
});
await starterShip.save();
logger.info('Created starter ship');
// Update player with current ship
testUser.currentShip = starterShip._id;
await testUser.save();
// Create inventory for test user
const inventory = new Inventory({
userId: testUser.userId,
maxSlots: 50,
items: [
{
id: 'starter_blaster_common',
name: 'Common Blaster',
type: 'weapon',
rarity: 'common',
quantity: 1,
stats: {
attack: 5,
criticalChance: 0.02,
damage: 10,
fireRate: 2,
range: 5,
energy: 5
},
description: 'A reliable basic blaster for new pilots',
equipable: true,
slot: 'weapon',
isEquipped: false,
stackable: false,
acquiredAt: new Date()
},
{
id: 'basic_armor_common',
name: 'Basic Armor',
type: 'armor',
rarity: 'common',
quantity: 1,
stats: {
defense: 3,
durability: 20,
weight: 2,
energyShield: 0
},
description: 'Light armor providing basic protection',
equipable: true,
slot: 'armor',
isEquipped: false,
stackable: false,
acquiredAt: new Date()
},
{
id: 'health_kit',
name: 'Health Kit',
type: 'consumable',
rarity: 'common',
quantity: 3,
stats: {},
description: 'A medical kit that restores health',
consumable: true,
effect: {
health: 50
},
stackable: true,
acquiredAt: new Date()
}
],
equippedItems: {
weapon: null,
armor: null,
engine: null,
shield: null,
special: null
}
});
await inventory.save();
logger.info('Created inventory with starter items');
logger.info('Database seeding completed successfully');
// Close connection
await mongoose.connection.close();
logger.info('Database connection closed');
} catch (error) {
logger.error('Seeding failed:', error);
process.exit(1);
}
}
if (require.main === module) {
seed();
}
module.exports = seed;

View File

@ -1,195 +0,0 @@
const express = require('express');
const http = require('http');
const socketIo = require('socket.io');
const cors = require('cors');
const helmet = require('helmet');
const compression = require('compression');
const rateLimit = require('rate-limiter-flexible');
require('dotenv').config();
const logger = require('./utils/logger');
const connectDB = require('./config/database');
const authRoutes = require('./routes/auth');
const gameRoutes = require('./routes/game');
const serverRoutes = require('./routes/servers');
const { initializeGameSystems } = require('./systems/GameSystem');
const SocketHandlers = require('./socket/socketHandlers');
const { errorHandler, notFound } = require('./middleware/errorHandler');
// Override console.error to properly log error objects
const originalConsoleError = console.error;
console.error = (...args) => {
args.forEach(arg => {
if (arg instanceof Error) {
logger.error('Console Error:', {
message: arg.message,
stack: arg.stack,
name: arg.name
});
} else if (typeof arg === 'object' && arg !== null) {
logger.error('Console Error Object:', arg);
} else {
logger.error('Console Error:', arg);
}
});
};
const app = express();
const server = http.createServer(app);
const io = socketIo(server, {
cors: {
origin: ["https://galaxystrike.online", "https://api.korvarix.com", "http://api.korvarix.com:3001"],
methods: ["GET", "POST"]
}
});
// Middleware
app.use(helmet());
app.use(compression());
app.use(cors({
origin: ["https://galaxystrike.online", "https://api.korvarix.com", "http://api.korvarix.com:3001"],
credentials: true
}));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true }));
// Static file serving
app.use(express.static('../Website/dist'));
// Rate limiting
const { RateLimiterMemory } = require('rate-limiter-flexible');
const limiter = new RateLimiterMemory({
keyGenerator: (req) => req.ip,
points: 100, // limit each IP to 100 requests per windowMs
duration: 900, // 15 minutes
blockDuration: 900, // Block for 15 minutes
});
app.use('/api/', async (req, res, next) => {
try {
const resLimiter = await limiter.consume(req.ip);
if (!resLimiter.remainingPoints) {
return res.status(429).json({ error: 'Too many requests, please try again later.' });
}
next();
} catch (error) {
next();
}
});
// Routes
app.use('/api/auth', authRoutes);
app.use('/api/game', gameRoutes);
app.use('/api/servers', serverRoutes);
// Health check
app.get('/health', (req, res) => {
res.status(200).json({
status: 'OK',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
});
// API version endpoint
app.get('/api/ssc/version', (req, res) => {
res.status(200).json({
version: '1.0.0',
service: 'galaxystrikeonline-server',
timestamp: new Date().toISOString()
});
});
// Fallback route for SPA - only serve index.html for non-API routes
app.get('*', (req, res) => {
// Don't try to serve index.html for API routes
if (req.path.startsWith('/api/')) {
return res.status(404).json({ error: 'API endpoint not found' });
}
// Try dist first (for built files), fallback to public (for development)
const distPath = require('path').resolve(__dirname, '../dist/index.html');
const publicPath = require('path').resolve(__dirname, '../public/index.html');
const fs = require('fs');
if (fs.existsSync(distPath)) {
res.sendFile(distPath);
} else if (fs.existsSync(publicPath)) {
res.sendFile(publicPath);
} else {
res.status(404).json({ error: 'Frontend not found' });
}
});
// Socket.IO connection handling
const socketHandlers = new SocketHandlers(io);
io.on('connection', (socket) => {
socketHandlers.handleConnection(socket);
});
// Error handling
app.use(notFound);
app.use(errorHandler);
// Initialize database and game systems
async function startServer() {
try {
// Connect to database
await connectDB();
logger.info('Database connected successfully');
// Initialize game systems
await initializeGameSystems();
logger.info('Game systems initialized');
// Check for duplicate accounts in database
logger.info('Checking for duplicate accounts in database...');
const socketHandlers = new SocketHandlers(io);
const duplicateCheck = await socketHandlers.checkForDuplicateAccounts();
if (duplicateCheck.error) {
logger.error('Error checking duplicate accounts:', duplicateCheck.error);
} else {
logger.info('Duplicate account check completed successfully');
}
// Start server
const PORT = process.env.PORT || 3003; // Combined Server on port 3003
server.listen(PORT, () => {
logger.info(`Combined Server running on port ${PORT}`);
logger.info('Combined Server handles: Authentication, Server Browser, Multiplayer, User Management');
// Log connection statistics every 5 minutes
setInterval(() => {
const stats = socketHandlers.getConnectionStats();
logger.info('Connection stats:', stats);
}, 300000);
});
} catch (error) {
logger.error('Failed to start server:', error);
process.exit(1);
}
}
// Handle uncaught errors
process.on('uncaughtException', (error) => {
logger.error('Uncaught Exception:', error);
});
process.on('unhandledRejection', (reason, promise) => {
logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
});
// Handle HTTP server errors
server.on('error', (error) => {
logger.error('HTTP Server error:', error);
});
// Handle Socket.IO errors
io.on('error', (error) => {
logger.error('Socket.IO error:', error);
});
startServer();
module.exports = { app, server, io };

View File

@ -1,406 +0,0 @@
const logger = require('../utils/logger');
const { getGameSystem } = require('../systems/GameSystem');
const Player = require('../models/Player');
class SocketHandlers {
constructor(io) {
this.io = io;
this.connectedUsers = new Map(); // userId -> socket.id
this.userSockets = new Map(); // socket.id -> userId
// Add connection cleanup interval
this.startConnectionCleanup();
}
startConnectionCleanup() {
// Clean up stale connections every 30 seconds
setInterval(() => {
this.cleanupStaleConnections();
}, 30000);
}
cleanupStaleConnections() {
logger.info(`[SOCKET HANDLERS] Checking ${this.connectedUsers.size} active connections...`);
for (const [userId, socketId] of this.connectedUsers.entries()) {
const socket = this.io.sockets.sockets.get(socketId);
if (!socket || !socket.connected) {
logger.warn(`[SOCKET HANDLERS] Cleaning up stale connection for user ${userId} (socket: ${socketId})`);
this.connectedUsers.delete(userId);
this.userSockets.delete(socketId);
}
}
}
handleConnection(socket) {
logger.info(`Client connected: ${socket.id}`);
// Authentication
socket.on('authenticate', async (token) => {
try {
const jwt = require('jsonwebtoken');
const decoded = jwt.verify(token, process.env.JWT_SECRET || 'fallback_secret');
const player = await Player.findOne({ userId: decoded.userId });
if (!player) {
socket.emit('auth_error', { error: 'Player not found' });
return;
}
// Check if user is already connected from another client
const existingSocketId = this.connectedUsers.get(decoded.userId);
if (existingSocketId && existingSocketId !== socket.id) {
logger.warn(`[SOCKET HANDLERS] User ${decoded.userId} attempting to connect from multiple clients. Disconnecting previous client.`);
logger.warn(`[SOCKET HANDLERS] Existing socket: ${existingSocketId}, New socket: ${socket.id}`);
// Disconnect the previous client
const previousSocket = this.io.sockets.sockets.get(existingSocketId);
if (previousSocket) {
logger.info(`[SOCKET HANDLERS] Force disconnecting previous socket: ${existingSocketId}`);
previousSocket.emit('force_disconnect', {
reason: 'Another client connected with your account',
newSocketId: socket.id
});
previousSocket.disconnect(true);
} else {
logger.warn(`[SOCKET HANDLERS] Previous socket ${existingSocketId} not found in active connections`);
// Clean up the stale mapping
this.connectedUsers.delete(decoded.userId);
this.userSockets.delete(existingSocketId);
}
} else {
logger.info(`[SOCKET HANDLERS] New connection for user ${decoded.userId} (socket: ${socket.id})`);
}
// Store user connection
this.connectedUsers.set(decoded.userId, socket.id);
this.userSockets.set(socket.id, decoded.userId);
socket.userId = decoded.userId;
socket.emit('authenticated', { userId: decoded.userId });
logger.info(`User authenticated: ${decoded.userId} (socket: ${socket.id})`);
// Join user to their current server if any
if (player.currentServer) {
socket.join(player.currentServer);
this.broadcastToServer(player.currentServer, 'user_joined', {
userId: decoded.userId,
username: player.username,
socketId: socket.id
});
}
} catch (error) {
logger.error('Authentication error:', error);
socket.emit('auth_error', { error: 'Invalid token' });
}
});
// Server management
socket.on('join_server', async (data) => {
try {
if (!socket.userId) {
socket.emit('error', { error: 'Not authenticated' });
return;
}
const gameSystem = getGameSystem();
const server = await gameSystem.joinServer(data.serverId, socket.userId);
// Update player's current server
await Player.findOneAndUpdate(
{ userId: socket.userId },
{ currentServer: data.serverId }
);
// Join socket room
socket.join(data.serverId);
socket.emit('server_joined', { server });
this.broadcastToServer(data.serverId, 'user_joined', {
userId: socket.userId,
serverId: data.serverId
});
logger.info(`User ${socket.userId} joined server ${data.serverId}`);
} catch (error) {
logger.error('Error joining server:', error);
socket.emit('error', { error: error.message });
}
});
socket.on('leave_server', async (data) => {
try {
if (!socket.userId) {
socket.emit('error', { error: 'Not authenticated' });
return;
}
const gameSystem = getGameSystem();
const server = await gameSystem.leaveServer(data.serverId, socket.userId);
// Update player's current server
await Player.findOneAndUpdate(
{ userId: socket.userId },
{ currentServer: null }
);
// Leave socket room
socket.leave(data.serverId);
socket.emit('server_left', { server });
this.broadcastToServer(data.serverId, 'user_left', {
userId: socket.userId,
serverId: data.serverId
});
logger.info(`User ${socket.userId} left server ${data.serverId}`);
} catch (error) {
logger.error('Error leaving server:', error);
socket.emit('error', { error: error.message });
}
});
// Game actions
socket.on('game_action', async (data) => {
try {
if (!socket.userId) {
socket.emit('error', { error: 'Not authenticated' });
return;
}
const gameSystem = getGameSystem();
const result = await gameSystem.processGameAction(socket.userId, data);
socket.emit('action_result', { action: data.type, result });
// Broadcast relevant actions to server
if (data.broadcast && socket.userId) {
const player = await Player.findOne({ userId: socket.userId });
if (player && player.currentServer) {
this.broadcastToServer(player.currentServer, 'user_action', {
userId: socket.userId,
username: player.username,
action: data.type,
result
});
}
}
} catch (error) {
logger.error('Error processing game action:', error);
socket.emit('error', { error: error.message });
}
});
// Chat functionality
socket.on('send_message', async (data) => {
try {
if (!socket.userId) {
socket.emit('error', { error: 'Not authenticated' });
return;
}
const player = await Player.findOne({ userId: socket.userId });
if (!player || !player.currentServer) {
socket.emit('error', { error: 'Not in a server' });
return;
}
const message = {
userId: socket.userId,
username: player.username,
message: data.message,
timestamp: new Date(),
type: data.type || 'chat'
};
// Broadcast to server
this.broadcastToServer(player.currentServer, 'new_message', message);
logger.info(`Chat message from ${socket.userId} in server ${player.currentServer}`);
} catch (error) {
logger.error('Error sending message:', error);
socket.emit('error', { error: error.message });
}
});
// Real-time updates
socket.on('request_server_status', async () => {
try {
if (!socket.userId) {
socket.emit('error', { error: 'Not authenticated' });
return;
}
const player = await Player.findOne({ userId: socket.userId });
if (!player || !player.currentServer) {
socket.emit('server_status', { server: null });
return;
}
const gameSystem = getGameSystem();
const server = gameSystem.servers.get(player.currentServer);
if (server) {
const players = await Player.find({
userId: { $in: server.players }
}).select('userId username info.stats.level');
socket.emit('server_status', {
server: {
id: server.id,
name: server.name,
currentPlayers: server.players.length,
maxPlayers: server.maxPlayers,
players: players.map(p => ({
userId: p.userId,
username: p.username,
level: p.info.stats.level
}))
}
});
}
} catch (error) {
logger.error('Error getting server status:', error);
socket.emit('error', { error: error.message });
}
});
// Disconnection
socket.on('disconnect', async () => {
logger.info(`Client disconnected: ${socket.id}`);
const userId = this.userSockets.get(socket.id);
if (userId) {
logger.info(`User ${userId} disconnected (socket: ${socket.id})`);
// Remove from tracking maps
this.connectedUsers.delete(userId);
this.userSockets.delete(socket.id);
// Update player's online status
try {
const player = await Player.findOne({ userId });
if (player) {
// Notify server if user was in one
if (player.currentServer) {
this.broadcastToServer(player.currentServer, 'user_disconnected', {
userId,
username: player.username,
socketId: socket.id
});
// Leave the server room
socket.leave(player.currentServer);
}
logger.info(`User ${userId} fully disconnected and cleaned up`);
}
} catch (error) {
logger.error(`Error cleaning up user ${userId} on disconnect:`, error);
}
} else {
logger.warn(`Unknown socket ${socket.id} disconnected without user mapping`);
}
});
}
broadcastToServer(serverId, event, data) {
this.io.to(serverId).emit(event, data);
}
sendToUser(userId, event, data) {
const socketId = this.connectedUsers.get(userId);
if (socketId) {
this.io.to(socketId).emit(event, data);
}
}
// Method to check for duplicate accounts
async checkForDuplicateAccounts() {
try {
const Player = require('../models/Player');
const allPlayers = await Player.find({}, 'userId email username');
const duplicateEmails = [];
const duplicateUsernames = [];
const emailMap = new Map();
const usernameMap = new Map();
allPlayers.forEach(player => {
if (emailMap.has(player.email)) {
duplicateEmails.push({
email: player.email,
user1: emailMap.get(player.email),
user2: player.userId
});
} else {
emailMap.set(player.email, player.userId);
}
if (usernameMap.has(player.username)) {
duplicateUsernames.push({
username: player.username,
user1: usernameMap.get(player.username),
user2: player.userId
});
} else {
usernameMap.set(player.username, player.userId);
}
});
if (duplicateEmails.length > 0) {
logger.error(`[SOCKET HANDLERS] Found ${duplicateEmails.length} duplicate emails in database:`, duplicateEmails);
}
if (duplicateUsernames.length > 0) {
logger.error(`[SOCKET HANDLERS] Found ${duplicateUsernames.length} duplicate usernames in database:`, duplicateUsernames);
}
logger.info(`[SOCKET HANDLERS] Account check complete: ${allPlayers.length} total players, ${duplicateEmails.length} duplicate emails, ${duplicateUsernames.length} duplicate usernames`);
return {
totalPlayers: allPlayers.length,
duplicateEmails,
duplicateUsernames
};
} catch (error) {
logger.error('[SOCKET HANDLERS] Error checking for duplicate accounts:', error);
return { error: error.message };
}
}
// Method to get connection statistics
getConnectionStats() {
return {
connectedUsers: this.connectedUsers.size,
userSockets: this.userSockets.size,
activeSockets: this.io.engine.clientsCount,
connections: Array.from(this.connectedUsers.entries()).map(([userId, socketId]) => ({
userId,
socketId,
isActive: !!this.io.sockets.sockets.get(socketId)
}))
};
}
broadcastToAll(event, data) {
this.io.emit(event, data);
}
getConnectedUsers() {
return Array.from(this.connectedUsers.keys());
}
getUserCount() {
return this.connectedUsers.size;
}
}
module.exports = SocketHandlers;

View File

@ -1,385 +0,0 @@
const logger = require('../utils/logger');
class EconomySystem {
constructor() {
this.shopItems = {
ships: [],
weapons: [],
armors: [],
materials: [],
consumables: []
};
this.dailyRewards = {
baseReward: 100,
consecutiveBonus: 50,
maxConsecutiveDays: 30
};
}
async initialize() {
logger.info('Initializing Economy System...');
// Initialize shop items
await this.initializeShopItems();
logger.info('Economy System initialized successfully');
}
async initializeShopItems() {
// Ships
this.shopItems.ships = [
// Starter Cruiser Variants
{
id: 'starter_cruiser_common',
name: 'Starter Cruiser',
type: 'ship',
rarity: 'common',
price: 5000,
currency: 'credits',
description: 'Reliable starter cruiser for new pilots',
texture: 'assets/textures/ships/starter_cruiser.png',
stats: { attack: 15, speed: 10, defense: 12, hull: 100 }
},
{
id: 'starter_cruiser_uncommon',
name: 'Starter Cruiser II',
type: 'ship',
rarity: 'uncommon',
price: 12000,
currency: 'credits',
description: 'Upgraded starter cruiser with enhanced systems',
texture: 'assets/textures/ships/starter_cruiser.png',
stats: { attack: 18, speed: 12, defense: 15, hull: 120 }
},
{
id: 'starter_cruiser_rare',
name: 'Starter Cruiser III',
type: 'ship',
rarity: 'rare',
price: 25000,
currency: 'credits',
description: 'Elite starter cruiser with advanced weaponry',
texture: 'assets/textures/ships/starter_cruiser.png',
stats: { attack: 22, speed: 14, defense: 18, hull: 140 }
},
{
id: 'starter_cruiser_epic',
name: 'Starter Cruiser IV',
type: 'ship',
rarity: 'epic',
price: 50000,
currency: 'credits',
description: 'Master starter cruiser with elite modifications',
texture: 'assets/textures/ships/starter_cruiser.png',
stats: { attack: 28, speed: 16, defense: 22, hull: 160 }
},
{
id: 'starter_cruiser_legendary',
name: 'Starter Cruiser V',
type: 'ship',
rarity: 'legendary',
price: 100000,
currency: 'credits',
description: 'Legendary starter cruiser with unparalleled performance',
texture: 'assets/textures/ships/starter_cruiser.png',
stats: { attack: 35, speed: 18, defense: 28, hull: 180 }
}
];
// Weapons
this.shopItems.weapons = [
// Starter Blaster Variants
{
id: 'starter_blaster_common',
name: 'Common Blaster',
type: 'weapon',
rarity: 'common',
price: 1000,
currency: 'credits',
description: 'Basic blaster for new pilots',
texture: 'assets/textures/weapons/starter_blaster.png',
stats: { damage: 10, fireRate: 2, range: 5, energy: 5 }
},
{
id: 'starter_blaster_uncommon',
name: 'Starter Blaster II',
type: 'weapon',
rarity: 'uncommon',
price: 2500,
currency: 'credits',
description: 'Improved blaster with better damage output',
texture: 'assets/textures/weapons/starter_blaster.png',
stats: { damage: 12, fireRate: 2.2, range: 5.5, energy: 6 }
},
{
id: 'starter_blaster_rare',
name: 'Starter Blaster III',
type: 'weapon',
rarity: 'rare',
price: 5000,
currency: 'credits',
description: 'Advanced blaster with enhanced capabilities',
texture: 'assets/textures/weapons/starter_blaster.png',
stats: { damage: 15, fireRate: 2.5, range: 6, energy: 7 }
},
{
id: 'starter_blaster_epic',
name: 'Starter Blaster IV',
type: 'weapon',
rarity: 'epic',
price: 10000,
currency: 'credits',
description: 'Elite blaster with superior performance',
texture: 'assets/textures/weapons/starter_blaster.png',
stats: { damage: 18, fireRate: 3, range: 6.5, energy: 8 }
},
{
id: 'starter_blaster_legendary',
name: 'Starter Blaster V',
type: 'weapon',
rarity: 'legendary',
price: 20000,
currency: 'credits',
description: 'Legendary starter blaster with ultimate power',
texture: 'assets/textures/weapons/starter_blaster.png',
stats: { damage: 22, fireRate: 4, range: 7, energy: 10 }
}
];
// Armors
this.shopItems.armors = [
// Basic Armor Variants
{
id: 'basic_armor_common',
name: 'Basic Armor',
type: 'armor',
rarity: 'common',
price: 1500,
currency: 'credits',
description: 'Light protection for beginners',
texture: 'assets/textures/armors/basic_armor.png',
stats: { defense: 5, durability: 20, weight: 2, energyShield: 0 }
},
{
id: 'basic_armor_uncommon',
name: 'Basic Armor II',
type: 'armor',
rarity: 'uncommon',
price: 4000,
currency: 'credits',
description: 'Improved basic armor with better durability',
texture: 'assets/textures/armors/basic_armor.png',
stats: { defense: 7, durability: 25, weight: 2.2, energyShield: 2 }
},
{
id: 'basic_armor_rare',
name: 'Basic Armor III',
type: 'armor',
rarity: 'rare',
price: 8000,
currency: 'credits',
description: 'Enhanced armor with energy shielding',
texture: 'assets/textures/armors/basic_armor.png',
stats: { defense: 10, durability: 30, weight: 2.5, energyShield: 5 }
},
{
id: 'basic_armor_epic',
name: 'Basic Armor IV',
type: 'armor',
rarity: 'epic',
price: 15000,
currency: 'credits',
description: 'Elite armor with advanced protection systems',
texture: 'assets/textures/armors/basic_armor.png',
stats: { defense: 15, durability: 35, weight: 3, energyShield: 10 }
},
{
id: 'basic_armor_legendary',
name: 'Basic Armor V',
type: 'armor',
rarity: 'legendary',
price: 30000,
currency: 'credits',
description: 'Legendary armor with ultimate protection',
texture: 'assets/textures/armors/basic_armor.png',
stats: { defense: 20, durability: 40, weight: 3.5, energyShield: 15 }
}
];
// Materials
this.shopItems.materials = [
{
id: 'iron_ore',
name: 'Iron Ore',
type: 'material',
rarity: 'common',
price: 50,
currency: 'credits',
description: 'Raw iron ore used for crafting basic weapons and armor',
stackable: true
},
{
id: 'copper_wire',
name: 'Copper Wire',
type: 'material',
rarity: 'common',
price: 75,
currency: 'credits',
description: 'Copper wiring used in electronic components',
stackable: true
},
{
id: 'energy_crystal',
name: 'Energy Crystal',
type: 'material',
rarity: 'uncommon',
price: 200,
currency: 'credits',
description: 'Crystallized energy used for powered equipment',
stackable: true
},
{
id: 'rare_metal',
name: 'Rare Metal',
type: 'material',
rarity: 'rare',
price: 500,
currency: 'credits',
description: 'Rare metallic alloy used for high-end crafting',
stackable: true
},
{
id: 'advanced_components',
name: 'Advanced Components',
type: 'material',
rarity: 'rare',
price: 1000,
currency: 'credits',
description: 'Sophisticated electronic components for advanced ship systems',
stackable: true
}
];
// Consumables
this.shopItems.consumables = [
{
id: 'health_kit',
name: 'Health Kit',
type: 'consumable',
rarity: 'common',
price: 100,
currency: 'credits',
description: 'A medical kit that restores health',
consumable: true,
effect: { health: 50 }
},
{
id: 'energy_pack',
name: 'Energy Pack',
type: 'consumable',
rarity: 'common',
price: 150,
currency: 'credits',
description: 'A pack that restores energy',
consumable: true,
effect: { energy: 25 }
},
{
id: 'repair_kit',
name: 'Repair Kit',
type: 'consumable',
rarity: 'uncommon',
price: 300,
currency: 'credits',
description: 'A kit that repairs ship damage',
consumable: true,
effect: { health: 100 }
}
];
logger.info(`Shop initialized with ${this.getTotalShopItems()} items`);
}
getTotalShopItems() {
return Object.values(this.shopItems).reduce((total, category) => total + category.length, 0);
}
getShopItems(category = null) {
if (category && this.shopItems[category]) {
return this.shopItems[category];
}
return this.shopItems;
}
getItem(itemId) {
for (const category of Object.values(this.shopItems)) {
const item = category.find(item => item.id === itemId);
if (item) return item;
}
return null;
}
purchaseItem(userId, itemId, quantity = 1) {
const item = this.getItem(itemId);
if (!item) {
throw new Error('Item not found in shop');
}
const totalCost = item.price * quantity;
return {
item,
quantity,
totalCost,
currency: item.currency
};
}
calculateDailyReward(consecutiveDays) {
const bonusMultiplier = Math.min(consecutiveDays - 1, this.dailyRewards.maxConsecutiveDays - 1);
const bonusAmount = bonusMultiplier * this.dailyRewards.consecutiveBonus;
const totalReward = this.dailyRewards.baseReward + bonusAmount;
return {
baseReward: this.dailyRewards.baseReward,
consecutiveBonus: bonusAmount,
totalReward,
consecutiveDays
};
}
getRandomShopItems(category, count = 6) {
const items = this.shopItems[category] || [];
const shuffled = [...items].sort(() => Math.random() - 0.5);
return shuffled.slice(0, Math.min(count, items.length));
}
refreshShopInventory() {
logger.info('Refreshing shop inventory...');
// This would typically involve database operations
// For now, we'll just log the refresh
return true;
}
getShopStats() {
const stats = {
totalItems: this.getTotalShopItems(),
itemsByCategory: {},
averagePriceByCategory: {}
};
for (const [category, items] of Object.entries(this.shopItems)) {
stats.itemsByCategory[category] = items.length;
if (items.length > 0) {
const totalPrice = items.reduce((sum, item) => sum + item.price, 0);
stats.averagePriceByCategory[category] = Math.round(totalPrice / items.length);
}
}
return stats;
}
}
module.exports = EconomySystem;

View File

@ -1,287 +0,0 @@
const logger = require('../utils/logger');
const Player = require('../models/Player');
const Ship = require('../models/Ship');
const Inventory = require('../models/Inventory');
const Economy = require('./EconomySystem');
class GameSystem {
constructor() {
this.players = new Map();
this.servers = new Map();
this.economy = new Economy();
}
async initializeGameSystems() {
logger.info('Initializing server-side game systems...');
// Initialize economy system
await this.economy.initialize();
logger.info('Game systems initialized successfully');
}
// Player management
async createPlayer(userId, playerData) {
try {
const player = new Player({
userId,
...playerData,
createdAt: new Date(),
lastLogin: new Date()
});
await player.save();
this.players.set(userId, player);
logger.info(`Created new player for user: ${userId}`);
return player;
} catch (error) {
logger.error('Error creating player:', error);
throw error;
}
}
async loadPlayer(userId) {
try {
let player = this.players.get(userId);
if (!player) {
player = await Player.findOne({ userId }).populate('ships inventory');
if (player) {
this.players.set(userId, player);
}
}
return player;
} catch (error) {
logger.error('Error loading player:', error);
throw error;
}
}
async savePlayer(userId) {
try {
const player = this.players.get(userId);
if (player) {
await player.save();
logger.info(`Saved player data for user: ${userId}`);
}
} catch (error) {
logger.error('Error saving player:', error);
throw error;
}
}
// Ship management
async addShipToPlayer(userId, shipData) {
try {
const player = await this.loadPlayer(userId);
if (!player) {
throw new Error('Player not found');
}
const ship = new Ship({
...shipData,
userId,
acquiredAt: new Date()
});
await ship.save();
player.ships.push(ship._id);
await player.save();
logger.info(`Added ship ${ship.name} to player ${userId}`);
return ship;
} catch (error) {
logger.error('Error adding ship to player:', error);
throw error;
}
}
async equipShip(userId, shipId) {
try {
const player = await this.loadPlayer(userId);
if (!player) {
throw new Error('Player not found');
}
const ship = await Ship.findOne({ _id: shipId, userId });
if (!ship) {
throw new Error('Ship not found');
}
// Unequip current ship
if (player.currentShip) {
await Ship.findByIdAndUpdate(player.currentShip, { isEquipped: false });
}
// Equip new ship
ship.isEquipped = true;
await ship.save();
player.currentShip = ship._id;
await player.save();
logger.info(`Equipped ship ${ship.name} for player ${userId}`);
return ship;
} catch (error) {
logger.error('Error equipping ship:', error);
throw error;
}
}
// Server management
async createServer(serverData) {
try {
const serverId = `server_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
const server = {
id: serverId,
...serverData,
createdAt: new Date(),
players: [],
status: 'active'
};
this.servers.set(serverId, server);
logger.info(`Created new server: ${serverId}`);
return server;
} catch (error) {
logger.error('Error creating server:', error);
throw error;
}
}
async joinServer(serverId, userId) {
try {
const server = this.servers.get(serverId);
if (!server) {
throw new Error('Server not found');
}
if (server.players.length >= server.maxPlayers) {
throw new Error('Server is full');
}
if (!server.players.includes(userId)) {
server.players.push(userId);
}
logger.info(`Player ${userId} joined server ${serverId}`);
return server;
} catch (error) {
logger.error('Error joining server:', error);
throw error;
}
}
async leaveServer(serverId, userId) {
try {
const server = this.servers.get(serverId);
if (!server) {
throw new Error('Server not found');
}
server.players = server.players.filter(id => id !== userId);
if (server.players.length === 0) {
this.servers.delete(serverId);
logger.info(`Server ${serverId} deleted (no players)`);
}
logger.info(`Player ${userId} left server ${serverId}`);
return server;
} catch (error) {
logger.error('Error leaving server:', error);
throw error;
}
}
getServerList() {
return Array.from(this.servers.values()).map(server => ({
id: server.id,
name: server.name,
type: server.type,
maxPlayers: server.maxPlayers,
currentPlayers: server.players.length,
status: server.status,
region: server.region,
createdAt: server.createdAt
}));
}
// Game actions
async processGameAction(userId, actionData) {
try {
const player = await this.loadPlayer(userId);
if (!player) {
throw new Error('Player not found');
}
switch (actionData.type) {
case 'dungeon_enter':
return await this.handleDungeonEnter(player, actionData);
case 'ship_upgrade':
return await this.handleShipUpgrade(player, actionData);
case 'item_purchase':
return await this.handleItemPurchase(player, actionData);
case 'daily_reward':
return await this.handleDailyReward(player, actionData);
default:
throw new Error('Unknown action type');
}
} catch (error) {
logger.error('Error processing game action:', error);
throw error;
}
}
async handleDungeonEnter(player, data) {
// Dungeon logic will be implemented here
logger.info(`Player ${player.userId} entering dungeon`);
return { success: true, message: 'Dungeon entered' };
}
async handleShipUpgrade(player, data) {
// Ship upgrade logic will be implemented here
logger.info(`Player ${player.userId} upgrading ship`);
return { success: true, message: 'Ship upgraded' };
}
async handleItemPurchase(player, data) {
// Item purchase logic will be implemented here
logger.info(`Player ${player.userId} purchasing item`);
return { success: true, message: 'Item purchased' };
}
async handleDailyReward(player, data) {
// Daily reward logic will be implemented here
logger.info(`Player ${player.userId} claiming daily reward`);
return { success: true, message: 'Daily reward claimed' };
}
}
// Singleton instance
let gameSystem = null;
async function initializeGameSystems() {
if (!gameSystem) {
gameSystem = new GameSystem();
await gameSystem.initializeGameSystems();
}
return gameSystem;
}
function getGameSystem() {
if (!gameSystem) {
throw new Error('Game system not initialized');
}
return gameSystem;
}
module.exports = {
GameSystem,
initializeGameSystems,
getGameSystem
};

View File

@ -1,220 +0,0 @@
const request = require('supertest');
const mongoose = require('mongoose');
const app = require('../server');
const Player = require('../models/Player');
describe('API Tests', () => {
let token;
let testUser;
beforeAll(async () => {
// Connect to test database
const mongoUri = process.env.MONGODB_TEST_URI || 'mongodb://localhost:27017/galaxystrikeonline_test';
await mongoose.connect(mongoUri);
});
afterAll(async () => {
// Clean up and close connection
await Player.deleteMany({});
await mongoose.connection.close();
});
beforeEach(async () => {
// Clean up before each test
await Player.deleteMany({});
});
describe('Authentication', () => {
test('POST /api/auth/register - should register new user', async () => {
const userData = {
username: 'testuser',
email: 'test@example.com',
password: 'password123'
};
const response = await request(app)
.post('/api/auth/register')
.send(userData)
.expect(201);
expect(response.body).toHaveProperty('token');
expect(response.body.user).toHaveProperty('username', userData.username);
expect(response.body.user).toHaveProperty('email', userData.email);
});
test('POST /api/auth/login - should login existing user', async () => {
// First register a user
const userData = {
username: 'testuser',
email: 'test@example.com',
password: 'password123'
};
await request(app)
.post('/api/auth/register')
.send(userData);
// Then login
const loginData = {
email: userData.email,
password: userData.password
};
const response = await request(app)
.post('/api/auth/login')
.send(loginData)
.expect(200);
expect(response.body).toHaveProperty('token');
token = response.body.token;
testUser = response.body.user;
});
test('GET /api/auth/verify - should verify token', async () => {
// First login to get token
const userData = {
username: 'testuser',
email: 'test@example.com',
password: 'password123'
};
const registerResponse = await request(app)
.post('/api/auth/register')
.send(userData);
token = registerResponse.body.token;
const response = await request(app)
.get('/api/auth/verify')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(response.body).toHaveProperty('valid', true);
expect(response.body.user).toHaveProperty('username', userData.username);
});
});
describe('Game API', () => {
beforeEach(async () => {
// Create and login a user for game tests
const userData = {
username: 'testuser',
email: 'test@example.com',
password: 'password123'
};
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: userData.email,
password: userData.password
});
token = loginResponse.body.token;
testUser = loginResponse.body.user;
});
test('GET /api/game/player - should get player data', async () => {
const response = await request(app)
.get('/api/game/player')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(response.body).toHaveProperty('userId');
expect(response.body).toHaveProperty('stats');
expect(response.body).toHaveProperty('attributes');
});
test('GET /api/game/ships - should get player ships', async () => {
const response = await request(app)
.get('/api/game/ships')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(response.body).toHaveProperty('ships');
expect(Array.isArray(response.body.ships)).toBe(true);
});
test('GET /api/game/inventory - should get player inventory', async () => {
const response = await request(app)
.get('/api/game/inventory')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(response.body).toHaveProperty('items');
expect(response.body).toHaveProperty('summary');
});
test('POST /api/game/daily-reward - should claim daily reward', async () => {
const response = await request(app)
.post('/api/game/daily-reward')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(response.body).toHaveProperty('success');
});
});
describe('Server API', () => {
beforeEach(async () => {
// Create and login a user for server tests
const userData = {
username: 'testuser',
email: 'test@example.com',
password: 'password123'
};
const loginResponse = await request(app)
.post('/api/auth/login')
.send({
email: userData.email,
password: userData.password
});
token = loginResponse.body.token;
testUser = loginResponse.body.user;
});
test('GET /api/servers - should get server list', async () => {
const response = await request(app)
.get('/api/servers')
.set('Authorization', `Bearer ${token}`)
.expect(200);
expect(response.body).toHaveProperty('servers');
expect(response.body).toHaveProperty('totalServers');
expect(Array.isArray(response.body.servers)).toBe(true);
});
test('POST /api/servers/create - should create new server', async () => {
const serverData = {
name: 'Test Server',
type: 'public',
maxPlayers: 10,
region: 'us-east'
};
const response = await request(app)
.post('/api/servers/create')
.set('Authorization', `Bearer ${token}`)
.send(serverData)
.expect(201);
expect(response.body).toHaveProperty('message');
expect(response.body.server).toHaveProperty('name', serverData.name);
expect(response.body.server).toHaveProperty('type', serverData.type);
});
});
describe('Health Check', () => {
test('GET /health - should return health status', async () => {
const response = await request(app)
.get('/health')
.expect(200);
expect(response.body).toHaveProperty('status', 'OK');
expect(response.body).toHaveProperty('timestamp');
expect(response.body).toHaveProperty('uptime');
});
});
});

View File

@ -1,27 +0,0 @@
const winston = require('winston');
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.errors({ stack: true }),
winston.format.json()
),
defaultMeta: { service: 'galaxystrikeonline-server' },
transports: [
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
new winston.transports.File({ filename: 'logs/combined.log' })
]
});
// Add console transport for development
if (process.env.NODE_ENV !== 'production') {
logger.add(new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
)
}));
}
module.exports = logger;