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