add chat system
This commit is contained in:
352
UI_V2/dbActions/chat.py
Normal file
352
UI_V2/dbActions/chat.py
Normal file
@@ -0,0 +1,352 @@
|
||||
import sqlite3
|
||||
from typing import Optional
|
||||
|
||||
class Chat:
|
||||
def __init__(self, db_path="instance/app_database.db"):
|
||||
self.db_path = db_path
|
||||
self._create_chatsessions_table()
|
||||
self._create_chatmessages_table()
|
||||
|
||||
def _create_chatsessions_table(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS chatsessions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER,
|
||||
session_token TEXT,
|
||||
status TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
last_message_at DATETIME ,
|
||||
last_message_from TEXT,
|
||||
unread_for_admin INTEGER,
|
||||
unread_for_user INTEGER
|
||||
);
|
||||
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
def _create_chatmessages_table(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS chatmessages (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
chat_id INTEGER,
|
||||
session_token TEXT,
|
||||
sender_type TEXT,
|
||||
sender_id INTEGER,
|
||||
text TEXT,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
read_at DATETIME
|
||||
);
|
||||
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
def add_chatsession(self, chatsession):
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
INSERT INTO chatsessions (user_id, session_token, status, last_message_at, last_message_from, unread_for_admin, unread_for_user)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
""", (
|
||||
chatsession['user_id'],
|
||||
chatsession['session_token'],
|
||||
chatsession['status'],
|
||||
chatsession['last_message_at'],
|
||||
chatsession['last_message_from'],
|
||||
chatsession['unread_for_admin'],
|
||||
chatsession['unread_for_user']
|
||||
)
|
||||
)
|
||||
conn.commit()
|
||||
return True
|
||||
except sqlite3.IntegrityError:
|
||||
return False
|
||||
|
||||
def get_chatsession(self, chat_id: int):
|
||||
"""Return a single chat session by its primary key id."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT * FROM chatsessions
|
||||
WHERE id = ?
|
||||
""",
|
||||
(chat_id,),
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return {
|
||||
'id': row[0],
|
||||
'user_id': row[1],
|
||||
'session_token': row[2],
|
||||
'status': row[3],
|
||||
'created_at': row[4],
|
||||
'last_message_at': row[5],
|
||||
'last_message_from': row[6],
|
||||
'unread_for_admin': row[7],
|
||||
'unread_for_user': row[8],
|
||||
}
|
||||
return None
|
||||
|
||||
def get_chatsession_by_token(self, session_token: str) -> Optional[dict]:
|
||||
"""Return the chat session associated with a given browser / user session token."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT * FROM chatsessions
|
||||
WHERE session_token = ?
|
||||
""",
|
||||
(session_token,),
|
||||
)
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return {
|
||||
'id': row[0],
|
||||
'user_id': row[1],
|
||||
'session_token': row[2],
|
||||
'status': row[3],
|
||||
'created_at': row[4],
|
||||
'last_message_at': row[5],
|
||||
'last_message_from': row[6],
|
||||
'unread_for_admin': row[7],
|
||||
'unread_for_user': row[8],
|
||||
}
|
||||
return None
|
||||
|
||||
def get_all_chatsessions(self):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT * FROM chatsessions
|
||||
""")
|
||||
rows = cursor.fetchall()
|
||||
if rows:
|
||||
buffer = []
|
||||
for row in rows:
|
||||
buffer.append(
|
||||
{
|
||||
'id': row[0],
|
||||
'user_id': row[1],
|
||||
'session_token': row[2],
|
||||
'status': row[3],
|
||||
'created_at' : row[4],
|
||||
'last_message_at': row[5],
|
||||
'last_message_from': row[6],
|
||||
'unread_for_admin': row[7],
|
||||
'unread_for_user': row[8],
|
||||
}
|
||||
)
|
||||
return buffer
|
||||
return []
|
||||
|
||||
def update_chatsession(self, chatsession):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
UPDATE chatsessions SET
|
||||
user_id = ?,
|
||||
status = ?,
|
||||
last_message_at = ?,
|
||||
last_message_from = ?,
|
||||
unread_for_admin = ?,
|
||||
unread_for_user = ?
|
||||
WHERE id = ?
|
||||
''', (
|
||||
chatsession['user_id'],
|
||||
chatsession['status'],
|
||||
chatsession['last_message_at'],
|
||||
chatsession['last_message_from'],
|
||||
chatsession['unread_for_admin'],
|
||||
chatsession['unread_for_user'],
|
||||
chatsession['id']
|
||||
)
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def delete(self, id):
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute('''
|
||||
DELETE FROM chatsessions WHERE id=?;
|
||||
''', (id,))
|
||||
conn.commit()
|
||||
|
||||
|
||||
def add_chatmessage(self, message: dict) -> bool:
|
||||
"""Insert a new chat message.
|
||||
|
||||
Expected keys in message dict:
|
||||
- chat_id (int)
|
||||
- session_token (str)
|
||||
- sender_type (str: 'client' or 'admin')
|
||||
- sender_id (int or None)
|
||||
- text (str)
|
||||
"""
|
||||
try:
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO chatmessages (chat_id, session_token, sender_type, sender_id, text)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
message['chat_id'],
|
||||
message.get('session_token'),
|
||||
message['sender_type'],
|
||||
message.get('sender_id'),
|
||||
message['text'],
|
||||
),
|
||||
)
|
||||
conn.commit()
|
||||
# Refresh last_message_at on the corresponding chat session
|
||||
cursor.execute(
|
||||
"""
|
||||
UPDATE chatsessions
|
||||
SET last_message_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
""",
|
||||
(message["chat_id"],),
|
||||
)
|
||||
conn.commit()
|
||||
return True
|
||||
except sqlite3.IntegrityError:
|
||||
return False
|
||||
|
||||
def get_chatmessages_for_chat(self, chat_id: int):
|
||||
"""Return all messages for a given chat, ordered by creation time."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT id, chat_id, session_token, sender_type, sender_id, text, created_at, read_at
|
||||
FROM chatmessages
|
||||
WHERE chat_id = ?
|
||||
ORDER BY created_at ASC, id ASC
|
||||
""",
|
||||
(chat_id,),
|
||||
)
|
||||
rows = cursor.fetchall()
|
||||
messages = []
|
||||
for row in rows:
|
||||
messages.append(
|
||||
{
|
||||
'id': row[0],
|
||||
'chat_id': row[1],
|
||||
'session_token': row[2],
|
||||
'sender_type': row[3],
|
||||
'sender_id': row[4],
|
||||
'text': row[5],
|
||||
'created_at': row[6],
|
||||
'read_at': row[7],
|
||||
}
|
||||
)
|
||||
return messages
|
||||
|
||||
def mark_messages_as_read(self, chat_id: int, reader_type: str):
|
||||
"""Mark messages in a chat as read for a given reader.
|
||||
|
||||
Typically called with reader_type='admin' or 'client'.
|
||||
We mark as read all messages where sender_type != reader_type and read_at IS NULL.
|
||||
"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
UPDATE chatmessages
|
||||
SET read_at = CURRENT_TIMESTAMP
|
||||
WHERE chat_id = ? AND sender_type != ? AND read_at IS NULL
|
||||
""",
|
||||
(chat_id, reader_type),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def delete_chatmessages_for_chat(self, chat_id: int):
|
||||
"""Delete all messages for a given chat (e.g. when cleaning up a closed session)."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
DELETE FROM chatmessages
|
||||
WHERE chat_id = ?
|
||||
""",
|
||||
(chat_id,),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
def cleanup_old_sessions(self):
|
||||
"""Delete chat sessions and their messages older than today.
|
||||
|
||||
Any session whose effective date (last_message_at if set, otherwise created_at)
|
||||
is strictly before today's date will be removed together with its messages.
|
||||
"""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
# Identify sessions to delete: older than today based on last_message_at or created_at
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT id FROM chatsessions
|
||||
WHERE DATE(COALESCE(last_message_at, created_at)) < DATE('now','localtime')
|
||||
"""
|
||||
)
|
||||
rows = cursor.fetchall()
|
||||
if not rows:
|
||||
return
|
||||
|
||||
old_ids = [row[0] for row in rows]
|
||||
|
||||
# Delete messages for these sessions
|
||||
cursor.execute(
|
||||
f"""
|
||||
DELETE FROM chatmessages
|
||||
WHERE chat_id IN ({','.join(['?'] * len(old_ids))})
|
||||
""",
|
||||
old_ids,
|
||||
)
|
||||
|
||||
# Delete the sessions themselves
|
||||
cursor.execute(
|
||||
f"""
|
||||
DELETE FROM chatsessions
|
||||
WHERE id IN ({','.join(['?'] * len(old_ids))})
|
||||
""",
|
||||
old_ids,
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
|
||||
def get_chatsessions_with_messages(self):
|
||||
"""Return only chat sessions that have at least one message."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT cs.id, cs.user_id, cs.session_token, cs.status,
|
||||
cs.created_at, cs.last_message_at, cs.last_message_from,
|
||||
cs.unread_for_admin, cs.unread_for_user
|
||||
FROM chatsessions cs
|
||||
WHERE cs.id IN (SELECT DISTINCT chat_id FROM chatmessages)
|
||||
"""
|
||||
)
|
||||
rows = cursor.fetchall()
|
||||
sessions = []
|
||||
for row in rows:
|
||||
sessions.append(
|
||||
{
|
||||
"id": row[0],
|
||||
"user_id": row[1],
|
||||
"session_token": row[2],
|
||||
"status": row[3],
|
||||
"created_at": row[4],
|
||||
"last_message_at": row[5],
|
||||
"last_message_from": row[6],
|
||||
"unread_for_admin": row[7],
|
||||
"unread_for_user": row[8],
|
||||
}
|
||||
)
|
||||
return sessions
|
||||
@@ -119,6 +119,29 @@ class Users:
|
||||
}
|
||||
return None
|
||||
|
||||
def get_user_by_role(self, role: str) -> Optional[dict]:
|
||||
"""Retrieve user details by username."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT * FROM users
|
||||
WHERE role = ?
|
||||
""", (role,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return {
|
||||
"id": row[0],
|
||||
"email": row[1],
|
||||
"token": row[3],
|
||||
"name":row[4],
|
||||
"phone": row[5],
|
||||
"address": row[6],
|
||||
"created_at": row[7],
|
||||
"status": row[8],
|
||||
"role":row[9]
|
||||
}
|
||||
return None
|
||||
|
||||
def get(self, id: int) -> Optional[dict]:
|
||||
"""Retrieve user details by username."""
|
||||
with sqlite3.connect(self.db_path) as conn:
|
||||
|
||||
Reference in New Issue
Block a user