messenger finalizat

This commit is contained in:
andrei-mihnea-cerbu
2024-01-13 08:39:13 +02:00
parent a6a228ded9
commit 54542da299
28 changed files with 455 additions and 47 deletions
+9 -1
View File
@@ -8,6 +8,14 @@ async def login(login_info: Login):
if not result: if not result:
raise HTTPException(status_code=404, detail='User not in system.') raise HTTPException(status_code=404, detail='User not in system.')
password, email = result[2], result[3] id, username, password, email = result[0], result[1], result[2], result[3]
if login_info.email != email or login_info.password != password: if login_info.email != email or login_info.password != password:
raise HTTPException(status_code=401, detail="Invalid credentials.") raise HTTPException(status_code=401, detail="Invalid credentials.")
return {
'info': {
'id': id,
'username': username,
'email': email,
}
}
@@ -8,6 +8,12 @@ async def signup(user: User):
if result: if result:
raise HTTPException(status_code=409, detail='User already exists.') raise HTTPException(status_code=409, detail='User already exists.')
print(user.username, user.email, user.password)
create_user_db(username=user.username, password=user.password, email=user.email) create_user_db(username=user.username, password=user.password, email=user.email)
user_info = get_user_by_email(user.email)
return {
'info': {
'id': user_info[0],
'username': user.username,
'email': user.email,
}
}
@@ -1,11 +1,17 @@
from fastapi import APIRouter from fastapi import APIRouter
from .send import send from .send import send
from .get_conversation import get_conversation from .get_conversation import get_conversation
from .get_conversation_url import get_conversation_url
from app.models import Message from app.models import Message
router = APIRouter() router = APIRouter()
@router.get("/get_conversation_url/{id_user1}/{id_user2}")
async def get_conversation_url_router(id_user1: str, id_user2: str):
return await get_conversation_url(id_user1, id_user2)
@router.post("/send") @router.post("/send")
async def send_router(message: Message): async def send_router(message: Message):
return await send(message) return await send(message)
@@ -1,9 +1,9 @@
from fastapi import HTTPException, status from fastapi import HTTPException, status
from database.queries.messages import find_conversation from database.queries.messages import find_conversation_db
async def get_conversation(conversation_url: str): async def get_conversation(conversation_url: str):
conversation = find_conversation(conversation_url) conversation = find_conversation_db(conversation_url)
if conversation is None: if conversation is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Conversation not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Conversation not found")
return conversation return conversation
@@ -0,0 +1,11 @@
from database.queries.messages import get_conversation_url_db
from database.queries.user import get_user_by_id
from fastapi import HTTPException
async def get_conversation_url(id_user1: str, id_user2: str):
if get_user_by_id(id_user1) is None or get_user_by_id(id_user2) is None:
raise HTTPException(status_code=404, detail="Users not in the system.")
conversation_url = get_conversation_url_db(id_user1, id_user2)
return {'conversation_url': conversation_url}
@@ -1,17 +1,12 @@
from fastapi import HTTPException from fastapi import HTTPException
from app.models import Message from app.models import Message
from database.queries.messages import create_conversation, send_message_to_mongodb from database.queries.messages import send_message_db
async def send(message: Message): async def send(message: Message):
conversation_url = create_conversation(message.sender, message.receiver) success = send_message_db(message)
if conversation_url is not None:
success = send_message_to_mongodb(conversation_url, message.sender, message.conversation)
if success: if success:
return {"success": True, "message": "Message sent successfully"} return {"success": True, "message": "Message sent successfully"}
raise HTTPException(status_code=500, detail="Internal Server Error") raise HTTPException(status_code=500, detail="Internal Server Error")
raise HTTPException(status_code=500, detail="Error creating conversation")
@@ -3,10 +3,16 @@ from app.models import User
from .create import create_user from .create import create_user
from .update import update_user from .update import update_user
from .delete import delete_user from .delete import delete_user
from .get_all import get_all_users
router = APIRouter() router = APIRouter()
@router.get("/get_all")
async def get_all_router():
return await get_all_users()
@router.post("/create") @router.post("/create")
async def create_user_router(user: User): async def create_user_router(user: User):
return await create_user(user) return await create_user(user)
@@ -0,0 +1,6 @@
from database.queries.user import get_all_users_db
async def get_all_users():
users = get_all_users_db()
return users
+3 -3
View File
@@ -3,7 +3,7 @@ from datetime import datetime
class Message(BaseModel): class Message(BaseModel):
conversation: str conversation_url: str
message: str
sender: int sender: int
receiver: int message_type: str
timestamp: datetime
+28 -18
View File
@@ -1,19 +1,10 @@
from app.models import Message
from database import postgresql_db_handler, mongo_db_handler from database import postgresql_db_handler, mongo_db_handler
import uuid import uuid
from datetime import datetime from datetime import datetime
def create_conversation(user1_id, user2_id): def create_conversation_db(user1_id, user2_id):
query = '''
SELECT conversation_url FROM conversations
WHERE id_user1 = %s AND id_user2 = %s
'''
params = (user1_id, user2_id)
existing_conversation = postgresql_db_handler.execute_query(query, params, fetchall=True)
if existing_conversation:
conversation_url = existing_conversation[0][0]
else:
current_datetime = datetime.now().strftime("%Y%m%d%H%M%S") current_datetime = datetime.now().strftime("%Y%m%d%H%M%S")
unique_id = str(uuid.uuid4()).replace('-', '') unique_id = str(uuid.uuid4()).replace('-', '')
conversation_url = f"{current_datetime}_{unique_id}" conversation_url = f"{current_datetime}_{unique_id}"
@@ -28,7 +19,7 @@ def create_conversation(user1_id, user2_id):
return conversation_url return conversation_url
def find_conversation(conversation_url): def find_conversation_db(conversation_url):
collection = mongo_db_handler.get_collection() collection = mongo_db_handler.get_collection()
query = {'name': conversation_url} query = {'name': conversation_url}
result_set = collection.find_one(query) result_set = collection.find_one(query)
@@ -38,21 +29,40 @@ def find_conversation(conversation_url):
return None return None
def send_message_to_mongodb(conversation_url, sender, message): def get_conversation_url_db(id_user1, id_user2):
query = """
SELECT * FROM conversations
WHERE
(id_user1 = %s AND id_user2 = %s)
OR
(id_user1 = %s AND id_user2 = %s)
"""
params = (id_user1, id_user2, id_user2, id_user1)
result = postgresql_db_handler.execute_query(query, params, fetchall=True)
if not result:
return create_conversation_db(id_user1, id_user2)
else:
return result[0][2]
def send_message_db(message: Message):
try: try:
collection = mongo_db_handler.get_collection() collection = mongo_db_handler.get_collection()
existing_document = collection.find_one({"name": conversation_url}) existing_document = collection.find_one({"name": message.conversation_url})
if existing_document is not None: if existing_document is not None:
new_message = {"sender": sender, "message": message, "timestamp": datetime.now()} new_message = {"sender": message.sender, "message": message.message,
"message_type": message.message_type, "timestamp": datetime.now()}
collection.update_one( collection.update_one(
{"name": conversation_url}, {"name": message.conversation_url},
{"$push": {"chat": new_message}} {"$push": {"chat": new_message}}
) )
else: else:
collection.insert_one({ collection.insert_one({
"name": conversation_url, "name": message.conversation_url,
"chat": [{"sender": sender, "message": message, "timestamp": datetime.now()}] "chat": [{"sender": message.sender, "message": message.message,
"message_type": message.message_type, "timestamp": datetime.now()}]
}) })
return True return True
+6
View File
@@ -54,3 +54,9 @@ def delete_user_db(user_id):
params = (user_id,) params = (user_id,)
postgresql_db_handler.execute_query(query, params) postgresql_db_handler.execute_query(query, params)
print(f"User with ID {user_id} deleted successfully.") print(f"User with ID {user_id} deleted successfully.")
def get_all_users_db():
query = "SELECT * FROM users;"
users = postgresql_db_handler.execute_query(query, fetchall=True)
return {"users": users}
+21 -1
View File
@@ -1,7 +1,10 @@
import json
import tkinter as tk import tkinter as tk
from PIL import Image, ImageTk
from app.views.login import LoginView from app.views.login import LoginView
from app.views.initial import InitialView from app.views.initial import InitialView
from app.views.signup import SignupView from app.views.signup import SignupView
from app.views.main_page import MainPageView
class MainApplication(tk.Tk): class MainApplication(tk.Tk):
@@ -12,8 +15,13 @@ class MainApplication(tk.Tk):
screen_height = self.winfo_screenheight() screen_height = self.winfo_screenheight()
self.geometry(f"{screen_height // 2}x{screen_height // 2}") self.geometry(f"{screen_height // 2}x{screen_height // 2}")
self.current_view = None
icon_path = "/home/andrei/Documents/messenger/messenger_gui/resources/message-icon-png-17.png"
self.iconphoto(True, tk.PhotoImage(file=icon_path))
self.current_logged_user = {}
self.current_view = None
self.change_to_initial_view() self.change_to_initial_view()
def change_to_initial_view(self): def change_to_initial_view(self):
@@ -25,6 +33,12 @@ class MainApplication(tk.Tk):
def change_to_login_view(self): def change_to_login_view(self):
self.change_view(LoginView(self)) self.change_view(LoginView(self))
def change_to_main_page_view(self):
self.change_view(MainPageView(self))
def change_to_profile_view(self):
pass
def change_view(self, view: tk.Frame): def change_view(self, view: tk.Frame):
if self.current_view: if self.current_view:
self.current_view.destroy() self.current_view.destroy()
@@ -33,5 +47,11 @@ class MainApplication(tk.Tk):
self.current_view.pack(expand=True, fill=tk.BOTH) self.current_view.pack(expand=True, fill=tk.BOTH)
self.current_view.place(relx=0.5, rely=0.5, anchor=tk.CENTER) self.current_view.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
def get_user_information(self, credentials):
data = json.loads(credentials)
self.current_logged_user['id'] = data['info']['id']
self.current_logged_user['username'] = data['info']['username']
self.current_logged_user['email'] = data['info']['email']
+125
View File
@@ -0,0 +1,125 @@
import json
import tkinter as tk
from app.http import request_builder
from app.views.message import MessageView
class ChatWindow(tk.Toplevel):
def __init__(self, master=None, user_data=None, current_logged_user=None):
super().__init__(master)
self.user_data = user_data
self.current_logged_user = current_logged_user
self.define_get_conversation_url_params()
self.geometry("700x700")
self.conversation_url = self.get_conversation_url()
self.define_get_conversation_params()
self.create_widgets(self.get_conversation())
self.after(1000, self.update_chat)
def define_get_conversation_url_params(self):
self.url = (f"http://localhost:8000/messages/get_conversation_url/"
f"{self.user_data['id']}/{self.current_logged_user['id']}")
self.headers = {}
self.method = "GET"
self.params = {}
self.body = None
def define_get_conversation_params(self):
self.url = f"http://localhost:8000/messages/get_conversation/{self.conversation_url}"
self.headers = {}
self.method = "GET"
self.params = {}
self.body = None
def define_send_message_params(self, message, message_type):
self.url = "http://localhost:8000/messages/send"
self.headers = {"Content-Type": "application/json"}
self.method = "POST"
self.params = {}
self.body = {
'conversation_url': self.conversation_url,
'message': message,
'message_type': message_type,
'sender': self.current_logged_user['id']
}
def create_widgets(self, conversation):
# Create a frame for displaying messages
self.messages_frame = tk.Frame(self)
self.messages_frame.pack()
# Display existing conversation
for message in conversation:
message_view = MessageView(self.messages_frame, message, self.current_logged_user, self.user_data)
message_view.pack()
# Create a frame for the text box and submit button
self.input_frame = tk.Frame(self)
self.input_frame.pack(side=tk.BOTTOM, fill=tk.X)
# Create an entry widget for typing messages
self.entry_widget = tk.Entry(self.input_frame)
self.entry_widget.pack(side=tk.LEFT, expand=True, fill=tk.X)
# Create a button to send messages
self.send_button = tk.Button(self.input_frame, text="Send", command=lambda: self.send_message())
self.send_button.pack(side=tk.RIGHT)
def update_chat(self):
self.messages_frame.destroy()
self.messages_frame = tk.Frame(self)
self.messages_frame.pack()
self.define_get_conversation_params()
for message in self.get_conversation():
message_view = MessageView(self.messages_frame, message, self.current_logged_user, self.user_data)
message_view.pack()
self.after(2000, self.update_chat)
def send_message_image(self):
pass
def send_message(self):
message = self.entry_widget.get()
self.define_send_message_params(message, "text")
if not message:
return
request_builder.set_url(self.url)
request_builder.set_body(self.body)
request_builder.set_headers(self.headers)
request_builder.set_method(self.method)
request_builder.set_params(self.params)
request_builder.build().make_request()
self.update_chat()
def get_conversation_url(self):
request_builder.set_url(self.url)
request_builder.set_body(self.body)
request_builder.set_headers(self.headers)
request_builder.set_method(self.method)
request_builder.set_params(self.params)
json_string = request_builder.build().make_request().text
data = json.loads(json_string)
return data['conversation_url']
def get_conversation(self):
request_builder.set_url(self.url)
request_builder.set_body(self.body)
request_builder.set_headers(self.headers)
request_builder.set_method(self.method)
request_builder.set_params(self.params)
json_string = request_builder.build().make_request().text
data = json.loads(json_string)
return data.get('chat', [])
+2 -1
View File
@@ -94,7 +94,8 @@ class LoginView(tk.Frame):
result = request_builder.build().make_request() result = request_builder.build().make_request()
if result.status_code == 200: if result.status_code == 200:
self.master.change_to_initial_view() self.master.get_user_information(result.text)
self.master.change_to_main_page_view()
else: else:
json_data = json.loads(result.text) json_data = json.loads(result.text)
detail_value = json_data.get('detail') detail_value = json_data.get('detail')
+74
View File
@@ -0,0 +1,74 @@
import json
import tkinter as tk
from app.http import request_builder
from app.views.chat import ChatWindow
from app.views.user_frame import UserFrame
class MainPageView(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.form_frame = None
self.define_http_params()
self.create_widgets()
def define_http_params(self):
self.url = "http://localhost:8000/users/get_all"
self.headers = {"Content-Type": "application/json"}
self.method = "GET"
self.params = None
self.body = None
def create_widgets(self):
# Create a main frame to center the user frame and button frame
main_frame = tk.Frame(self.master)
main_frame.pack(expand=True)
# Create a frame to hold the user frames
user_frame_container = tk.Frame(main_frame)
user_frame_container.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
users_data = self.get_user_list()
users_data = [user for user in users_data if user[1] != self.master.current_logged_user['username']]
# Display users dynamically with two users per row
for i in range(0, len(users_data)):
user_data_slice = users_data[i]
# Calculate the row and column for the UserFrame in the grid
row = i // 2
column = i % 2
user_frame_instance = UserFrame(user_frame_container, user_data_slice, self.master.current_logged_user,
self.open_chat)
user_frame_instance.grid(row=row, column=column, padx=10, pady=10)
# Create a frame to hold the buttons
button_frame = tk.Frame(main_frame)
button_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)
# Create buttons in the button frame
logout_button = tk.Button(button_frame, text="Logout", command=self.master.change_to_initial_view)
# Pack buttons in the button frame
logout_button.pack(padx=5, pady=5)
def return_to_initial_view(self):
self.master.change_to_initial_view()
def open_chat(self, user_data):
# Pass both the clicked user data and the current logged user info to ChatWindow
chat_window = ChatWindow(self, user_data, self.master.current_logged_user)
chat_window.mainloop() # Main loop for the new window
def get_user_list(self):
request_builder.set_url(self.url)
request_builder.set_headers(self.headers)
request_builder.set_method(self.method)
request_builder.set_body(self.body)
request_builder.set_params(self.params)
json_string = request_builder.build().make_request().text
data = json.loads(json_string)
return data.get('users', [])
+72
View File
@@ -0,0 +1,72 @@
import tkinter as tk
import emoji
from PIL import Image, ImageTk
import io
import base64
class MessageView(tk.Frame):
def __init__(self, master, message, current_logged_user, user_data, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.current_logged_user = current_logged_user
self.user_data = user_data
self.create_widgets(message)
def create_widgets(self, message):
is_sent_by_current_user = 1
sender_name = None
if is_sent_by_current_user:
sender_name = self.current_logged_user['username']
else:
sender_name = self.user_data['username']
message_frame = tk.Frame(self) # Set a background color for the message frame
message_label = None
image_label = None
pack = None
match message['message_type']:
case 'text':
decoded_message = self.decode_emoji(f"sender: {sender_name}\nmessage: {message['message']}")
pack = tk.Label(message_frame, text=decoded_message, bg='white')
case 'image':
decoded_image = self.decode_image(message['message'])
pack = tk.Label(message_frame, image=decoded_image)
pack.image = decoded_image # Keep a reference to prevent garbage collection
pack.pack()
if is_sent_by_current_user:
alignment = 'right'
else:
alignment = 'left'
message_frame.pack(side=alignment, padx=5, pady=5)
def decode_emoji(self, message):
emoji_mapping = {
':)': '😊',
':(': '☹️',
':D': '😃',
':P': '😛',
';)': '😉',
'XD': '😆',
'<3': '❤️',
'O:)': '😇',
':/': '😕',
':*': '😘',
'8)': '😎',
}
for emoji_string, unicode_character in emoji_mapping.items():
message = message.replace(emoji_string, unicode_character)
return message
def decode_image(self, encoded_image):
# Assume encoded_image is a base64-encoded image
decoded_image = base64.b64decode(encoded_image)
image = Image.open(io.BytesIO(decoded_image))
image = ImageTk.PhotoImage(image)
return image
+1
View File
@@ -103,6 +103,7 @@ class SignupView(tk.Frame):
result = request_builder.build().make_request() result = request_builder.build().make_request()
if result.status_code == 200: if result.status_code == 200:
self.master.get_user_information(result.text)
self.master.change_to_initial_view() self.master.change_to_initial_view()
else: else:
json_data = json.loads(result.text) json_data = json.loads(result.text)
+61
View File
@@ -0,0 +1,61 @@
import tkinter as tk
from PIL import Image, ImageTk
class UserFrame(tk.Frame):
def __init__(self, master, user_data, current_logged_user, click_callback, *args, **kwargs):
super().__init__(master, *args, **kwargs)
self.current_logged_user = current_logged_user
print(user_data)
self.user_data = {
'id': user_data[0],
'username': user_data[1],
'email': user_data[3]
}
self.click_callback = click_callback
# Create widgets
self.create_widgets()
def create_widgets(self):
user_frame = tk.Frame(self, bg='white') # Set a background color for the user frame
# Load the photo (replace "path/to/photo.png" with the actual path)
photo_path = "/home/andrei/Documents/messenger/messenger_gui/resources/9131529.png"
user_photo = self.load_image_with_alpha(photo_path, (50, 50))
# Create widgets for each user
photo_label = tk.Label(user_frame, image=user_photo)
div_frame = tk.Frame(user_frame, bg='white') # Set a background color for the div frame
name_label = tk.Label(div_frame, text=self.user_data['username'], anchor="e", bg='white') # Align to the right
email_label = tk.Label(div_frame, text=self.user_data['email'], anchor="e", bg='white') # Align to the right
# Keep a reference to the PhotoImage to prevent it from being garbage collected
photo_label.image = user_photo
# Grid layout
photo_label.grid(row=0, column=0, padx=5, pady=5)
div_frame.grid(row=0, column=1, padx=5, pady=5)
name_label.grid(row=0, column=0, padx=5, pady=5, sticky="e")
email_label.grid(row=1, column=0, padx=5, pady=5, sticky="e")
# Bind the click event to the user frame
user_frame.bind("<Button-1>", lambda event, user_data=self.user_data, current_logged_user=self.current_logged_user: self.on_user_click(user_data, current_logged_user))
# Pack the user frame
user_frame.pack(side=tk.LEFT, padx=10, pady=10)
def on_user_click(self, user_data, current_logged_user):
# Call the click_callback with the clicked user data
if self.click_callback:
self.click_callback(user_data)
def load_image_with_alpha(self, path, size):
image = Image.open(path).convert("RGBA").resize(size)
image_without_alpha = Image.new("RGB", image.size, (255, 255, 255))
image_without_alpha.paste(image, mask=image.split()[3])
return ImageTk.PhotoImage(image_without_alpha)
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB