Skip to content

Commit 59bb23a

Browse files
authored
Merge pull request #94 from Seluj78/messages
2 parents bc04a62 + bcfeefd commit 59bb23a

29 files changed

+1859
-181
lines changed

PyMatcha.postman_collection.json

Lines changed: 1358 additions & 70 deletions
Large diffs are not rendered by default.

backend/PyMatcha/__init__.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454

5555
for item in REQUIRED_ENV_VARS:
5656
if item not in os.environ:
57-
raise EnvironmentError(f"{item} is not set in the server's environment or .env file. It is required")
57+
raise EnvironmentError(f"{item} is not set in the server's environment or .env file. It is required.")
5858

5959
if os.getenv("ENABLE_LOGGING") == "True":
6060
setup_logging()
@@ -122,7 +122,7 @@ def expired_token_callback(expired_token):
122122

123123
logging.debug("Setting database config from environment variables")
124124
database_config = {
125-
"host": os.getenv("DB_HOST") if not os.getenv("IS_DOCKER_COMPOSE") else "mysql",
125+
"host": os.getenv("DB_HOST"),
126126
"port": int(os.getenv("DB_PORT")),
127127
"user": os.getenv("DB_USER"),
128128
"password": database_password,
@@ -185,7 +185,6 @@ def check_if_token_is_revoked(decrypted_token):
185185
return entry == "true"
186186

187187

188-
from PyMatcha.routes.api.ping_pong import ping_pong_bp
189188
from PyMatcha.routes.api.user import user_bp
190189
from PyMatcha.routes.api.auth.email import auth_email_bp
191190
from PyMatcha.routes.api.auth.password import auth_password_bp
@@ -197,9 +196,9 @@ def check_if_token_is_revoked(decrypted_token):
197196
from PyMatcha.routes.api.profile.report import profile_report_bp
198197
from PyMatcha.routes.api.like import like_bp
199198
from PyMatcha.routes.api.match import match_bp
199+
from PyMatcha.routes.api.messages import messages_bp
200200

201201
logging.debug("Registering Flask blueprints")
202-
application.register_blueprint(ping_pong_bp)
203202
application.register_blueprint(user_bp)
204203
application.register_blueprint(auth_email_bp)
205204
application.register_blueprint(auth_password_bp)
@@ -211,6 +210,7 @@ def check_if_token_is_revoked(decrypted_token):
211210
application.register_blueprint(profile_report_bp)
212211
application.register_blueprint(like_bp)
213212
application.register_blueprint(match_bp)
213+
application.register_blueprint(messages_bp)
214214

215215
if application.debug:
216216
logging.debug("Registering debug route")

backend/PyMatcha/models/message.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""
2+
PyMatcha - A Python Dating Website
3+
Copyright (C) 2018-2019 jlasne/gmorer
4+
<jlasne@student.42.fr> - <gmorer@student.42.fr>
5+
6+
This program is free software: you can redistribute it and/or modify
7+
it under the terms of the GNU General Public License as published by
8+
the Free Software Foundation, either version 3 of the License, or
9+
(at your option) any later version.
10+
11+
This program is distributed in the hope that it will be useful,
12+
but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
GNU General Public License for more details.
15+
16+
You should have received a copy of the GNU General Public License
17+
along with this program. If not, see <https://www.gnu.org/licenses/>.
18+
"""
19+
from __future__ import annotations
20+
21+
import logging
22+
from datetime import datetime
23+
24+
from PyMatcha.utils import create_messages_table
25+
from PyMatcha.utils.orm import Field
26+
from PyMatcha.utils.orm import Model
27+
28+
29+
class Message(Model):
30+
table_name = "messages"
31+
32+
id = Field(int, modifiable=False)
33+
from_id = Field(int)
34+
to_id = Field(int)
35+
timestamp = Field(datetime)
36+
seen_timestamp = Field(datetime)
37+
content = Field(str)
38+
is_seen = Field(bool)
39+
is_liked = Field(bool)
40+
41+
@staticmethod
42+
def create(
43+
from_id: int,
44+
to_id: int,
45+
content: str,
46+
timestamp: datetime = datetime.utcnow(),
47+
seen_timestamp: datetime = None,
48+
is_seen: bool = False,
49+
is_liked: bool = False,
50+
) -> Message:
51+
new_message = Message(
52+
from_id=from_id,
53+
to_id=to_id,
54+
content=content,
55+
timestamp=timestamp,
56+
seen_timestamp=seen_timestamp,
57+
is_seen=is_seen,
58+
is_liked=is_liked,
59+
)
60+
new_message.save()
61+
logging.debug("Created new message")
62+
return new_message
63+
64+
@classmethod
65+
def create_table(cls):
66+
create_messages_table(cls.db)

backend/PyMatcha/models/user.py

Lines changed: 112 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,16 @@
1919
from __future__ import annotations
2020

2121
import datetime
22-
import hashlib
2322
import logging
2423
from typing import Any
2524
from typing import Dict
25+
from typing import List
2626
from typing import Optional
2727

2828
import Geohash
2929
from PyMatcha.models.like import Like
3030
from PyMatcha.models.match import Match
31+
from PyMatcha.models.message import Message
3132
from PyMatcha.models.report import Report
3233
from PyMatcha.models.tag import Tag
3334
from PyMatcha.models.view import View
@@ -62,11 +63,6 @@ class User(Model):
6263
confirmed_on = Field(datetime.datetime, fmt="%Y-%m-%d %H:%M:%S")
6364
previous_reset_token = Field(str)
6465

65-
def check_password(self, password: str) -> bool:
66-
logging.debug("Checking password again {} hashed password".format(self.id))
67-
_hash, salt = self.password.split(":")
68-
return _hash == hashlib.sha256(salt.encode() + password.encode()).hexdigest()
69-
7066
@staticmethod
7167
def create(
7268
first_name: str,
@@ -94,7 +90,7 @@ def create(
9490
pass
9591
else:
9692
logging.error("Email {} taken".format(email))
97-
raise ConflictError("Email {} taken".format(email), "Use another email")
93+
raise ConflictError("Email {} taken.".format(email), "Use another email.")
9894

9995
# Check username availability
10096
try:
@@ -103,21 +99,20 @@ def create(
10399
pass
104100
else:
105101
logging.error("Username {} taken".format(username))
106-
raise ConflictError("Username {} taken".format(username), "Try another username")
102+
raise ConflictError("Username {} taken.".format(username), "Try another username.")
107103

108104
# Check correct gender
109105
if gender not in ["male", "female", "other"]:
110106
logging.error("Gender must be male, female or other, not {}".format(gender))
111-
raise ConflictError("Gender must be male, female or other, not {}".format(gender), "Try again")
107+
raise ConflictError("Gender must be male, female or other, not {}.".format(gender))
112108

113109
# Check correct orientation
114110
if orientation not in ["heterosexual", "homosexual", "bisexual", "other"]:
115111
logging.error(
116-
"Sexual Orientation must be heterosexual, homosexual, bisexual or other, not {}".format(orientation)
112+
"Sexual Orientation must be heterosexual, homosexual, bisexual or other, not {}.".format(orientation)
117113
)
118114
raise ConflictError(
119-
"Sexual Orientation must be heterosexual, homosexual, bisexual or other, not {}".format(orientation),
120-
"Try again",
115+
"Sexual Orientation must be heterosexual, homosexual, bisexual or other, not {}.".format(orientation)
121116
)
122117

123118
# Check correct geohash
@@ -163,7 +158,7 @@ def register(email: str, username: str, password: str, first_name: str, last_nam
163158
pass
164159
else:
165160
logging.debug("Email {} taken".format(email))
166-
raise ConflictError("Email {} taken".format(email), "Use another email")
161+
raise ConflictError("Email {} taken.".format(email), "Use another email.")
167162

168163
# Check username availability
169164
try:
@@ -172,7 +167,7 @@ def register(email: str, username: str, password: str, first_name: str, last_nam
172167
pass
173168
else:
174169
logging.error("Username {} taken".format(username))
175-
raise ConflictError("Username {} taken".format(username), "Try another username")
170+
raise ConflictError("Username {} taken.".format(username), "Try another username.")
176171

177172
# Encrypt password
178173
password = hash_password(password)
@@ -387,6 +382,108 @@ def get_matches(self):
387382
match_list.append(Match(match))
388383
return match_list
389384

385+
def send_message(self, to_id, content):
386+
Message.create(from_id=self.id, to_id=to_id, content=content)
387+
388+
def get_messages(self) -> List[Message]:
389+
with self.db.cursor() as c:
390+
c.execute(
391+
"""
392+
SELECT
393+
messages.from_id as from_id,
394+
messages.to_id as to_id,
395+
messages.id as id,
396+
messages.timestamp as timestamp,
397+
messages.seen_timestamp as seen_timestamp,
398+
messages.content as content,
399+
messages.is_liked as is_liked,
400+
messages.is_seen as is_seen
401+
FROM messages
402+
INNER JOIN users on users.id = messages.from_id or users.id = messages.to_id
403+
WHERE users.id = CAST({} AS SIGNED)
404+
""".format(
405+
self.id
406+
)
407+
)
408+
messages = c.fetchall()
409+
message_list = []
410+
for message in messages:
411+
message_list.append(Message(message))
412+
logging.debug("Getting all messages sent or received by user {}".format(self.id))
413+
return message_list
414+
415+
def get_conversation_list(self) -> List[Message]:
416+
with self.db.cursor() as c:
417+
c.execute(
418+
"""
419+
SELECT t1.*
420+
FROM messages AS t1
421+
INNER JOIN
422+
(
423+
SELECT
424+
LEAST(from_id, to_id) AS from_id,
425+
GREATEST(from_id, to_id) AS to_id,
426+
MAX(id) AS max_id
427+
FROM messages
428+
GROUP BY
429+
LEAST(from_id, to_id),
430+
GREATEST(from_id, to_id)
431+
) AS t2
432+
ON LEAST(t1.from_id, t1.to_id) = t2.from_id AND
433+
GREATEST(t1.from_id, t1.to_id) = t2.to_id AND
434+
t1.id = t2.max_id
435+
WHERE t1.from_id = {0} OR t1.to_id = {0}
436+
""".format(
437+
self.id
438+
)
439+
)
440+
conversations = c.fetchall()
441+
conversation_list = []
442+
for last_message in conversations:
443+
conversation_list.append(Message(last_message))
444+
return conversation_list
445+
446+
def get_messages_with_user(self, with_user_id) -> List[Message]:
447+
with self.db.cursor() as c:
448+
c.execute(
449+
"""
450+
SELECT
451+
messages.from_id as from_id,
452+
messages.to_id as to_id,
453+
messages.id as id,
454+
messages.timestamp as timestamp,
455+
messages.seen_timestamp as seen_timestamp,
456+
messages.content as content,
457+
messages.is_liked as is_liked,
458+
messages.is_seen as is_seen
459+
FROM messages
460+
WHERE from_id=CAST({0} AS SIGNED) and to_id=CAST({1} AS SIGNED)
461+
462+
UNION ALL
463+
464+
SELECT messages.from_id as from_id,
465+
messages.to_id as to_id,
466+
messages.id as id,
467+
messages.timestamp as timestamp,
468+
messages.seen_timestamp as seen_timestamp,
469+
messages.content as content,
470+
messages.is_liked as is_liked,
471+
messages.is_seen as is_seen
472+
FROM messages
473+
WHERE from_id=CAST({1} AS SIGNED) and to_id=CAST({0} AS SIGNED)
474+
""".format(
475+
self.id, with_user_id
476+
)
477+
)
478+
messages = c.fetchall()
479+
message_list = []
480+
for message in messages:
481+
message_list.append(Message(message))
482+
logging.debug(
483+
"Getting all messages between user {} and {} (Total: {})".format(self.id, with_user_id, len(message_list))
484+
)
485+
return message_list
486+
390487

391488
def get_user(uid: Any[int, str]) -> Optional[User]:
392489
not_found = 0
@@ -417,6 +514,6 @@ def get_user(uid: Any[int, str]) -> Optional[User]:
417514
# If none of those worked, throw an error
418515
if not_found == 3:
419516
logging.debug("User {} not found.".format(uid))
420-
raise NotFoundError("User {} not found.".format(uid), "Try again with another uid")
517+
raise NotFoundError("User {} not found.".format(uid))
421518
logging.debug("Found user {} from {}".format(f_user.id, uid))
422519
return f_user

backend/PyMatcha/routes/api/auth/login.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,10 @@
3535
from PyMatcha.utils.decorators import validate_params
3636
from PyMatcha.utils.errors import NotFoundError
3737
from PyMatcha.utils.errors import UnauthorizedError
38+
from PyMatcha.utils.password import check_password
3839
from PyMatcha.utils.success import Success
3940
from PyMatcha.utils.success import SuccessOutput
4041

41-
4242
REQUIRED_KEYS_LOGIN = {"username": str, "password": str}
4343

4444
auth_login_bp = Blueprint("auth_login", __name__)
@@ -55,14 +55,14 @@ def auth_login():
5555
u = get_user(username)
5656
except NotFoundError:
5757
current_app.logger.debug("/auth/login -> User not found")
58-
raise UnauthorizedError("Incorrect username or password", "Try again")
59-
if not u.check_password(password):
58+
raise UnauthorizedError("Incorrect username or password.")
59+
if not check_password(u.password, password):
6060
current_app.logger.debug("/auth/login -> Password invalid")
61-
raise UnauthorizedError("Incorrect username or password", "Try again")
61+
raise UnauthorizedError("Incorrect username or password.")
6262

6363
if not u.is_confirmed:
6464
current_app.logger.debug("/auth/login -> User is trying to login unconfirmed")
65-
raise UnauthorizedError("User needs to be confirmed first.", "Try again when you have confirmed your email")
65+
raise UnauthorizedError("User needs to be confirmed first.", "Try again when you have confirmed your email.")
6666
access_token = create_access_token(identity=u.get_jwt_info(), fresh=True)
6767
refresh_token = create_refresh_token(identity=u.get_jwt_info())
6868
access_jti = get_jti(access_token)

backend/PyMatcha/routes/api/auth/password.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,17 +82,17 @@ def reset_password():
8282
else:
8383
if token_type != "reset":
8484
current_app.logger.debug("/auth/password/reset -> Wrong token type")
85-
raise BadRequestError("Wrong token type", "Try again with the correct type")
85+
raise BadRequestError("Wrong token type.")
8686
try:
8787
u = get_user(email)
8888
except NotFoundError:
8989
current_app.logger.debug("/auth/password/reset -> User not found")
90-
raise NotFoundError("User not found", "Try again with another user.")
90+
raise NotFoundError("User not found.")
9191
if u.previous_reset_token == data["token"]:
9292
current_app.logger.debug("/auth/password/reset -> Token already used")
93-
raise BadRequestError("Token already used", "Please request a new one")
93+
raise BadRequestError("Token already used", "Please request a new one.")
9494
u.password = hash_password(data["password"])
9595
u.previous_reset_token = data["token"]
9696
u.save()
9797
current_app.logger.debug("/auth/password/reset -> Password reset successfully")
98-
return Success("Password reset successful")
98+
return Success("Password reset successful.")

0 commit comments

Comments
 (0)