removed combined server software
This commit is contained in:
parent
62b676c0d1
commit
08edb2d80d
@ -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
|
|
||||||
@ -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;
|
|
||||||
@ -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
|
|
||||||
};
|
|
||||||
@ -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
|
|
||||||
};
|
|
||||||
@ -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);
|
|
||||||
@ -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);
|
|
||||||
@ -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
6068
Server/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
@ -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;
|
|
||||||
@ -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;
|
|
||||||
@ -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;
|
|
||||||
@ -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;
|
|
||||||
@ -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;
|
|
||||||
195
Server/server.js
195
Server/server.js
@ -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 };
|
|
||||||
@ -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;
|
|
||||||
@ -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;
|
|
||||||
@ -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
|
|
||||||
};
|
|
||||||
@ -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');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -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;
|
|
||||||
Reference in New Issue
Block a user