Skip to content

Commit 1a71d2c

Browse files
committed
WIP: Added base image routes and model
1 parent 4ced763 commit 1a71d2c

File tree

8 files changed

+176
-1
lines changed

8 files changed

+176
-1
lines changed

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)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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 SuccessOutput
13+
14+
images_bp = Blueprint("images", __name__)
15+
16+
17+
@images_bp.route("/profile/images", methods=["POST"])
18+
@jwt_required
19+
def add_image_profile():
20+
is_primary = request.args.get("is_primary", "false") == "true"
21+
# check if the post request has the file part
22+
if "file[]" not in request.files:
23+
raise BadRequestError("No file passed in request")
24+
file = request.files["file[]"]
25+
# if user does not select file, browser also
26+
# submit an empty part without filename
27+
if file.filename == "":
28+
raise BadRequestError("No filename passed in request")
29+
if file:
30+
tmp_img = BytesIO()
31+
file.save(tmp_img)
32+
link = upload_image(tmp_img, current_user.username)
33+
# TODO: Check if an image is already primary
34+
# TODO: Check if no more than 5 images
35+
Image.create(current_user.id, link, is_primary=is_primary)
36+
return SuccessOutput("image", link)
37+
else:
38+
raise ValueError("NO FILE")
39+
40+
41+
@images_bp.route("/profile/images/<image_id>", methods=["DELETE"])
42+
@jwt_required
43+
def delete_image_profile(image_id):
44+
try:
45+
image = Image.get(id=image_id)
46+
except ValueError:
47+
raise NotFoundError(f"Image not found for user {current_user.id}")
48+
image.delete()
49+
return Success("Image successfully deleted.")
50+
51+
52+
@images_bp.route("/profile/images/<image_id>", methods=["PUT"])
53+
@jwt_required
54+
def change_main_image(image_id):
55+
try:
56+
image = Image.get(id=image_id)
57+
except ValueError:
58+
raise NotFoundError(f"Image not found for user {current_user.id}")
59+
try:
60+
current_main_image = Image.get_multi(user_id=current_user.id, is_primary=True)
61+
except NotFoundError:
62+
# That means there was no primary image before
63+
pass
64+
else:
65+
current_main_image.is_primary = False
66+
current_main_image.save()
67+
image.is_primary = True
68+
image.save()
69+
70+
71+
@images_bp.route("/profile/images", methods=["GET"])
72+
@jwt_required
73+
def get_images_profile():
74+
images = Image.get_multis(user_id=current_user.id)
75+
ret = []
76+
if images:
77+
ret = [image.to_dict() for image in images]
78+
return SuccessOutput("images", ret)

backend/PyMatcha/utils/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from PyMatcha.utils.password import hash_password
2+
from PyMatcha.utils.tables import _create_images_table
23
from PyMatcha.utils.tables import _create_likes_table
34
from PyMatcha.utils.tables import _create_matches_table
45
from PyMatcha.utils.tables import _create_messages_table
@@ -15,6 +16,7 @@
1516
create_likes_table = _create_likes_table
1617
create_matches_table = _create_matches_table
1718
create_messages_table = _create_messages_table
19+
create_images_table = _create_images_table
1820

1921
__all__ = [
2022
"hash_password",
@@ -26,4 +28,5 @@
2628
"create_likes_table",
2729
"create_matches_table",
2830
"create_messages_table",
31+
"create_images_table",
2932
]

backend/PyMatcha/utils/images.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import os
2+
from io import BytesIO
3+
4+
from PIL import Image
5+
from pyimgur import Imgur
6+
from PyMatcha.utils.static import IMGUR_CLIENT_ID
7+
from PyMatcha.utils.static import IMGUR_CLIENT_SECRET
8+
9+
imgur_client = Imgur(client_id=IMGUR_CLIENT_ID, client_secret=IMGUR_CLIENT_SECRET)
10+
11+
12+
def upload_image(bytesio_img: BytesIO, username: str):
13+
path = f"{username}.png"
14+
Image.open(bytesio_img).convert("RGB").save(path)
15+
uploaded_image = imgur_client.upload_image(path=path, title=username)
16+
os.remove(path)
17+
return uploaded_image.link

backend/PyMatcha/utils/static.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@
3030

3131
PYMATCHA_ROOT = os.path.join(os.path.dirname(__file__), "../..")
3232
BACKEND_ROOT = os.path.join(os.path.dirname(__file__), "../")
33+
34+
IMGUR_CLIENT_ID = os.getenv("IMGUR_CLIENT_ID")
35+
IMGUR_CLIENT_SECRET = os.getenv("IMGUR_CLIENT_SECRET")

backend/PyMatcha/utils/tables.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,26 @@ def _create_messages_table(db):
177177
c.close()
178178

179179

180+
def _create_images_table(db):
181+
with db.cursor() as c:
182+
logging.info("Creating table images.")
183+
c.execute(DISABLE_SQL_NOTES)
184+
c.execute(
185+
"""
186+
CREATE TABLE IF NOT EXISTS images
187+
(
188+
id INT auto_increment PRIMARY KEY,
189+
user_id INT NOT NULL,
190+
timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
191+
link VARCHAR(256) NOT NULL,
192+
is_primary BOOLEAN DEFAULT FALSE
193+
) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci ;
194+
"""
195+
)
196+
c.execute(ENABLE_SQL_NOTES)
197+
c.close()
198+
199+
180200
def create_tables(db):
181201
_create_user_table(db)
182202
_create_tags_table(db)
@@ -185,3 +205,4 @@ def create_tables(db):
185205
_create_likes_table(db)
186206
_create_matches_table(db)
187207
_create_messages_table(db)
208+
_create_images_table(db)

backend/requirements.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,6 @@ python-Levenshtein==0.12.0
3636

3737
sentry-sdk==0.17.6
3838

39-
randomuser==1.6
39+
randomuser==1.6
40+
pyimgur==0.6.0
41+
Pillow==7.2.0

0 commit comments

Comments
 (0)