From 54542da29939ed60eac6cd7be32e6390f3b23f53 Mon Sep 17 00:00:00 2001 From: andrei-mihnea-cerbu Date: Sat, 13 Jan 2024 08:39:13 +0200 Subject: [PATCH] messenger finalizat --- messenger_app/api/v1/endpoints/auth/login.py | 10 +- messenger_app/api/v1/endpoints/auth/signup.py | 10 +- .../api/v1/endpoints/messages/__init__.py | 6 + .../__pycache__/__init__.cpython-310.pyc | Bin 731 -> 1008 bytes .../get_conversation.cpython-310.pyc | Bin 589 -> 592 bytes .../messages/__pycache__/send.cpython-310.pyc | Bin 758 -> 588 bytes .../v1/endpoints/messages/get_conversation.py | 4 +- .../messages/get_conversation_url.py | 11 ++ .../api/v1/endpoints/messages/send.py | 15 +-- .../api/v1/endpoints/users/__init__.py | 6 + .../__pycache__/__init__.cpython-310.pyc | Bin 884 -> 1079 bytes .../api/v1/endpoints/users/get_all.py | 6 + .../__pycache__/message.cpython-310.pyc | Bin 507 -> 508 bytes messenger_app/app/models/message.py | 6 +- .../__pycache__/messages.cpython-310.pyc | Bin 1745 -> 2081 bytes .../queries/__pycache__/user.cpython-310.pyc | Bin 1538 -> 1717 bytes messenger_app/database/queries/messages.py | 62 +++++---- messenger_app/database/queries/user.py | 6 + messenger_gui/app/main.py | 22 ++- messenger_gui/app/views/chat.py | 125 ++++++++++++++++++ messenger_gui/app/views/login.py | 5 +- messenger_gui/app/views/main_page.py | 74 +++++++++++ messenger_gui/app/views/message.py | 72 ++++++++++ messenger_gui/app/views/signup.py | 1 + messenger_gui/app/views/user_frame.py | 61 +++++++++ messenger_gui/resources/9131529.png | Bin 0 -> 21886 bytes messenger_gui/resources/background_image.png | Bin 0 -> 1551 bytes .../resources/message-icon-png-17.png | Bin 0 -> 21725 bytes 28 files changed, 455 insertions(+), 47 deletions(-) create mode 100644 messenger_app/api/v1/endpoints/messages/get_conversation_url.py create mode 100644 messenger_app/api/v1/endpoints/users/get_all.py create mode 100644 messenger_gui/app/views/chat.py create mode 100644 messenger_gui/app/views/main_page.py create mode 100644 messenger_gui/app/views/message.py create mode 100644 messenger_gui/app/views/user_frame.py create mode 100644 messenger_gui/resources/9131529.png create mode 100644 messenger_gui/resources/background_image.png create mode 100644 messenger_gui/resources/message-icon-png-17.png diff --git a/messenger_app/api/v1/endpoints/auth/login.py b/messenger_app/api/v1/endpoints/auth/login.py index 21f1d42..8a9bff0 100644 --- a/messenger_app/api/v1/endpoints/auth/login.py +++ b/messenger_app/api/v1/endpoints/auth/login.py @@ -8,6 +8,14 @@ async def login(login_info: Login): if not result: 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: raise HTTPException(status_code=401, detail="Invalid credentials.") + + return { + 'info': { + 'id': id, + 'username': username, + 'email': email, + } + } diff --git a/messenger_app/api/v1/endpoints/auth/signup.py b/messenger_app/api/v1/endpoints/auth/signup.py index 67b529c..f021b67 100644 --- a/messenger_app/api/v1/endpoints/auth/signup.py +++ b/messenger_app/api/v1/endpoints/auth/signup.py @@ -8,6 +8,12 @@ async def signup(user: User): if result: 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) + user_info = get_user_by_email(user.email) + return { + 'info': { + 'id': user_info[0], + 'username': user.username, + 'email': user.email, + } + } diff --git a/messenger_app/api/v1/endpoints/messages/__init__.py b/messenger_app/api/v1/endpoints/messages/__init__.py index f0a88c7..6bc83ab 100644 --- a/messenger_app/api/v1/endpoints/messages/__init__.py +++ b/messenger_app/api/v1/endpoints/messages/__init__.py @@ -1,11 +1,17 @@ from fastapi import APIRouter from .send import send from .get_conversation import get_conversation +from .get_conversation_url import get_conversation_url from app.models import Message 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") async def send_router(message: Message): return await send(message) diff --git a/messenger_app/api/v1/endpoints/messages/__pycache__/__init__.cpython-310.pyc b/messenger_app/api/v1/endpoints/messages/__pycache__/__init__.cpython-310.pyc index 88d817c5e77796204430917b0e9f69d7fd08c7e9..ed97ff21ef5aad7aa0f8282696b55fc5249c014b 100644 GIT binary patch literal 1008 zcmZ`&J#X7E5G5&DlE0P2Nq~0kViX9G%tg@x9h#v*(R9*62qx_c(1$_F0pi3!3-?EK zY`YZs9sL(tJ7vz+sdrTE8cs@q$H&Lh@$Q{ebh|AAW6=MuelS9Q;bwC*VDkc|`2t1~ zNmG*1h*Bb%bW)bFh*|8WPUb|8#f{X>+{gt?q?a}_FY+i+bV(v#`CkL+f1@%`P1#h< zu`64${n=g8s0C+j*@3gJ^2SW|WZ&`~`%Vb_4)`;|@Au2w>jeC}-v4r4Va`BCiHwtP$(GL+`r2C0$jg zyFh9TIHY<9F&0*vKA@_N!T(;4#9e)PkLf+w&7a)s2EW?uD0XjAh1=ABlhoLRIw=nj zB(;ykR#J!e6spx3pszItfGV$M&xZZVAIGKy`8BGc@ek5InkM?Spf^WZAysNL3ak&2 zb>Nc9F{QRLSMIa`j74i3+!rEFQz15Tvsqfzf@ctJma6C20P_e;hjeKDcr0)RG;l*Z GTkanp;rDm| delta 409 zcmYjN%Sr<=6wS>%GM!h)$4)`qh(kdaZl(AG?MB?pW*A6XgsC$m)3rkVfii2suB3ZE z!fzOtet?2M;JqzKZ^%i`xi{w~`4OMapo$`&VSK-=)H^WtMPxT@BsVxsy&Rm6m~={e z=41{q1q+tB%6%2mU4azJnQ-aJ-m|@c%tO5=ebfWxqaMm=qX#mcLKe#YElZQ-$5oI* zCq~McVyfCpK0H{%SX0XsbPty`(_#At*->EBgUod`Z`3C7OGW%C<~nZI{O*(^EO=J`(R%_SnLJ@c;7YF?@#{xma(2q6oBhRZ!G0nbjrxBmcQ C<5aW& diff --git a/messenger_app/api/v1/endpoints/messages/__pycache__/get_conversation.cpython-310.pyc b/messenger_app/api/v1/endpoints/messages/__pycache__/get_conversation.cpython-310.pyc index 126f33aa5333babfc3a6fc73f35e9fbba7ed7e90..e1e254ce8d5f921845f27a8ae9919277d8faa8c3 100644 GIT binary patch delta 47 zcmX@ha)E_6pO=@50SLZ|EK1Ft$Xg^Tl9rj55}%x(SC(2-oLG{XpBJB!v~l7dMgUdc B5IX<> delta 44 ycmcb>a+ZZRpO=@50SHdbUYMFbk+(=lFfB7LB|bSnuPn8wII$!%KX2odJ&XV`B@bu- diff --git a/messenger_app/api/v1/endpoints/messages/__pycache__/send.cpython-310.pyc b/messenger_app/api/v1/endpoints/messages/__pycache__/send.cpython-310.pyc index 2ab03f28a4b8ead9479f06243834e6e8ba959327..9fa2b1fafc2095bb76f4e0de0cd6cbada52d3f05 100644 GIT binary patch delta 265 zcmeyydWMBBpO=@50SJDIEJ{sdoX97`Xf{z>Re+Hpg(a0ajVXmSg{_4pifv+wMm*y! z{^HcUl=$4#;^M^g)cBO7TkKGFG9$KN#L?dGfLk&YSV=ZG1V-ZgZ zV+vCW^BksNh7!gU7LWzKOihfM?30ZcH5pka$1s}7XfhRn3@8Gb1Z1;<*la*zB|{PC zWG1ErF_4p)8aNnPn79~u7>l@pTtA=*bxaP%Aa)TaDgjb{i^C>2KczG$)ehv=Vh$j| L!@vX7EyN4}D7Y}9 delta 429 zcmYk1J5Iwu5QaVL^+Qo&%tJy-QiKA5g+w|EDjM1dM3=@G?-GKrR~kDM$WegMrm$Qg z(NJ;@S}uS&05_nZV%MbbNdL^}n_11jAN0p?M61=%WPClp@&(DAE*$22+Z&NNG(lsO z>z9NX%zUClD?i_zk#apm9;Dn4MSROK3DU8M%guzx%#V1I1S9UJ!jD8e66~V1mMjEy zWEr}wRO($S8OZbisg`NiAVub63K=kM0=SjxnPN(Gu1`s}L$Jeu5vIS8Gc7e3Rpu7t z5vtz$?>#&J_P{Jw;b3;xIYt!Nt6Y!cQLpaXGg0{{O=X{BX(JE$Sbhr)g`4$VBocW# znrh#;D*~KRuwbiu> QRPSqPrPwEJ>cX1-2WZ7@U;qFB diff --git a/messenger_app/api/v1/endpoints/messages/get_conversation.py b/messenger_app/api/v1/endpoints/messages/get_conversation.py index 1a76144..032536b 100644 --- a/messenger_app/api/v1/endpoints/messages/get_conversation.py +++ b/messenger_app/api/v1/endpoints/messages/get_conversation.py @@ -1,9 +1,9 @@ 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): - conversation = find_conversation(conversation_url) + conversation = find_conversation_db(conversation_url) if conversation is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Conversation not found") return conversation diff --git a/messenger_app/api/v1/endpoints/messages/get_conversation_url.py b/messenger_app/api/v1/endpoints/messages/get_conversation_url.py new file mode 100644 index 0000000..7bead34 --- /dev/null +++ b/messenger_app/api/v1/endpoints/messages/get_conversation_url.py @@ -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} diff --git a/messenger_app/api/v1/endpoints/messages/send.py b/messenger_app/api/v1/endpoints/messages/send.py index beea570..023914a 100644 --- a/messenger_app/api/v1/endpoints/messages/send.py +++ b/messenger_app/api/v1/endpoints/messages/send.py @@ -1,17 +1,12 @@ from fastapi import HTTPException 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): - 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: - return {"success": True, "message": "Message sent successfully"} - raise HTTPException(status_code=500, detail="Internal Server Error") - - raise HTTPException(status_code=500, detail="Error creating conversation") + if success: + return {"success": True, "message": "Message sent successfully"} + raise HTTPException(status_code=500, detail="Internal Server Error") diff --git a/messenger_app/api/v1/endpoints/users/__init__.py b/messenger_app/api/v1/endpoints/users/__init__.py index 2c4a38e..38f3693 100644 --- a/messenger_app/api/v1/endpoints/users/__init__.py +++ b/messenger_app/api/v1/endpoints/users/__init__.py @@ -3,10 +3,16 @@ from app.models import User from .create import create_user from .update import update_user from .delete import delete_user +from .get_all import get_all_users router = APIRouter() +@router.get("/get_all") +async def get_all_router(): + return await get_all_users() + + @router.post("/create") async def create_user_router(user: User): return await create_user(user) diff --git a/messenger_app/api/v1/endpoints/users/__pycache__/__init__.cpython-310.pyc b/messenger_app/api/v1/endpoints/users/__pycache__/__init__.cpython-310.pyc index 4c27a3cf055bc3349d0613254ff31c1d4b412b25..65c93d69948ecb810492289c87822e01f285e5e5 100644 GIT binary patch literal 1079 zcma)5JCD;q5Z<+&*m0c4<#h)YZJM*_5dw*VYfiw4#*Gza_ktxmwq|W4P86Wr58=wR z_ZRpV+ae)deH|4u>m+9(kl1KP-+VjXZyuYx*Yg>!&CeToYcckNoaS=Sd4XHqq7h8+ zlw~~PoUoNznH|}PZQ-O&){0sNx6*dzMy|o_)XO?i$6z=0vu@PoO!5tj0vUYk3GarB zj_|M9hDUv52BM2hAni*_^hE#KL4Jt*kr*8Kp*S-92zgJ8kRQu2#!tl5@Dnjx@n|ZJ zFWCI#4)bPnt~)Q^oW0AdQUW^8dnMtHyppsGwoh3REYxvYN+;ce5YBC7bvhzWZfue8>cP>Jmb_VK>tzg z?#2lLdhm!S8!0nM?G(8xO-Hp|R3%W++Qx?gCK)$2k2Y>J&a}`B%6o>{RH=MUGgnX0 T1T5hDU)%C*pL@>mu>IC=t&;YN delta 454 zcmZ9Gy-LJD6oqq>>?WJdfYCK1A;#z0a8SLU*57atFJVL- z*%zoUFOB^A5iYi(OXA~#r{#=ON_kmulJk9<*dn0-Db|wFsiw6GNE+|&1pU> zD3ql!EencObu%eQ{jt(?oMmbHYwA@?d;d1aGUJCJKFv0SxS9*i?&`J?fNeI%>HGkC CRb|-# diff --git a/messenger_app/api/v1/endpoints/users/get_all.py b/messenger_app/api/v1/endpoints/users/get_all.py new file mode 100644 index 0000000..d880495 --- /dev/null +++ b/messenger_app/api/v1/endpoints/users/get_all.py @@ -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 \ No newline at end of file diff --git a/messenger_app/app/models/__pycache__/message.cpython-310.pyc b/messenger_app/app/models/__pycache__/message.cpython-310.pyc index 9f35ac203204d55e351b9d50d4717a85c3b4a63d..ba8040aa168c03d9dea00229efbf08d3789e5895 100644 GIT binary patch delta 119 zcmey({D+x0pO=@50SLYcFG^iCk#}Acb1F*;lMO>EE0AUe(jY#I4MPfRFoP!BEq343 z;^M^g)LR0{`FUljMa79FnfZC~rA0Zn*mI#$Y{jX0DXB%bc%TCDC6xuKewu6(|JzS? HX50Y)Uh*g% delta 120 zcmeyv{F|9KpO=@50SM-M`hCL4xS5X}sv*?=^Q4MPfRFoP!BEq343 z;^M^g)LT5s`FUljMa79FnfZCQ*oss0Qc{a*@bZj0DfyFVgLXD diff --git a/messenger_app/app/models/message.py b/messenger_app/app/models/message.py index 994ee8d..f208f49 100644 --- a/messenger_app/app/models/message.py +++ b/messenger_app/app/models/message.py @@ -3,7 +3,7 @@ from datetime import datetime class Message(BaseModel): - conversation: str + conversation_url: str + message: str sender: int - receiver: int - timestamp: datetime + message_type: str diff --git a/messenger_app/database/queries/__pycache__/messages.cpython-310.pyc b/messenger_app/database/queries/__pycache__/messages.cpython-310.pyc index d76d219e28f1ecfa7a60cf00d374349dc4d44848..f3f1091bdca443629a0e761dbe0016514c35adba 100644 GIT binary patch literal 2081 zcmbtV&2Aev5MFY*tCcJ(PT(YU|FA%-Hd{DxWAqfXMbktxlc>~ruUl3%6@+Mz=%K8oN_ME8o0^&OnF|`e$R5BD1H5bPtrH4|e$63s`fFjIU-Y^ zQ|=zqiCfWUkU`}>bAERxjC&KWaw<}>9e3jE+BB%V%0H&WsTkba?;bYWkJ@>gx9_$$ z+P#@Tld4ksQ6h(mFRR-sPIb4<5CHEeKkDM)>qG%#HGw4 z8|0^(_a1b5;gz;p4QF$8BHBe?qm=I1XCj~52 zWJ)UvsBkM+UgWfLIbDI19v~em?RE_-5_n%W*iCfgN(^XPL!seHStbTpR}<`{1&>FC zh?rbPgT~uKdC_hFoXbqdN@)8^Gw~iCEnDYz61)T_stF<>0ck*LkRFjCY@7i)A_1pO z?*=FwQ$pZEdOK|5YG;pBHYx7@2EeYEx~#3m5$yq(Ia`5tUBOfV)PGszA%t=CI`=xa zw!$yMJDc}6&Y)DCv3q#8v)MVbgWhkT9)1J(4R3aDzk$5nnV}gi=i~n7{|K^$WUwPN zlqt(3Lf{250#ppkFVCSsbVQyB=1urUTen4$_~^!;RqEX;U7nTvZ8KvB01`v+v4vu! zSzDqEiq)j@GmKP2!~>Ae01~T()N3pDHz`_NnDL>%xsdX6_%zLv>|=c}7N(9Q zrF4>y54}&vrP`GrpbxdKR^K}O7cY^3VlVrznO)f{e}TPZ zw!i(?!7JZy(F;Ik!LRg{T*u2SmKcmlrw literal 1745 zcmZ8h-ESL35Z~SV@cBq0w-C|rc~C5JQm946144i(jjJLWSBhP#=7H12yKxSk@7%pT zl33>oDZKI*w3X~f_>=aP2gD!1OOYaG)^Xyn((TR8%+AioZzf+|^$Dz%moxEKjgUX_ zu)G)?zJag6jT25&GNgNS$o3e;GfS;K3;tGW5A8i0cs6%_C3_Bcc@6$9_jn!tHO{&u z^#2AcBxHJhlox6sMe#I^_pO$d@>skZO;|N~mNg`smDJc|bVu-G#4s z1{Y?j1HmOzabHcr<_ME)5CJwL9?N`o$g)dCa3o}RqS_}Qo_(wm0)onj$-|Yqc zJo{P5B34PBMWsxGd%F*Ju5b&Z{`h`-w;d!rDhnY$4ZaGRMR2FHz2Mw79HZI8TpS41 zKaA5 zL7&45LqFVk@TlDlHk-v3IEde+EUZf`j9wMTqF*WzJuQVikv8(IEI-m-q2vJ;P+LH0 zyDSr~9o#?DH7Q1E+!vvzJ(Gb5WQMwplw)jZcNEKbSmECFL+^mig}fN!t9Zyn}C(E{j6k+in+emR62MQbRELSzFWFR3UVjapzT z-j55>LZ1>bSBwXuxHUS_ulJ<@m|pSAg;$EVV6&-bH>k~4sJ*)O&l_|NXy1ktlPKlxHHej2e5A0$E zHjjGLpGQjN2f*m#^NR%ohTym{vHbzW`1AAVAeJecM+lKYzj9Ji`Edn3rmU$wgF*8G zdJwdnxfZv$T{#I&D0bju!rd+bsf!a}`ObiH=LKYIp<@$OJLyp}aVZ%$l*enTUb&|f zdYqjattXJjiTex&7iJWxBYH%_dPlwu9*3580UaEMRSkgx_1AU8cA?_oXzYA6DvLw; z4r(%U!O*?&%|}_hpNc@`0bUQz@8Wp}XWsU=Uj+9;-&9M!3)kg)xFH%OrcC?g2(gSc z(|(e{uuz7sw~zZ`WQyvHWRfX~bqH&wtnx#=e-k$>k-m1(Kl~i-`n60PMTWlb zfD9HOb_U{NDIk%;Sj$ktki}TTkiwM0+{;wUn8H%Sn8^s#&gxes671^Z>Kvk=rQjCi z@2gN+oLW?D9a01`;T9{HSHuQnYcdsa0;wXo$ro9LH3ZUAOX3rAa^k@X;!~2WfpWzl tbu5g0j2w(b@<67aCdcIEteTASlh3fK3Go3%co;aCIe3APlY^Uw69DI6E2sbf delta 65 zcmdnW+r-0{&&$ij00beE7N+iDoyaG{XfaXSk|mfylXqi*4J)&sCi~=IHqFV6Y)S$= RKy^F}9LyZNK*-6%4FJ|R3(f!l diff --git a/messenger_app/database/queries/messages.py b/messenger_app/database/queries/messages.py index d0e4214..35060ba 100644 --- a/messenger_app/database/queries/messages.py +++ b/messenger_app/database/queries/messages.py @@ -1,34 +1,25 @@ +from app.models import Message from database import postgresql_db_handler, mongo_db_handler import uuid from datetime import datetime -def create_conversation(user1_id, user2_id): +def create_conversation_db(user1_id, user2_id): + current_datetime = datetime.now().strftime("%Y%m%d%H%M%S") + unique_id = str(uuid.uuid4()).replace('-', '') + conversation_url = f"{current_datetime}_{unique_id}" + query = ''' - SELECT conversation_url FROM conversations - WHERE id_user1 = %s AND id_user2 = %s + INSERT INTO conversations (id_user1, id_user2, conversation_url) + VALUES (%s, %s, %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") - unique_id = str(uuid.uuid4()).replace('-', '') - conversation_url = f"{current_datetime}_{unique_id}" - - query = ''' - INSERT INTO conversations (id_user1, id_user2, conversation_url) - VALUES (%s, %s, %s) - ''' - params = (user1_id, user2_id, conversation_url) - postgresql_db_handler.execute_query(query, params) + params = (user1_id, user2_id, conversation_url) + postgresql_db_handler.execute_query(query, params) return conversation_url -def find_conversation(conversation_url): +def find_conversation_db(conversation_url): collection = mongo_db_handler.get_collection() query = {'name': conversation_url} result_set = collection.find_one(query) @@ -38,21 +29,40 @@ def find_conversation(conversation_url): 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: 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: - 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( - {"name": conversation_url}, + {"name": message.conversation_url}, {"$push": {"chat": new_message}} ) else: collection.insert_one({ - "name": conversation_url, - "chat": [{"sender": sender, "message": message, "timestamp": datetime.now()}] + "name": message.conversation_url, + "chat": [{"sender": message.sender, "message": message.message, + "message_type": message.message_type, "timestamp": datetime.now()}] }) return True diff --git a/messenger_app/database/queries/user.py b/messenger_app/database/queries/user.py index 230fda7..6449d77 100644 --- a/messenger_app/database/queries/user.py +++ b/messenger_app/database/queries/user.py @@ -54,3 +54,9 @@ def delete_user_db(user_id): params = (user_id,) postgresql_db_handler.execute_query(query, params) 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} diff --git a/messenger_gui/app/main.py b/messenger_gui/app/main.py index 41f4c29..1cd0ec3 100644 --- a/messenger_gui/app/main.py +++ b/messenger_gui/app/main.py @@ -1,7 +1,10 @@ +import json import tkinter as tk +from PIL import Image, ImageTk from app.views.login import LoginView from app.views.initial import InitialView from app.views.signup import SignupView +from app.views.main_page import MainPageView class MainApplication(tk.Tk): @@ -12,8 +15,13 @@ class MainApplication(tk.Tk): screen_height = self.winfo_screenheight() 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() def change_to_initial_view(self): @@ -25,6 +33,12 @@ class MainApplication(tk.Tk): def change_to_login_view(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): if self.current_view: self.current_view.destroy() @@ -33,5 +47,11 @@ class MainApplication(tk.Tk): self.current_view.pack(expand=True, fill=tk.BOTH) 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'] + diff --git a/messenger_gui/app/views/chat.py b/messenger_gui/app/views/chat.py new file mode 100644 index 0000000..3d07477 --- /dev/null +++ b/messenger_gui/app/views/chat.py @@ -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', []) diff --git a/messenger_gui/app/views/login.py b/messenger_gui/app/views/login.py index 561e534..c5f771d 100644 --- a/messenger_gui/app/views/login.py +++ b/messenger_gui/app/views/login.py @@ -94,9 +94,10 @@ class LoginView(tk.Frame): result = request_builder.build().make_request() 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: json_data = json.loads(result.text) detail_value = json_data.get('detail') self.error_var.set(detail_value) - self.error_label.grid(row=0, column=0, pady=5) \ No newline at end of file + self.error_label.grid(row=0, column=0, pady=5) diff --git a/messenger_gui/app/views/main_page.py b/messenger_gui/app/views/main_page.py new file mode 100644 index 0000000..0cbe766 --- /dev/null +++ b/messenger_gui/app/views/main_page.py @@ -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', []) diff --git a/messenger_gui/app/views/message.py b/messenger_gui/app/views/message.py new file mode 100644 index 0000000..db7642a --- /dev/null +++ b/messenger_gui/app/views/message.py @@ -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 \ No newline at end of file diff --git a/messenger_gui/app/views/signup.py b/messenger_gui/app/views/signup.py index 50518a5..8e22a3e 100644 --- a/messenger_gui/app/views/signup.py +++ b/messenger_gui/app/views/signup.py @@ -103,6 +103,7 @@ class SignupView(tk.Frame): result = request_builder.build().make_request() if result.status_code == 200: + self.master.get_user_information(result.text) self.master.change_to_initial_view() else: json_data = json.loads(result.text) diff --git a/messenger_gui/app/views/user_frame.py b/messenger_gui/app/views/user_frame.py new file mode 100644 index 0000000..492387e --- /dev/null +++ b/messenger_gui/app/views/user_frame.py @@ -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("", 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) diff --git a/messenger_gui/resources/9131529.png b/messenger_gui/resources/9131529.png new file mode 100644 index 0000000000000000000000000000000000000000..1db461c643c218d3a46e71987d119363cded686e GIT binary patch literal 21886 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 literal 0 HcmV?d00001 diff --git a/messenger_gui/resources/message-icon-png-17.png b/messenger_gui/resources/message-icon-png-17.png new file mode 100644 index 0000000000000000000000000000000000000000..861984671bee80b6a48970353db2158e85375bfb GIT binary patch literal 21725 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