diff --git a/backend/migrations/cleanup.sql b/backend/migrations/cleanup.sql
new file mode 100644
index 0000000..a3c99ca
--- /dev/null
+++ b/backend/migrations/cleanup.sql
@@ -0,0 +1,21 @@
+-- Создаем временную таблицу для уникальных адресов
+CREATE TEMP TABLE unique_users AS
+SELECT DISTINCT ON (LOWER(address))
+ id,
+ LOWER(address) as address,
+ created_at
+FROM users
+ORDER BY LOWER(address), created_at ASC;
+
+-- Удаляем все записи из users
+TRUNCATE users CASCADE;
+
+-- Восстанавливаем уникальные записи
+INSERT INTO users (id, address, created_at)
+SELECT id, address, created_at FROM unique_users;
+
+-- Обновляем последовательность id
+SELECT setval('users_id_seq', (SELECT MAX(id) FROM users));
+
+-- Удаляем временную таблицу
+DROP TABLE unique_users;
\ No newline at end of file
diff --git a/backend/migrations/init.sql b/backend/migrations/init.sql
index 074876a..9341fb4 100644
--- a/backend/migrations/init.sql
+++ b/backend/migrations/init.sql
@@ -29,4 +29,12 @@ CREATE TABLE IF NOT EXISTS chat_history (
-- Даем права пользователю
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO postgres;
-GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO postgres;
\ No newline at end of file
+GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO postgres;
+
+-- Обновляем существующие адреса
+UPDATE users SET address = LOWER(address);
+
+-- Удаляем дубликаты
+DELETE FROM users a USING users b
+WHERE a.id > b.id
+AND LOWER(a.address) = LOWER(b.address);
\ No newline at end of file
diff --git a/backend/routes/api.js b/backend/routes/api.js
index 28fbf72..4a25f35 100644
--- a/backend/routes/api.js
+++ b/backend/routes/api.js
@@ -130,7 +130,7 @@ router.post('/chat', requireAuth, async (req, res) => {
// Получаем или создаем пользователя
let user = await pool.query(
- 'INSERT INTO users (address) VALUES ($1) ON CONFLICT (address) DO UPDATE SET address = EXCLUDED.address RETURNING id',
+ 'INSERT INTO users (address) VALUES (LOWER($1)) ON CONFLICT (address) DO UPDATE SET address = LOWER($1) RETURNING id',
[userAddress]
);
const userId = user.rows[0].id;
@@ -166,34 +166,36 @@ router.post('/chat', requireAuth, async (req, res) => {
router.get('/chat/history', requireAuth, async (req, res) => {
try {
const userAddress = req.session.siwe.address;
+ console.log('Запрос истории чата для:', userAddress);
// Получаем ID пользователя
const userResult = await pool.query(
- 'SELECT id FROM users WHERE address = $1',
+ 'SELECT id FROM users WHERE LOWER(address) = LOWER($1) ORDER BY created_at ASC LIMIT 1',
[userAddress]
);
+ console.log('Найден пользователь:', userResult.rows);
+
+ if (userResult.rows.length === 0) {
+ return res.status(404).json({ error: 'User not found' });
+ }
+
const userId = userResult.rows[0].id;
// Получаем историю чата
const history = await pool.query(
- `SELECT * FROM (
- SELECT
- message as content,
- created_at,
- 'user' as role
- FROM chat_history
- WHERE user_id = $1
- UNION ALL
- SELECT
- response as content,
- created_at,
- 'assistant' as role
- FROM chat_history
- WHERE user_id = $1
- ) messages
- ORDER BY created_at ASC`,
+ `SELECT
+ ch.id,
+ LOWER(u.address) as address,
+ ch.message,
+ ch.response,
+ ch.created_at
+ FROM chat_history ch
+ JOIN users u ON ch.user_id = u.id
+ WHERE ch.user_id = $1
+ ORDER BY created_at DESC`,
[userId]
);
+ console.log('История чата:', history.rows);
res.json({
history: history.rows
@@ -204,4 +206,19 @@ router.get('/chat/history', requireAuth, async (req, res) => {
}
});
+// Получение списка пользователей
+router.get('/users', requireAuth, async (req, res) => {
+ try {
+ console.log('Запрос списка пользователей');
+ const users = await pool.query(
+ 'SELECT id, LOWER(address) as address, created_at FROM users ORDER BY created_at DESC'
+ );
+ console.log('Найдено пользователей:', users.rows);
+ res.json({ users: users.rows });
+ } catch (error) {
+ console.error('Ошибка получения пользователей:', error);
+ res.status(500).json({ error: 'Ошибка сервера' });
+ }
+});
+
module.exports = router;
\ No newline at end of file
diff --git a/backend/sessions/MQ7gY14YfnCv8X0bQKUNjZjITzdTWz58.json b/backend/sessions/MQ7gY14YfnCv8X0bQKUNjZjITzdTWz58.json
index e01e81f..2777518 100644
--- a/backend/sessions/MQ7gY14YfnCv8X0bQKUNjZjITzdTWz58.json
+++ b/backend/sessions/MQ7gY14YfnCv8X0bQKUNjZjITzdTWz58.json
@@ -1 +1 @@
-{"cookie":{"originalMaxAge":2591999999,"expires":"2025-03-23T14:05:28.613Z","secure":false,"httpOnly":true,"domain":"127.0.0.1","path":"/","sameSite":"lax"},"nonce":null,"__lastAccess":1740146728614,"siwe":{"domain":"127.0.0.1:5173","address":"0xF45aa4917b3775bA37f48Aeb3dc1a943561e9e0B","statement":"Sign in with Ethereum to access DApp features and AI Assistant","uri":"http://127.0.0.1:5173","version":"1","nonce":"KF6YuwJBLPv8vFKCK","issuedAt":"2025-02-21T14:05:01.684Z","chainId":11155111,"resources":["http://127.0.0.1:5173/api/chat","http://127.0.0.1:5173/api/contract"]},"authenticated":true}
\ No newline at end of file
+{"cookie":{"originalMaxAge":2591999999,"expires":"2025-03-23T16:06:19.831Z","secure":false,"httpOnly":true,"domain":"127.0.0.1","path":"/","sameSite":"lax"},"nonce":null,"__lastAccess":1740153979832,"siwe":{"domain":"127.0.0.1:5173","address":"0xF45aa4917b3775bA37f48Aeb3dc1a943561e9e0B","statement":"Sign in with Ethereum to access DApp features and AI Assistant","uri":"http://127.0.0.1:5173","version":"1","nonce":"u7P3wT2kyPmGb4Z0I","issuedAt":"2025-02-21T16:05:49.561Z","chainId":11155111,"resources":["http://127.0.0.1:5173/api/chat","http://127.0.0.1:5173/api/contract"]},"authenticated":true}
\ No newline at end of file
diff --git a/backend/sessions/OgJhv73wKRJ4xuAAUBa9k5CtZ9E4dcFJ.json b/backend/sessions/OgJhv73wKRJ4xuAAUBa9k5CtZ9E4dcFJ.json
new file mode 100644
index 0000000..b710c12
--- /dev/null
+++ b/backend/sessions/OgJhv73wKRJ4xuAAUBa9k5CtZ9E4dcFJ.json
@@ -0,0 +1 @@
+{"cookie":{"originalMaxAge":2592000000,"expires":"2025-03-23T14:54:20.531Z","secure":false,"httpOnly":true,"domain":"127.0.0.1","path":"/","sameSite":"lax"},"__lastAccess":1740149660531}
\ No newline at end of file
diff --git a/backend/sessions/P6j78qGOxf6uv3j-AGfgu0la3luxKbaV.json b/backend/sessions/P6j78qGOxf6uv3j-AGfgu0la3luxKbaV.json
new file mode 100644
index 0000000..519ec36
--- /dev/null
+++ b/backend/sessions/P6j78qGOxf6uv3j-AGfgu0la3luxKbaV.json
@@ -0,0 +1 @@
+{"cookie":{"originalMaxAge":2592000000,"expires":"2025-03-23T14:53:08.151Z","secure":false,"httpOnly":true,"domain":"127.0.0.1","path":"/","sameSite":"lax"},"__lastAccess":1740149588151}
\ No newline at end of file
diff --git a/backend/sessions/VO2T1gK1S6E0sq7-yYtjHx6p3nsixeGv.json b/backend/sessions/VO2T1gK1S6E0sq7-yYtjHx6p3nsixeGv.json
new file mode 100644
index 0000000..ba2a55a
--- /dev/null
+++ b/backend/sessions/VO2T1gK1S6E0sq7-yYtjHx6p3nsixeGv.json
@@ -0,0 +1 @@
+{"cookie":{"originalMaxAge":2592000000,"expires":"2025-03-23T14:36:28.622Z","secure":false,"httpOnly":true,"domain":"127.0.0.1","path":"/","sameSite":"lax"},"__lastAccess":1740148588623}
\ No newline at end of file
diff --git a/backend/sessions/jK4QzdazTk9W_ubXZTLnIEwgGD2xcwmE.json b/backend/sessions/jK4QzdazTk9W_ubXZTLnIEwgGD2xcwmE.json
new file mode 100644
index 0000000..6c28ad7
--- /dev/null
+++ b/backend/sessions/jK4QzdazTk9W_ubXZTLnIEwgGD2xcwmE.json
@@ -0,0 +1 @@
+{"cookie":{"originalMaxAge":2592000000,"expires":"2025-03-23T14:49:54.299Z","secure":false,"httpOnly":true,"domain":"127.0.0.1","path":"/","sameSite":"lax"},"__lastAccess":1740149394299}
\ No newline at end of file
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index e91305b..7123ab3 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -5,8 +5,14 @@
+
@@ -15,10 +21,12 @@ import { ref, watch } from 'vue'
import ContractInteraction from './components/ContractInteraction.vue'
import AIAssistant from './components/AIAssistant.vue'
import ServerControl from './components/ServerControl.vue'
+import DataTables from './components/DataTables.vue'
const contractInteraction = ref(null)
const isConnected = ref(false)
const userAddress = ref(null)
+const dataTables = ref(null)
watch(() => contractInteraction.value?.isConnected, (newValue) => {
isConnected.value = newValue
@@ -27,6 +35,10 @@ watch(() => contractInteraction.value?.isConnected, (newValue) => {
watch(() => contractInteraction.value?.address, (newValue) => {
userAddress.value = newValue
})
+
+function handleChatUpdate() {
+ dataTables.value?.fetchData()
+}
\ No newline at end of file