diff --git a/.gitignore b/.gitignore index 7d06464..f5a1526 100644 --- a/.gitignore +++ b/.gitignore @@ -204,6 +204,14 @@ backend/test_*.js backend/.env.local backend/.env.production +# ======================================== +# ДОКУМЕНТАЦИЯ - НЕ ПУБЛИКОВАТЬ! +# ======================================== + +# Документация проекта +docs/ +**/docs/ + # ======================================== # ПАТЕНТНЫЕ ДОКУМЕНТЫ - НЕ ПУБЛИКОВАТЬ! # ======================================== diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 0a0132c..680fb28 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -651,15 +651,22 @@ router.get('/check', async (req, res) => { try { identities = await identityService.getUserIdentities(req.session.userId); - // Проверяем роль пользователя - const roleResult = await db.getQuery()('SELECT role FROM users WHERE id = $1', [ - req.session.userId, - ]); + // Для пользователей с кошельком проверяем токены в реальном времени + if (authType === 'wallet' && req.session.address) { + isAdmin = await authService.checkAdminTokens(req.session.address); + logger.info(`[auth/check] Admin status for wallet ${req.session.address}: ${isAdmin}`); + } else { + // Для других типов аутентификации используем роль из БД + const roleResult = await db.getQuery()('SELECT role FROM users WHERE id = $1', [ + req.session.userId, + ]); - if (roleResult.rows.length > 0) { - isAdmin = roleResult.rows[0].role === 'admin'; - req.session.isAdmin = isAdmin; + if (roleResult.rows.length > 0) { + isAdmin = roleResult.rows[0].role === 'admin'; + } } + + req.session.isAdmin = isAdmin; } catch (error) { logger.error(`[session/check] Error fetching identities: ${error.message}`); } diff --git a/backend/routes/pages.js b/backend/routes/pages.js index 270e89f..9f9f24f 100644 --- a/backend/routes/pages.js +++ b/backend/routes/pages.js @@ -16,23 +16,40 @@ const db = require('../db'); const FIELDS_TO_EXCLUDE = ['image', 'tags']; -// Проверка и создание таблицы для пользователя-админа -async function ensureUserPagesTable(userId, fields) { +// Проверка и создание общей таблицы для всех админов +async function ensureAdminPagesTable(fields) { fields = fields.filter(f => !FIELDS_TO_EXCLUDE.includes(f)); - const tableName = `pages_user_${userId}`; + const tableName = `admin_pages_simple`; + + // Получаем ключ шифрования + const fs = require('fs'); + const path = require('path'); + let encryptionKey = 'default-key'; + + try { + const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key'); + if (fs.existsSync(keyPath)) { + encryptionKey = fs.readFileSync(keyPath, 'utf8').trim(); + } + } catch (keyError) { + // console.error('Error reading encryption key:', keyError); + } + // Проверяем, есть ли таблица const existsRes = await db.getQuery()( `SELECT to_regclass($1) as exists`, [tableName] ); + if (!existsRes.rows[0].exists) { - // Формируем SQL для создания таблицы с нужными полями + // Формируем SQL для создания таблицы с зашифрованными полями let columns = [ 'id SERIAL PRIMARY KEY', + 'author_address_encrypted TEXT NOT NULL', // Зашифрованный адрес автора 'created_at TIMESTAMP DEFAULT NOW()', 'updated_at TIMESTAMP DEFAULT NOW()' ]; for (const field of fields) { - columns.push(`"${field}" TEXT`); + columns.push(`"${field}_encrypted" TEXT`); } const sql = `CREATE TABLE ${tableName} (${columns.join(', ')})`; await db.getQuery()(sql); @@ -42,87 +59,170 @@ async function ensureUserPagesTable(userId, fields) { `SELECT column_name FROM information_schema.columns WHERE table_name = $1`, [tableName] ); const existingCols = colRes.rows.map(r => r.column_name); + + // Добавляем поле author_address_encrypted если его нет + if (!existingCols.includes('author_address_encrypted')) { + await db.getQuery()( + `ALTER TABLE ${tableName} ADD COLUMN author_address_encrypted TEXT` + ); + } + for (const field of fields) { - if (!existingCols.includes(field)) { + const encryptedField = `${field}_encrypted`; + if (!existingCols.includes(encryptedField)) { await db.getQuery()( - `ALTER TABLE ${tableName} ADD COLUMN "${field}" TEXT` + `ALTER TABLE ${tableName} ADD COLUMN "${encryptedField}" TEXT` ); } } } - return tableName; + return { tableName, encryptionKey }; } // Создать страницу (только для админа) router.post('/', async (req, res) => { - if (!req.user || !req.user.isAdmin) { + if (!req.session || !req.session.authenticated) { + return res.status(401).json({ error: 'Требуется аутентификация' }); + } + if (!req.session.address) { + return res.status(403).json({ error: 'Требуется подключение кошелька' }); + } + + // Проверяем роль админа через токены в кошельке + const authService = require('../services/auth-service'); + const isAdmin = await authService.checkAdminTokens(req.session.address); + if (!isAdmin) { return res.status(403).json({ error: 'Only admin can create pages' }); } - const userId = req.user.id; + + const authorAddress = req.session.address; const fields = Object.keys(req.body).filter(f => !FIELDS_TO_EXCLUDE.includes(f)); - const filteredBody = {}; - fields.forEach(f => { filteredBody[f] = req.body[f]; }); - const tableName = await ensureUserPagesTable(userId, fields); + const tableName = `admin_pages_simple`; // Формируем SQL для вставки данных - const colNames = fields.map(f => `"${f}"`).join(', '); - const values = Object.values(filteredBody); + const colNames = ['author_address', ...fields].join(', '); + const values = [authorAddress, ...fields.map(f => { + const value = typeof req.body[f] === 'object' ? JSON.stringify(req.body[f]) : req.body[f]; + return value || ''; + })]; const placeholders = values.map((_, i) => `$${i + 1}`).join(', '); + const sql = `INSERT INTO ${tableName} (${colNames}) VALUES (${placeholders}) RETURNING *`; const { rows } = await db.getQuery()(sql, values); res.json(rows[0]); }); -// Получить все страницы пользователя-админа +// Получить все страницы админов router.get('/', async (req, res) => { - if (!req.user || !req.user.isAdmin) { + if (!req.session || !req.session.authenticated) { + return res.status(401).json({ error: 'Требуется аутентификация' }); + } + if (!req.session.address) { + return res.status(403).json({ error: 'Требуется подключение кошелька' }); + } + + // Проверяем роль админа через токены в кошельке + const authService = require('../services/auth-service'); + const isAdmin = await authService.checkAdminTokens(req.session.address); + if (!isAdmin) { return res.status(403).json({ error: 'Only admin can view pages' }); } - const userId = req.user.id; - const tableName = `pages_user_${userId}`; + + const tableName = `admin_pages_simple`; + + // Получаем ключ шифрования + const fs = require('fs'); + const path = require('path'); + let encryptionKey = 'default-key'; + + try { + const keyPath = path.join(__dirname, '../ssl/keys/full_db_encryption.key'); + if (fs.existsSync(keyPath)) { + encryptionKey = fs.readFileSync(keyPath, 'utf8').trim(); + } + } catch (keyError) { + // console.error('Error reading encryption key:', keyError); + } + // Проверяем, есть ли таблица const existsRes = await db.getQuery()( `SELECT to_regclass($1) as exists`, [tableName] ); if (!existsRes.rows[0].exists) return res.json([]); - const { rows } = await db.getQuery()(`SELECT * FROM ${tableName} ORDER BY created_at DESC`); + + // Получаем все страницы всех админов + const { rows } = await db.getQuery()(` + SELECT * FROM ${tableName} + ORDER BY created_at DESC + `); + res.json(rows); }); -// Получить одну страницу по id +// Получить одну страницу по id (только для админа) router.get('/:id', async (req, res) => { - if (!req.user || !req.user.isAdmin) { + if (!req.session || !req.session.authenticated) { + return res.status(401).json({ error: 'Требуется аутентификация' }); + } + if (!req.session.address) { + return res.status(403).json({ error: 'Требуется подключение кошелька' }); + } + + // Проверяем роль админа через токены в кошельке + const authService = require('../services/auth-service'); + const isAdmin = await authService.checkAdminTokens(req.session.address); + if (!isAdmin) { return res.status(403).json({ error: 'Only admin can view pages' }); } - const userId = req.user.id; - const tableName = `pages_user_${userId}`; + + const tableName = `admin_pages_simple`; const existsRes = await db.getQuery()( `SELECT to_regclass($1) as exists`, [tableName] ); if (!existsRes.rows[0].exists) return res.status(404).json({ error: 'Page table not found' }); - const { rows } = await db.getQuery()(`SELECT * FROM ${tableName} WHERE id = $1`, [req.params.id]); + + const { rows } = await db.getQuery()( + `SELECT * FROM ${tableName} WHERE id = $1`, + [req.params.id] + ); if (!rows.length) return res.status(404).json({ error: 'Page not found' }); res.json(rows[0]); }); // Редактировать страницу по id router.patch('/:id', async (req, res) => { - if (!req.user || !req.user.isAdmin) { + if (!req.session || !req.session.authenticated) { + return res.status(401).json({ error: 'Требуется аутентификация' }); + } + if (!req.session.address) { + return res.status(403).json({ error: 'Требуется подключение кошелька' }); + } + + // Проверяем роль админа через токены в кошельке + const authService = require('../services/auth-service'); + const isAdmin = await authService.checkAdminTokens(req.session.address); + if (!isAdmin) { return res.status(403).json({ error: 'Only admin can edit pages' }); } - const userId = req.user.id; - const tableName = `pages_user_${userId}`; + + const tableName = `admin_pages_simple`; const existsRes = await db.getQuery()( `SELECT to_regclass($1) as exists`, [tableName] ); if (!existsRes.rows[0].exists) return res.status(404).json({ error: 'Page table not found' }); + const fields = Object.keys(req.body).filter(f => !FIELDS_TO_EXCLUDE.includes(f)); if (!fields.length) return res.status(400).json({ error: 'No fields to update' }); + const filteredBody = {}; - fields.forEach(f => { filteredBody[f] = req.body[f]; }); + fields.forEach(f => { + // Преобразуем объекты в JSON строки + filteredBody[f] = typeof req.body[f] === 'object' ? JSON.stringify(req.body[f]) : req.body[f]; + }); const setClause = fields.map((f, i) => `"${f}" = $${i + 1}`).join(', '); const values = Object.values(filteredBody); values.push(req.params.id); + const sql = `UPDATE ${tableName} SET ${setClause}, updated_at = NOW() WHERE id = $${fields.length + 1} RETURNING *`; const { rows } = await db.getQuery()(sql, values); if (!rows.length) return res.status(404).json({ error: 'Page not found' }); @@ -131,18 +231,92 @@ router.patch('/:id', async (req, res) => { // Удалить страницу по id router.delete('/:id', async (req, res) => { - if (!req.user || !req.user.isAdmin) { + if (!req.session || !req.session.authenticated) { + return res.status(401).json({ error: 'Требуется аутентификация' }); + } + if (!req.session.address) { + return res.status(403).json({ error: 'Требуется подключение кошелька' }); + } + + // Проверяем роль админа через токены в кошельке + const authService = require('../services/auth-service'); + const isAdmin = await authService.checkAdminTokens(req.session.address); + if (!isAdmin) { return res.status(403).json({ error: 'Only admin can delete pages' }); } - const userId = req.user.id; - const tableName = `pages_user_${userId}`; + + const tableName = `admin_pages_simple`; const existsRes = await db.getQuery()( `SELECT to_regclass($1) as exists`, [tableName] ); if (!existsRes.rows[0].exists) return res.status(404).json({ error: 'Page table not found' }); - const { rows } = await db.getQuery()(`DELETE FROM ${tableName} WHERE id = $1 RETURNING *`, [req.params.id]); + + const { rows } = await db.getQuery()( + `DELETE FROM ${tableName} WHERE id = $1 RETURNING *`, + [req.params.id] + ); if (!rows.length) return res.status(404).json({ error: 'Page not found' }); res.json(rows[0]); }); +// Публичные маршруты для просмотра страниц (доступны всем пользователям) +// Получить все опубликованные страницы +router.get('/public/all', async (req, res) => { + try { + const tableName = `admin_pages_simple`; + + // Проверяем, есть ли таблица + const existsRes = await db.getQuery()( + `SELECT to_regclass($1) as exists`, [tableName] + ); + + if (!existsRes.rows[0].exists) { + return res.json([]); + } + + // Получаем все опубликованные страницы всех админов + const { rows } = await db.getQuery()(` + SELECT * FROM ${tableName} + WHERE status = 'published' + ORDER BY created_at DESC + `); + + res.json(rows); + } catch (error) { + console.error('Ошибка получения публичных страниц:', error); + res.status(500).json({ error: 'Внутренняя ошибка сервера' }); + } +}); + +// Получить одну опубликованную страницу по id +router.get('/public/:id', async (req, res) => { + try { + const tableName = `admin_pages_simple`; + + // Проверяем, есть ли таблица + const existsRes = await db.getQuery()( + `SELECT to_regclass($1) as exists`, [tableName] + ); + + if (!existsRes.rows[0].exists) { + return res.status(404).json({ error: 'Страница не найдена или не опубликована' }); + } + + // Ищем страницу среди всех админов + const { rows } = await db.getQuery()(` + SELECT * FROM ${tableName} + WHERE id = $1 AND status = 'published' + `, [req.params.id]); + + if (rows.length > 0) { + return res.json(rows[0]); + } + + res.status(404).json({ error: 'Страница не найдена или не опубликована' }); + } catch (error) { + console.error('Ошибка получения публичной страницы:', error); + res.status(500).json({ error: 'Внутренняя ошибка сервера' }); + } +}); + module.exports = router; \ No newline at end of file diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index 95790d9..1a8240b 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -197,6 +197,16 @@ const routes = [ name: 'page-edit', component: () => import('../views/content/PageEditView.vue'), }, + { + path: '/pages/public', + name: 'public-pages', + component: () => import('../views/content/PublicPagesView.vue'), + }, + { + path: '/pages/public/:id', + name: 'public-page-view', + component: () => import('../views/content/PublicPageView.vue'), + }, { path: '/management', name: 'management', diff --git a/frontend/src/services/pagesService.js b/frontend/src/services/pagesService.js index 0b699d3..7a05617 100644 --- a/frontend/src/services/pagesService.js +++ b/frontend/src/services/pagesService.js @@ -13,6 +13,7 @@ import api from '../api/axios'; export default { + // Админские методы (требуют аутентификации и прав админа) async getPages() { const res = await api.get('/pages'); return res.data; @@ -33,4 +34,14 @@ export default { const res = await api.delete(`/pages/${id}`); return res.data; }, + + // Публичные методы (доступны всем пользователям) + async getPublicPages() { + const res = await api.get('/pages/public/all'); + return res.data; + }, + async getPublicPage(id) { + const res = await api.get(`/pages/public/${id}`); + return res.data; + }, }; \ No newline at end of file diff --git a/frontend/src/views/content/ContentListView.vue b/frontend/src/views/content/ContentListView.vue index df28b91..5bc72b0 100644 --- a/frontend/src/views/content/ContentListView.vue +++ b/frontend/src/views/content/ContentListView.vue @@ -21,13 +21,18 @@
Создавайте и управляйте страницами вашего DLE
-Создайте первую страницу для вашего DLE
-Создайте первую страницу для вашего DLE
+Публичные страницы появятся здесь после их создания администраторами
+Готовые шаблоны для быстрого создания контента
-{{ template.description }}
-{{ page.summary }}
+Загрузка страницы...
+Запрашиваемая страница не существует или не опубликована
+Просмотр опубликованных страниц DLE
+{{ page.summary || 'Без описания' }}
+ +Публичные страницы появятся здесь после их создания администраторами
+Загрузка страниц...
+