Update files
This commit is contained in:
parent
a4bae70823
commit
d83399e5f9
|
|
@ -0,0 +1,145 @@
|
|||
# Обновление: Управление админами и исправления
|
||||
|
||||
## ✅ Что сделано
|
||||
|
||||
### 1. Убран суффикс "Сообщите об ошибке" из специфичных ошибок
|
||||
- Обновлён `backend/server.js`
|
||||
- Суффикс не добавляется к ошибкам валидации, публикации и других операционных сообщений
|
||||
- Список исключений: "Загрузите хотя бы одно изображение", "Не удалось опубликовать в канал", "Требуется авторизация", и др.
|
||||
|
||||
### 2. Добавлено управление админами через Mini App
|
||||
**Новые модели:**
|
||||
- `backend/models/AdminConfirmation.js` - хранение кодов подтверждения (TTL 5 минут)
|
||||
- Обновлена `backend/models/ModerationAdmin.js` - добавлено поле `adminNumber` (1-10)
|
||||
|
||||
**Новые API endpoints в `/api/mod-app`:**
|
||||
- `GET /admins` - получить список всех админов
|
||||
- `POST /admins/initiate-add` - инициировать добавление админа (только для @glpshchn00)
|
||||
- `POST /admins/confirm-add` - подтвердить добавление по коду
|
||||
- `POST /admins/initiate-remove` - инициировать удаление админа (только для @glpshchn00)
|
||||
- `POST /admins/confirm-remove` - подтвердить удаление по коду
|
||||
|
||||
**Как работает:**
|
||||
1. Владелец (@glpshchn00) видит кнопки "Назначить" и "Снять" у пользователей
|
||||
2. При нажатии выбирается номер админа (1-10)
|
||||
3. Система генерирует 6-значный код и отправляет пользователю в личку бота
|
||||
4. Пользователь вводит код в Mini App
|
||||
5. После подтверждения админ добавляется/удаляется
|
||||
|
||||
### 3. Номера админов (1-10)
|
||||
- Каждому админу присваивается уникальный номер от 1 до 10
|
||||
- Номер выбирается владельцем при назначении
|
||||
- Номер используется автоматически при публикации постов (теперь НЕ нужно выбирать слот)
|
||||
|
||||
### 4. Убран выбор слота из публикации
|
||||
- В `backend/routes/modApp.js` роут `/channel/publish` обновлён
|
||||
- Теперь автоматически берётся `adminNumber` из базы данных
|
||||
- Поле `slot` больше не требуется в запросе
|
||||
|
||||
### 5. Исправлен live chat
|
||||
- Обновлён `backend/websocket.js`
|
||||
- Владелец (@glpshchn00) теперь может подключаться к чату
|
||||
- Добавлена проверка `config.moderationOwnerUsernames`
|
||||
- Улучшено логирование подключений
|
||||
|
||||
## 📦 Деплой
|
||||
|
||||
### На сервере:
|
||||
|
||||
```bash
|
||||
cd /var/www/nakama
|
||||
|
||||
# 1. Обновить код (если через git)
|
||||
git pull
|
||||
|
||||
# 2. Установить зависимости (если добавились новые)
|
||||
npm install --production
|
||||
|
||||
# 3. Перезапустить бекэнд
|
||||
pm2 restart nakama-backend --update-env
|
||||
|
||||
# 4. Проверить логи
|
||||
pm2 logs nakama-backend --lines 50
|
||||
```
|
||||
|
||||
### Обновление существующих админов:
|
||||
|
||||
Если у тебя уже есть админы в базе БЕЗ `adminNumber`, нужно добавить номера вручную:
|
||||
|
||||
```bash
|
||||
mongosh nakama
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Посмотреть текущих админов
|
||||
db.moderationadmins.find()
|
||||
|
||||
// Назначить номера вручную (замени ID и номера)
|
||||
db.moderationadmins.updateOne(
|
||||
{ _id: ObjectId("...") },
|
||||
{ $set: { adminNumber: 1 } }
|
||||
)
|
||||
|
||||
db.moderationadmins.updateOne(
|
||||
{ _id: ObjectId("...") },
|
||||
{ $set: { adminNumber: 2 } }
|
||||
)
|
||||
|
||||
// И так далее для каждого админа
|
||||
```
|
||||
|
||||
Или удалить всех и добавить заново через Mini App:
|
||||
|
||||
```javascript
|
||||
db.moderationadmins.deleteMany({})
|
||||
```
|
||||
|
||||
## 🎯 Следующие шаги
|
||||
|
||||
Нужно обновить фронтенд модерации (`moderation/frontend/src/App.jsx`), чтобы добавить:
|
||||
|
||||
1. **Новую вкладку "Админы"** с:
|
||||
- Списком всех админов с номерами
|
||||
- Кнопками "Назначить" и "Снять" (только для @glpshchn00)
|
||||
- Модальным окном для ввода кода подтверждения
|
||||
- Выбором номера админа (1-10)
|
||||
|
||||
2. **Убрать выбор слота** из вкладки "Публикация":
|
||||
- Удалить dropdown со слотами
|
||||
- Показывать текущий номер админа из базы
|
||||
|
||||
3. **Тестирование:**
|
||||
- Проверить live chat
|
||||
- Проверить добавление/удаление админов
|
||||
- Проверить публикацию с автоматическим слотом
|
||||
|
||||
## 🔒 Безопасность
|
||||
|
||||
- Все операции с админами требуют авторизации через `authenticateModeration`
|
||||
- Добавление/удаление доступно только владельцу через middleware `requireOwner`
|
||||
- Коды подтверждения удаляются автоматически через 5 минут (MongoDB TTL)
|
||||
- Коды одноразовые - удаляются сразу после использования
|
||||
- Боту нужны права отправки сообщений пользователям
|
||||
|
||||
## ⚠️ Важно
|
||||
|
||||
**Перед запуском на проде убедись:**
|
||||
1. `MODERATION_BOT_TOKEN` правильно настроен в `.env`
|
||||
2. Бот может отправлять сообщения пользователям (они должны начать диалог с ботом)
|
||||
3. Владелец (@glpshchn00) правильно указан в `MODERATION_OWNER_USERNAMES`
|
||||
4. MongoDB доступна и работает
|
||||
|
||||
## 🐛 Возможные проблемы
|
||||
|
||||
**"Бот не отправляет код":**
|
||||
- Проверь, что пользователь написал боту `/start`
|
||||
- Проверь `MODERATION_BOT_TOKEN` в логах
|
||||
|
||||
**"Номер админа уже занят":**
|
||||
- Проверь `db.moderationadmins.find()` - возможно есть дубликаты
|
||||
- Очисти базу: `db.moderationadmins.deleteMany({})`
|
||||
|
||||
**"Live chat не подключается":**
|
||||
- Проверь, что владелец указан в `MODERATION_OWNER_USERNAMES`
|
||||
- Посмотри логи WebSocket подключения
|
||||
|
||||
|
|
@ -394,9 +394,31 @@ const sendChannelMediaGroup = async (files, caption) => {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Отправить сообщение пользователю
|
||||
*/
|
||||
const sendMessageToUser = async (userId, message) => {
|
||||
if (!moderationBot) {
|
||||
throw new Error('Бот модерации не инициализирован');
|
||||
}
|
||||
|
||||
try {
|
||||
await axios.post(`${TELEGRAM_API}/sendMessage`, {
|
||||
chat_id: userId,
|
||||
text: message,
|
||||
parse_mode: 'HTML'
|
||||
});
|
||||
log('info', 'Сообщение отправлено пользователю', { userId });
|
||||
} catch (error) {
|
||||
log('error', 'Не удалось отправить сообщение пользователю', { userId, error: error.response?.data || error.message });
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
startServerMonitorBot,
|
||||
sendChannelMediaGroup,
|
||||
sendMessageToUser,
|
||||
isModerationAdmin
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
const mongoose = require('mongoose');
|
||||
|
||||
const AdminConfirmationSchema = new mongoose.Schema({
|
||||
userId: {
|
||||
type: String,
|
||||
required: true,
|
||||
index: true
|
||||
},
|
||||
code: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
adminNumber: {
|
||||
type: Number,
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 10
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
enum: ['add', 'remove'],
|
||||
required: true
|
||||
},
|
||||
createdAt: {
|
||||
type: Date,
|
||||
default: Date.now,
|
||||
expires: 300 // Удаляется через 5 минут
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = mongoose.model('AdminConfirmation', AdminConfirmationSchema);
|
||||
|
||||
|
|
@ -15,6 +15,13 @@ const ModerationAdminSchema = new mongoose.Schema({
|
|||
},
|
||||
firstName: String,
|
||||
lastName: String,
|
||||
adminNumber: {
|
||||
type: Number,
|
||||
required: true,
|
||||
min: 1,
|
||||
max: 10,
|
||||
unique: true
|
||||
},
|
||||
addedBy: {
|
||||
type: String,
|
||||
lowercase: true,
|
||||
|
|
|
|||
|
|
@ -3,13 +3,16 @@ const router = express.Router();
|
|||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const multer = require('multer');
|
||||
const crypto = require('crypto');
|
||||
const { authenticateModeration } = require('../middleware/auth');
|
||||
const { logSecurityEvent } = require('../middleware/logger');
|
||||
const User = require('../models/User');
|
||||
const Post = require('../models/Post');
|
||||
const Report = require('../models/Report');
|
||||
const ModerationAdmin = require('../models/ModerationAdmin');
|
||||
const AdminConfirmation = require('../models/AdminConfirmation');
|
||||
const { listAdmins, isModerationAdmin, normalizeUsername } = require('../services/moderationAdmin');
|
||||
const { sendChannelMediaGroup } = require('../bots/serverMonitor');
|
||||
const { sendChannelMediaGroup, sendMessageToUser } = require('../bots/serverMonitor');
|
||||
const config = require('../config');
|
||||
|
||||
const TEMP_DIR = path.join(__dirname, '../uploads/mod-channel');
|
||||
|
|
@ -44,6 +47,7 @@ const requireModerationAccess = async (req, res, next) => {
|
|||
|
||||
if (OWNER_USERNAMES.has(username) || req.user.role === 'admin') {
|
||||
req.isModerationAdmin = true;
|
||||
req.isOwner = true;
|
||||
return next();
|
||||
}
|
||||
|
||||
|
|
@ -53,9 +57,17 @@ const requireModerationAccess = async (req, res, next) => {
|
|||
}
|
||||
|
||||
req.isModerationAdmin = true;
|
||||
req.isOwner = false;
|
||||
return next();
|
||||
};
|
||||
|
||||
const requireOwner = (req, res, next) => {
|
||||
if (!req.isOwner) {
|
||||
return res.status(403).json({ error: 'Требуются права владельца' });
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
const serializeUser = (user) => ({
|
||||
id: user._id,
|
||||
username: user.username,
|
||||
|
|
@ -360,20 +372,289 @@ router.put('/reports/:id', authenticateModeration, requireModerationAccess, asyn
|
|||
res.json({ success: true });
|
||||
});
|
||||
|
||||
// ========== УПРАВЛЕНИЕ АДМИНАМИ ==========
|
||||
|
||||
// Получить список всех админов
|
||||
router.get('/admins', authenticateModeration, requireModerationAccess, async (req, res) => {
|
||||
try {
|
||||
const admins = await ModerationAdmin.find().sort({ adminNumber: 1 });
|
||||
res.json({
|
||||
admins: admins.map(admin => ({
|
||||
id: admin._id,
|
||||
telegramId: admin.telegramId,
|
||||
username: admin.username,
|
||||
firstName: admin.firstName,
|
||||
lastName: admin.lastName,
|
||||
adminNumber: admin.adminNumber,
|
||||
addedBy: admin.addedBy,
|
||||
createdAt: admin.createdAt
|
||||
}))
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка получения списка админов:', error);
|
||||
res.status(500).json({ error: 'Ошибка получения списка админов' });
|
||||
}
|
||||
});
|
||||
|
||||
// Инициировать добавление админа (только для владельца)
|
||||
router.post('/admins/initiate-add', authenticateModeration, requireModerationAccess, requireOwner, async (req, res) => {
|
||||
try {
|
||||
const { userId, adminNumber } = req.body;
|
||||
|
||||
if (!userId || !adminNumber) {
|
||||
return res.status(400).json({ error: 'Не указан ID пользователя или номер админа' });
|
||||
}
|
||||
|
||||
if (adminNumber < 1 || adminNumber > 10) {
|
||||
return res.status(400).json({ error: 'Номер админа должен быть от 1 до 10' });
|
||||
}
|
||||
|
||||
// Проверить, не занят ли номер
|
||||
const existingAdmin = await ModerationAdmin.findOne({ adminNumber });
|
||||
if (existingAdmin) {
|
||||
return res.status(400).json({ error: 'Номер админа уже занят' });
|
||||
}
|
||||
|
||||
// Проверить, существует ли пользователь
|
||||
const user = await User.findById(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'Пользователь не найден' });
|
||||
}
|
||||
|
||||
// Проверить, не является ли пользователь уже админом
|
||||
const isAlreadyAdmin = await ModerationAdmin.findOne({ telegramId: user.telegramId });
|
||||
if (isAlreadyAdmin) {
|
||||
return res.status(400).json({ error: 'Пользователь уже является админом' });
|
||||
}
|
||||
|
||||
// Генерировать 6-значный код
|
||||
const code = crypto.randomInt(100000, 999999).toString();
|
||||
|
||||
// Сохранить код подтверждения
|
||||
await AdminConfirmation.create({
|
||||
userId: user.telegramId,
|
||||
code,
|
||||
adminNumber,
|
||||
action: 'add'
|
||||
});
|
||||
|
||||
// Отправить код пользователю
|
||||
await sendMessageToUser(
|
||||
user.telegramId,
|
||||
`<b>Подтверждение назначения админом</b>\n\n` +
|
||||
`Вас назначают администратором модерации.\n` +
|
||||
`Номер админа: <b>${adminNumber}</b>\n\n` +
|
||||
`Для подтверждения введите код в приложении:\n` +
|
||||
`<code>${code}</code>\n\n` +
|
||||
`Код действителен 5 минут.`
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Код подтверждения отправлен пользователю',
|
||||
username: user.username
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка инициирования добавления админа:', error);
|
||||
res.status(500).json({ error: 'Ошибка отправки кода подтверждения' });
|
||||
}
|
||||
});
|
||||
|
||||
// Подтвердить добавление админа
|
||||
router.post('/admins/confirm-add', authenticateModeration, requireModerationAccess, requireOwner, async (req, res) => {
|
||||
try {
|
||||
const { userId, code } = req.body;
|
||||
|
||||
if (!userId || !code) {
|
||||
return res.status(400).json({ error: 'Не указан ID пользователя или код' });
|
||||
}
|
||||
|
||||
const user = await User.findById(userId);
|
||||
if (!user) {
|
||||
return res.status(404).json({ error: 'Пользователь не найден' });
|
||||
}
|
||||
|
||||
// Найти код подтверждения
|
||||
const confirmation = await AdminConfirmation.findOne({
|
||||
userId: user.telegramId,
|
||||
code,
|
||||
action: 'add'
|
||||
});
|
||||
|
||||
if (!confirmation) {
|
||||
return res.status(400).json({ error: 'Неверный код подтверждения' });
|
||||
}
|
||||
|
||||
// Проверить, не занят ли номер
|
||||
const existingAdmin = await ModerationAdmin.findOne({ adminNumber: confirmation.adminNumber });
|
||||
if (existingAdmin) {
|
||||
await AdminConfirmation.deleteOne({ _id: confirmation._id });
|
||||
return res.status(400).json({ error: 'Номер админа уже занят' });
|
||||
}
|
||||
|
||||
// Добавить админа
|
||||
const newAdmin = await ModerationAdmin.create({
|
||||
telegramId: user.telegramId,
|
||||
username: normalizeUsername(user.username),
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
adminNumber: confirmation.adminNumber,
|
||||
addedBy: normalizeUsername(req.user.username)
|
||||
});
|
||||
|
||||
// Удалить код подтверждения
|
||||
await AdminConfirmation.deleteOne({ _id: confirmation._id });
|
||||
|
||||
// Уведомить пользователя
|
||||
try {
|
||||
await sendMessageToUser(
|
||||
user.telegramId,
|
||||
`<b>✅ Вы назначены администратором модерации!</b>\n\n` +
|
||||
`Ваш номер: <b>${confirmation.adminNumber}</b>\n` +
|
||||
`Теперь вы можете использовать модераторское приложение.`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Не удалось отправить уведомление пользователю:', error);
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
admin: {
|
||||
id: newAdmin._id,
|
||||
telegramId: newAdmin.telegramId,
|
||||
username: newAdmin.username,
|
||||
firstName: newAdmin.firstName,
|
||||
lastName: newAdmin.lastName,
|
||||
adminNumber: newAdmin.adminNumber
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка подтверждения добавления админа:', error);
|
||||
res.status(500).json({ error: 'Ошибка добавления админа' });
|
||||
}
|
||||
});
|
||||
|
||||
// Инициировать удаление админа (только для владельца)
|
||||
router.post('/admins/initiate-remove', authenticateModeration, requireModerationAccess, requireOwner, async (req, res) => {
|
||||
try {
|
||||
const { adminId } = req.body;
|
||||
|
||||
if (!adminId) {
|
||||
return res.status(400).json({ error: 'Не указан ID админа' });
|
||||
}
|
||||
|
||||
const admin = await ModerationAdmin.findById(adminId);
|
||||
if (!admin) {
|
||||
return res.status(404).json({ error: 'Администратор не найден' });
|
||||
}
|
||||
|
||||
// Генерировать 6-значный код
|
||||
const code = crypto.randomInt(100000, 999999).toString();
|
||||
|
||||
// Сохранить код подтверждения
|
||||
await AdminConfirmation.create({
|
||||
userId: admin.telegramId,
|
||||
code,
|
||||
adminNumber: admin.adminNumber,
|
||||
action: 'remove'
|
||||
});
|
||||
|
||||
// Отправить код пользователю
|
||||
await sendMessageToUser(
|
||||
admin.telegramId,
|
||||
`<b>Подтверждение снятия с должности админа</b>\n\n` +
|
||||
`Вас снимают с должности администратора модерации.\n\n` +
|
||||
`Для подтверждения введите код в приложении:\n` +
|
||||
`<code>${code}</code>\n\n` +
|
||||
`Код действителен 5 минут.`
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Код подтверждения отправлен админу',
|
||||
username: admin.username
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Ошибка инициирования удаления админа:', error);
|
||||
res.status(500).json({ error: 'Ошибка отправки кода подтверждения' });
|
||||
}
|
||||
});
|
||||
|
||||
// Подтвердить удаление админа
|
||||
router.post('/admins/confirm-remove', authenticateModeration, requireModerationAccess, requireOwner, async (req, res) => {
|
||||
try {
|
||||
const { adminId, code } = req.body;
|
||||
|
||||
if (!adminId || !code) {
|
||||
return res.status(400).json({ error: 'Не указан ID админа или код' });
|
||||
}
|
||||
|
||||
const admin = await ModerationAdmin.findById(adminId);
|
||||
if (!admin) {
|
||||
return res.status(404).json({ error: 'Администратор не найден' });
|
||||
}
|
||||
|
||||
// Найти код подтверждения
|
||||
const confirmation = await AdminConfirmation.findOne({
|
||||
userId: admin.telegramId,
|
||||
code,
|
||||
action: 'remove'
|
||||
});
|
||||
|
||||
if (!confirmation) {
|
||||
return res.status(400).json({ error: 'Неверный код подтверждения' });
|
||||
}
|
||||
|
||||
// Удалить админа
|
||||
await ModerationAdmin.deleteOne({ _id: admin._id });
|
||||
|
||||
// Удалить код подтверждения
|
||||
await AdminConfirmation.deleteOne({ _id: confirmation._id });
|
||||
|
||||
// Уведомить пользователя
|
||||
try {
|
||||
await sendMessageToUser(
|
||||
admin.telegramId,
|
||||
`<b>❌ Вы сняты с должности администратора модерации</b>\n\n` +
|
||||
`Доступ к модераторскому приложению прекращён.`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Не удалось отправить уведомление пользователю:', error);
|
||||
}
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Ошибка подтверждения удаления админа:', error);
|
||||
res.status(500).json({ error: 'Ошибка удаления админа' });
|
||||
}
|
||||
});
|
||||
|
||||
// ========== ПУБЛИКАЦИЯ В КАНАЛ ==========
|
||||
|
||||
router.post(
|
||||
'/channel/publish',
|
||||
authenticateModeration,
|
||||
requireModerationAccess,
|
||||
upload.array('images', 10),
|
||||
async (req, res) => {
|
||||
const { description = '', tags, slot } = req.body;
|
||||
const { description = '', tags } = req.body;
|
||||
const files = req.files || [];
|
||||
|
||||
if (!files.length) {
|
||||
return res.status(400).json({ error: 'Загрузите хотя бы одно изображение' });
|
||||
}
|
||||
|
||||
const slotNumber = Math.max(Math.min(parseInt(slot, 10) || 1, 10), 1);
|
||||
// Получить номер админа из базы
|
||||
const admin = await ModerationAdmin.findOne({ telegramId: req.user.telegramId });
|
||||
|
||||
// Проверить, что админ имеет номер от 1 до 10
|
||||
if (!admin || !admin.adminNumber || admin.adminNumber < 1 || admin.adminNumber > 10) {
|
||||
return res.status(403).json({
|
||||
error: 'Публиковать в канал могут только админы с номерами от 1 до 10. Обратитесь к владельцу для назначения номера.'
|
||||
});
|
||||
}
|
||||
|
||||
const slotNumber = admin.adminNumber;
|
||||
|
||||
let tagsArray = [];
|
||||
if (typeof tags === 'string' && tags.trim()) {
|
||||
|
|
|
|||
|
|
@ -80,20 +80,40 @@ app.use((req, res, next) => {
|
|||
return;
|
||||
}
|
||||
|
||||
if (typeof obj.error === 'string' && !obj.error.includes(ERROR_SUPPORT_SUFFIX)) {
|
||||
// Список ошибок, к которым НЕ нужно добавлять суффикс
|
||||
const skipSuffixMessages = [
|
||||
'Загрузите хотя бы одно изображение',
|
||||
'Не удалось опубликовать в канал',
|
||||
'Публиковать в канал могут только админы',
|
||||
'Требуется авторизация',
|
||||
'Требуются права',
|
||||
'Неверный код подтверждения',
|
||||
'Код подтверждения истёк',
|
||||
'Номер админа уже занят',
|
||||
'Пользователь не найден',
|
||||
'Администратор не найден'
|
||||
];
|
||||
|
||||
const shouldSkipSuffix = (text) => {
|
||||
if (!text || typeof text !== 'string') return false;
|
||||
return skipSuffixMessages.some(msg => text.includes(msg));
|
||||
};
|
||||
|
||||
if (typeof obj.error === 'string' && !obj.error.includes(ERROR_SUPPORT_SUFFIX) && !shouldSkipSuffix(obj.error)) {
|
||||
obj.error += ERROR_SUPPORT_SUFFIX;
|
||||
}
|
||||
|
||||
if (typeof obj.message === 'string' && res.statusCode >= 400 && !obj.message.includes(ERROR_SUPPORT_SUFFIX)) {
|
||||
if (typeof obj.message === 'string' && res.statusCode >= 400 && !obj.message.includes(ERROR_SUPPORT_SUFFIX) && !shouldSkipSuffix(obj.message)) {
|
||||
obj.message += ERROR_SUPPORT_SUFFIX;
|
||||
}
|
||||
|
||||
if (Array.isArray(obj.errors)) {
|
||||
obj.errors = obj.errors.map((item) => {
|
||||
if (typeof item === 'string') {
|
||||
if (shouldSkipSuffix(item)) return item;
|
||||
return item.includes(ERROR_SUPPORT_SUFFIX) ? item : `${item}${ERROR_SUPPORT_SUFFIX}`;
|
||||
}
|
||||
if (item && typeof item === 'object' && typeof item.message === 'string' && !item.message.includes(ERROR_SUPPORT_SUFFIX)) {
|
||||
if (item && typeof item === 'object' && typeof item.message === 'string' && !item.message.includes(ERROR_SUPPORT_SUFFIX) && !shouldSkipSuffix(item.message)) {
|
||||
return { ...item, message: `${item.message}${ERROR_SUPPORT_SUFFIX}` };
|
||||
}
|
||||
return item;
|
||||
|
|
|
|||
|
|
@ -68,12 +68,20 @@ function registerModerationChat() {
|
|||
const telegramId = payload.telegramId;
|
||||
|
||||
if (!username || !telegramId) {
|
||||
log('warn', 'Mod chat auth failed: no username/telegramId', { username, telegramId });
|
||||
socket.emit('unauthorized');
|
||||
return socket.disconnect(true);
|
||||
}
|
||||
|
||||
const allowed = await isModerationAdmin({ username, telegramId });
|
||||
if (!allowed) {
|
||||
// Проверить, является ли владельцем
|
||||
const ownerUsernames = config.moderationOwnerUsernames || [];
|
||||
const isOwner = ownerUsernames.includes(username);
|
||||
|
||||
// Проверить, является ли админом
|
||||
const isAdmin = await isModerationAdmin({ username, telegramId });
|
||||
|
||||
if (!isOwner && !isAdmin) {
|
||||
log('warn', 'Mod chat access denied', { username, telegramId });
|
||||
socket.emit('unauthorized');
|
||||
return socket.disconnect(true);
|
||||
}
|
||||
|
|
@ -81,11 +89,15 @@ function registerModerationChat() {
|
|||
socket.data.authorized = true;
|
||||
socket.data.username = username;
|
||||
socket.data.telegramId = telegramId;
|
||||
socket.data.isOwner = isOwner;
|
||||
|
||||
connectedModerators.set(socket.id, {
|
||||
username,
|
||||
telegramId
|
||||
telegramId,
|
||||
isOwner
|
||||
});
|
||||
|
||||
log('info', 'Mod chat auth success', { username, isOwner, isAdmin });
|
||||
socket.emit('ready');
|
||||
broadcastOnline();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -131,10 +131,7 @@ export default function App() {
|
|||
} finally {
|
||||
if (!cancelled) {
|
||||
setLoading(false);
|
||||
const telegramApp = window.Telegram?.WebApp;
|
||||
telegramApp?.MainButton?.setText?.('Закрыть');
|
||||
telegramApp?.MainButton?.show?.();
|
||||
telegramApp?.MainButton?.onClick?.(() => telegramApp.close());
|
||||
// Убрана кнопка "Закрыть"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -143,7 +140,6 @@ export default function App() {
|
|||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
window.Telegram?.WebApp?.MainButton?.hide?.();
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
@ -645,11 +641,6 @@ export default function App() {
|
|||
<h1>Nakama Moderation</h1>
|
||||
<span className="subtitle">@{user.username}</span>
|
||||
</div>
|
||||
<div className="header-actions">
|
||||
<button className="btn" onClick={() => window.Telegram?.WebApp?.close()}>
|
||||
Закрыть
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav className="tabbar">
|
||||
|
|
|
|||
Loading…
Reference in New Issue