Skip to content

Commit 3a9b9f5

Browse files
authored
Merge pull request #274 from Seluj78/132-images
2 parents 4ced763 + 162dbb1 commit 3a9b9f5

File tree

14 files changed

+699
-254
lines changed

14 files changed

+699
-254
lines changed

.env.enc

0 Bytes
Binary file not shown.

PyMatcha.postman_collection.json

Lines changed: 279 additions & 147 deletions
Large diffs are not rendered by default.

backend/PyMatcha/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
"REDIS_PORT",
6060
"DEBUG_AUTH_TOKEN",
6161
"FRONTEND_BASE_URL",
62+
"IMGUR_CLIENT_ID",
63+
"IMGUR_CLIENT_SECRET",
6264
]
6365

6466
for item in REQUIRED_ENV_VARS:
@@ -224,6 +226,7 @@ def check_if_token_is_revoked(decrypted_token):
224226
from PyMatcha.routes.api.match import match_bp
225227
from PyMatcha.routes.api.messages import messages_bp
226228
from PyMatcha.routes.api.recommendations import recommendations_bp
229+
from PyMatcha.routes.api.profile.images import images_bp
227230

228231
logging.debug("Registering Flask blueprints")
229232
application.register_blueprint(user_bp)
@@ -239,6 +242,7 @@ def check_if_token_is_revoked(decrypted_token):
239242
application.register_blueprint(match_bp)
240243
application.register_blueprint(messages_bp)
241244
application.register_blueprint(recommendations_bp)
245+
application.register_blueprint(images_bp)
242246

243247
if application.debug:
244248
logging.debug("Registering debug route")

backend/PyMatcha/models/image.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"""
2+
PyMatcha - A Python Dating Website
3+
Copyright (C) 2018-2019 jlasne/gmorer
4+
<jlasne@student.42.fr> - <lauris.skraucis@gmail.com>
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_images_table
25+
from PyMatcha.utils.orm import Field
26+
from PyMatcha.utils.orm import Model
27+
28+
29+
class Image(Model):
30+
table_name = "images"
31+
32+
id = Field(int, modifiable=False)
33+
user_id = Field(int)
34+
link = Field(str)
35+
timestamp = Field(datetime, fmt="%Y-%m-%d %H:%M:%S")
36+
is_primary = Field(bool)
37+
38+
@staticmethod
39+
def create(user_id: int, link: str, is_primary: bool = False, timestamp: datetime = datetime.utcnow()) -> Image:
40+
new_image = Image(user_id=user_id, link=link, is_primary=is_primary, timestamp=timestamp)
41+
new_image.save()
42+
logging.debug("Creating new image")
43+
return new_image
44+
45+
@classmethod
46+
def create_table(cls):
47+
create_images_table(cls.db)

backend/PyMatcha/models/user.py

Lines changed: 40 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from typing import Optional
2727

2828
import Geohash
29+
from PyMatcha.models.image import Image
2930
from PyMatcha.models.like import Like
3031
from PyMatcha.models.match import Match
3132
from PyMatcha.models.message import Message
@@ -199,6 +200,7 @@ def register(email: str, username: str, password: str, first_name: str, last_nam
199200
def to_dict(self) -> Dict:
200201
returned_dict = super().to_dict()
201202
returned_dict["tags"] = [t.to_dict() for t in self.get_tags()]
203+
returned_dict["images"] = [image.to_dict() for image in self.get_images()]
202204
returned_dict["reports"] = {"sent": [], "received": []}
203205
returned_dict["reports"]["sent"] = [r.to_dict() for r in self.get_reports_sent()]
204206
returned_dict["reports"]["received"] = [r.to_dict() for r in self.get_reports_received()]
@@ -222,128 +224,61 @@ def get_jwt_info(self):
222224
"date_lastseen": self.date_lastseen,
223225
}
224226

227+
def get_images(self):
228+
logging.debug("Getting all images for user {}".format(self.id))
229+
image_list = Image.get_multis(user_id=self.id)
230+
if not image_list:
231+
return []
232+
else:
233+
return image_list
234+
225235
def get_tags(self):
226236
logging.debug("Getting all tags for user {}".format(self.id))
227-
with self.db.cursor() as c:
228-
c.execute(
229-
"""
230-
SELECT tags.id as id, tags.user_id as user_id, tags.name as name
231-
FROM users
232-
INNER JOIN tags on users.id = tags.user_id
233-
WHERE users.id = CAST({} AS UNSIGNED)
234-
""".format(
235-
self.id
236-
)
237-
)
238-
tags = c.fetchall()
239-
tags_list = []
240-
for t in tags:
241-
tags_list.append(Tag(t))
242-
return tags_list
237+
tag_list = Tag.get_multis(user_id=self.id)
238+
if not tag_list:
239+
return []
240+
else:
241+
return tag_list
243242

244243
def get_views(self):
245244
logging.debug("Getting all views for user profile {}".format(self.id))
246-
with self.db.cursor() as c:
247-
c.execute(
248-
"""
249-
SELECT views.id as id, views.profile_id as profile_id,
250-
views.viewer_id as viewer_id, views.dt_seen as dt_seen
251-
FROM users
252-
INNER JOIN views on users.id = views.profile_id
253-
WHERE users.id = CAST({} AS UNSIGNED)
254-
""".format(
255-
self.id
256-
)
257-
)
258-
views = c.fetchall()
259-
views_list = []
260-
for v in views:
261-
views_list.append(View(v))
262-
return views_list
245+
view_list = View.get_multis(profile_id=self.id)
246+
if not view_list:
247+
return []
248+
else:
249+
return view_list
263250

264251
def get_reports_received(self):
265252
logging.debug("Getting all reports received for user {}".format(self.id))
266-
with self.db.cursor() as c:
267-
c.execute(
268-
"""
269-
SELECT reports.id as id, reports.reported_id as reported_id,
270-
reports.reporter_id as reporter_id, reports.dt_reported as dt_reported,
271-
reports.details as details, reports.reason as reason, reports.status as status
272-
FROM users
273-
INNER JOIN reports on users.id = reports.reported_id
274-
WHERE users.id = CAST({} AS UNSIGNED)
275-
""".format(
276-
self.id
277-
)
278-
)
279-
reports = c.fetchall()
280-
reports_list = []
281-
for r in reports:
282-
reports_list.append(Report(r))
283-
return reports_list
253+
reports_received_list = Report.get_multis(reported_id=self.id)
254+
if not reports_received_list:
255+
return []
256+
else:
257+
return reports_received_list
284258

285259
def get_reports_sent(self):
286260
logging.debug("Getting all reports sent for user {}".format(self.id))
287-
with self.db.cursor() as c:
288-
c.execute(
289-
"""
290-
SELECT reports.id as id, reports.reported_id as reported_id,
291-
reports.reporter_id as reporter_id, reports.dt_reported as dt_reported,
292-
reports.details as details, reports.reason as reason, reports.status as status
293-
FROM users
294-
INNER JOIN reports on users.id = reports.reporter_id
295-
WHERE users.id = CAST({} AS UNSIGNED)
296-
""".format(
297-
self.id
298-
)
299-
)
300-
reports = c.fetchall()
301-
reports_list = []
302-
for r in reports:
303-
reports_list.append(Report(r))
304-
return reports_list
261+
reports_sent_list = Report.get_multis(reporter_id=self.id)
262+
if not reports_sent_list:
263+
return []
264+
else:
265+
return reports_sent_list
305266

306267
def get_likes_received(self):
307268
logging.debug("Getting all likes received for user {}".format(self.id))
308-
with self.db.cursor() as c:
309-
c.execute(
310-
"""
311-
SELECT likes.id as id, likes.liked_id as liked_id,
312-
likes.liker_id as liker_id, likes.dt_liked as dt_liked,
313-
likes.is_superlike as is_superlike
314-
FROM users
315-
INNER JOIN likes on users.id = likes.liked_id
316-
WHERE users.id = CAST({} AS UNSIGNED)
317-
""".format(
318-
self.id
319-
)
320-
)
321-
likes = c.fetchall()
322-
like_list = []
323-
for like in likes:
324-
like_list.append(Like(like))
325-
return like_list
269+
likes_received_list = Like.get_multis(liked_id=self.id)
270+
if not likes_received_list:
271+
return []
272+
else:
273+
return likes_received_list
326274

327275
def get_likes_sent(self):
328276
logging.debug("Getting all likes sent for user {}".format(self.id))
329-
with self.db.cursor() as c:
330-
c.execute(
331-
"""
332-
SELECT likes.id as id, likes.liked_id as liked_id,
333-
likes.liker_id as liker_id, likes.dt_liked as dt_liked,
334-
likes.is_superlike as is_superlike
335-
FROM users
336-
INNER JOIN likes on users.id = likes.liker_id
337-
WHERE users.id = CAST({} AS UNSIGNED)
338-
""".format(
339-
self.id
340-
)
341-
)
342-
likes = c.fetchall()
343-
like_list = []
344-
for like in likes:
345-
like_list.append(Like(like))
346-
return like_list
277+
likes_sent_list = Like.get_multis(liker_id=self.id)
278+
if not likes_sent_list:
279+
return []
280+
else:
281+
return likes_sent_list
347282

348283
def already_likes(self, liked_id: int) -> bool:
349284
with self.db.cursor() as c:

backend/PyMatcha/routes/api/debug.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from flask_jwt_extended import current_user
88
from flask_jwt_extended import jwt_required
99
from PyMatcha import redis
10+
from PyMatcha.models.image import Image
1011
from PyMatcha.models.like import Like
1112
from PyMatcha.models.match import Match
1213
from PyMatcha.models.message import Message
@@ -141,6 +142,7 @@ def delete_tables():
141142
User.drop_table()
142143
Tag.drop_table()
143144
Message.drop_table()
145+
Image.drop_table()
144146

145147
Match.create_table()
146148
Like.create_table()
@@ -149,6 +151,7 @@ def delete_tables():
149151
User.create_table()
150152
Tag.create_table()
151153
Message.create_table()
154+
Image.create_table()
152155
return "", 204
153156

154157

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
from io import BytesIO
2+
3+
from flask import Blueprint
4+
from flask import request
5+
from flask_jwt_extended import current_user
6+
from flask_jwt_extended import jwt_required
7+
from PyMatcha.models.image import Image
8+
from PyMatcha.utils.errors import BadRequestError
9+
from PyMatcha.utils.errors import NotFoundError
10+
from PyMatcha.utils.images import upload_image
11+
from PyMatcha.utils.success import Success
12+
from PyMatcha.utils.success import SuccessDeleted
13+
from PyMatcha.utils.success import SuccessOutput
14+
15+
images_bp = Blueprint("images", __name__)
16+
17+
18+
@images_bp.route("/profile/images", methods=["POST"])
19+
@jwt_required
20+
def add_image_profile():
21+
is_primary = request.args.get("is_primary", "false") == "true"
22+
# check if the post request has the file part
23+
if "file[]" not in request.files:
24+
raise BadRequestError("No file body passed in request form data")
25+
file = request.files["file[]"]
26+
# if user does not select file, browser also
27+
# submit an empty part without filename
28+
if file.filename == "":
29+
raise BadRequestError("No file passed in request")
30+
if file:
31+
if is_primary:
32+
try:
33+
Image.get_multi(user_id=current_user.id, is_primary=True)
34+
except ValueError:
35+
# That means there was no primary image before
36+
tmp_img = BytesIO()
37+
file.save(tmp_img)
38+
try:
39+
link = upload_image(tmp_img, current_user.username)
40+
except BadRequestError as e:
41+
raise e
42+
Image.create(current_user.id, link, is_primary=True)
43+
return SuccessOutput("image", link)
44+
else:
45+
raise BadRequestError("There already is a primary image for user {}".format(current_user.id))
46+
else:
47+
images = Image.get_multis(user_id=current_user.id, is_primary=False)
48+
if images:
49+
image_count = len(Image.get_multis(user_id=current_user.id, is_primary=False))
50+
else:
51+
image_count = 0
52+
if image_count >= 4:
53+
raise BadRequestError("There's already enough images for this account")
54+
tmp_img = BytesIO()
55+
file.save(tmp_img)
56+
try:
57+
link = upload_image(tmp_img, current_user.username)
58+
except BadRequestError as e:
59+
raise e
60+
Image.create(current_user.id, link, is_primary=False)
61+
return SuccessOutput("image", link)
62+
else:
63+
raise ValueError("NO FILE")
64+
65+
66+
@images_bp.route("/profile/images/<image_id>", methods=["DELETE"])
67+
@jwt_required
68+
def delete_image_profile(image_id):
69+
try:
70+
image = Image.get(id=image_id)
71+
except ValueError:
72+
raise NotFoundError(f"Image not found for user {current_user.id}")
73+
image.delete()
74+
return SuccessDeleted("Image successfully deleted.")
75+
76+
77+
@images_bp.route("/profile/images/<image_id>", methods=["PUT"])
78+
@jwt_required
79+
def change_main_image(image_id):
80+
try:
81+
image = Image.get(id=image_id)
82+
except ValueError:
83+
raise NotFoundError(f"Image not found for user {current_user.id}")
84+
try:
85+
current_main_image = Image.get_multi(user_id=current_user.id, is_primary=True)
86+
except NotFoundError:
87+
# That means there was no primary image before
88+
pass
89+
else:
90+
current_main_image.is_primary = False
91+
current_main_image.save()
92+
image.is_primary = True
93+
image.save()
94+
return Success("Profile picture successfully modified")
95+
96+
97+
@images_bp.route("/profile/images", methods=["GET"])
98+
@jwt_required
99+
def get_images_profile():
100+
images = Image.get_multis(user_id=current_user.id)
101+
ret = []
102+
if images:
103+
ret = [image.to_dict() for image in images]
104+
return SuccessOutput("images", ret)

0 commit comments

Comments
 (0)