207 lines
6.0 KiB
TypeScript
207 lines
6.0 KiB
TypeScript
import express from 'express';
|
|
import mongoose from 'mongoose';
|
|
import dotenv from 'dotenv';
|
|
import jwt from 'jsonwebtoken';
|
|
import cors from 'cors';
|
|
import User, { IUser } from './models/User';
|
|
import ShopItem from './models/ShopItem';
|
|
|
|
dotenv.config();
|
|
|
|
// Расширяем типы Express
|
|
declare global {
|
|
namespace Express {
|
|
interface Request {
|
|
user?: {
|
|
userId: string;
|
|
telegramId: string;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const app = express();
|
|
|
|
// Middleware
|
|
app.use(cors({
|
|
origin: process.env.NEXT_PUBLIC_URL || 'http://localhost:3000',
|
|
credentials: true
|
|
}));
|
|
app.use(express.json());
|
|
|
|
// Подключение к MongoDB
|
|
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/campfire-id')
|
|
.then(() => console.log('Connected to MongoDB'))
|
|
.catch(err => console.error('MongoDB connection error:', err));
|
|
|
|
// Middleware для проверки JWT
|
|
const authenticateToken = (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
const authHeader = req.headers['authorization'];
|
|
const token = authHeader && authHeader.split(' ')[1];
|
|
|
|
if (!token) {
|
|
return res.status(401).json({ error: 'Требуется авторизация' });
|
|
}
|
|
|
|
jwt.verify(token, process.env.JWT_SECRET || 'your-secret-key', (err: any, user: any) => {
|
|
if (err) {
|
|
return res.status(403).json({ error: 'Недействительный токен' });
|
|
}
|
|
req.user = user;
|
|
next();
|
|
});
|
|
};
|
|
|
|
// Обработка ошибок
|
|
const errorHandler = (err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
console.error(err.stack);
|
|
res.status(500).json({
|
|
error: 'Ошибка сервера',
|
|
message: process.env.NODE_ENV === 'development' ? err.message : undefined
|
|
});
|
|
};
|
|
|
|
// Регистрация/авторизация пользователя
|
|
app.post('/api/auth', async (req, res, next) => {
|
|
try {
|
|
const { telegramId, username } = req.body;
|
|
|
|
if (!telegramId || !username) {
|
|
return res.status(400).json({ error: 'Отсутствуют обязательные поля' });
|
|
}
|
|
|
|
let user = await User.findOne({ telegramId });
|
|
|
|
if (!user) {
|
|
user = new User({
|
|
telegramId,
|
|
username,
|
|
// Начальный бонус для новых пользователей
|
|
balance: 100
|
|
});
|
|
await user.save();
|
|
}
|
|
|
|
const token = jwt.sign(
|
|
{ userId: user._id, telegramId },
|
|
process.env.JWT_SECRET || 'your-secret-key',
|
|
{ expiresIn: '7d' }
|
|
);
|
|
|
|
res.json({ token, user });
|
|
} catch (error) {
|
|
next(error);
|
|
}
|
|
});
|
|
|
|
// Получение профиля пользователя
|
|
app.get('/api/profile', authenticateToken, async (req, res) => {
|
|
try {
|
|
const user = await User.findById(req.user.userId);
|
|
if (!user) {
|
|
return res.status(404).json({ error: 'Пользователь не найден' });
|
|
}
|
|
res.json(user);
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Ошибка сервера' });
|
|
}
|
|
});
|
|
|
|
// Получение списка предметов в магазине
|
|
app.get('/api/shop', authenticateToken, async (req, res) => {
|
|
try {
|
|
const items = await ShopItem.find({ available: true });
|
|
res.json(items);
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Ошибка сервера' });
|
|
}
|
|
});
|
|
|
|
// Покупка предмета
|
|
app.post('/api/shop/purchase', authenticateToken, async (req, res) => {
|
|
try {
|
|
const { itemId } = req.body;
|
|
const user = await User.findById(req.user.userId);
|
|
const item = await ShopItem.findById(itemId);
|
|
|
|
if (!user || !item) {
|
|
return res.status(404).json({ error: 'Пользователь или предмет не найден' });
|
|
}
|
|
|
|
if (user.balance < item.price) {
|
|
return res.status(400).json({ error: 'Недостаточно средств' });
|
|
}
|
|
|
|
// Обновляем баланс и инвентарь
|
|
user.balance -= item.price;
|
|
const existingItem = user.inventory.find(i => i.itemId === itemId);
|
|
|
|
if (existingItem) {
|
|
existingItem.quantity += 1;
|
|
} else {
|
|
user.inventory.push({
|
|
itemId: item._id,
|
|
name: item.name,
|
|
description: item.description,
|
|
quantity: 1,
|
|
imageUrl: item.imageUrl
|
|
});
|
|
}
|
|
|
|
await user.save();
|
|
res.json({ success: true, user });
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Ошибка сервера' });
|
|
}
|
|
});
|
|
|
|
// Перевод баланса между пользователями
|
|
app.post('/api/transfer', authenticateToken, async (req, res) => {
|
|
try {
|
|
const { recipientUsername, amount } = req.body;
|
|
|
|
if (amount <= 0) {
|
|
return res.status(400).json({ error: 'Неверная сумма перевода' });
|
|
}
|
|
|
|
const sender = await User.findById(req.user.userId);
|
|
const recipient = await User.findOne({ username: recipientUsername });
|
|
|
|
if (!sender || !recipient) {
|
|
return res.status(404).json({ error: 'Отправитель или получатель не найден' });
|
|
}
|
|
|
|
if (sender.balance < amount) {
|
|
return res.status(400).json({ error: 'Недостаточно средств' });
|
|
}
|
|
|
|
// Выполняем перевод
|
|
sender.balance -= amount;
|
|
recipient.balance += amount;
|
|
|
|
await sender.save();
|
|
await recipient.save();
|
|
|
|
// Добавляем достижение за первый перевод
|
|
if (sender.achievements.every(a => a.id !== 'first_transfer')) {
|
|
await sender.addAchievement({
|
|
id: 'first_transfer',
|
|
name: 'Щедрая душа',
|
|
description: 'Совершили первый перевод',
|
|
dateUnlocked: new Date()
|
|
});
|
|
}
|
|
|
|
res.json({ success: true, balance: sender.balance });
|
|
} catch (error) {
|
|
res.status(500).json({ error: 'Ошибка сервера' });
|
|
}
|
|
});
|
|
|
|
// Добавляем обработчик ошибок в конце
|
|
app.use(errorHandler);
|
|
|
|
const PORT = process.env.PORT || 3001;
|
|
app.listen(PORT, () => {
|
|
console.log(`Server is running on port ${PORT}`);
|
|
});
|