mirror of
https://github.com/Derpy-Leggies/OnlyLegs.git
synced 2025-01-29 01:28:24 +00:00
Functional settings menu plus sass stuff apparently?
This commit is contained in:
parent
1a59e413a9
commit
317c875cf0
|
@ -3,7 +3,6 @@ Onlylegs - API endpoints
|
|||
"""
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import logging
|
||||
from uuid import uuid4
|
||||
|
||||
|
@ -23,88 +22,13 @@ from flask_login import login_required, current_user
|
|||
from colorthief import ColorThief
|
||||
|
||||
from onlylegs.extensions import db
|
||||
from onlylegs.models import Users, Pictures, Exif
|
||||
from onlylegs.models import Pictures, Exif
|
||||
from onlylegs.utils.generate_image import generate_thumbnail
|
||||
|
||||
|
||||
blueprint = Blueprint("api", __name__, url_prefix="/api")
|
||||
|
||||
|
||||
@blueprint.route("/account/picture/<int:user_id>", methods=["POST"])
|
||||
@login_required
|
||||
def account_picture(user_id):
|
||||
"""
|
||||
Returns the profile of a user
|
||||
"""
|
||||
user = db.get_or_404(Users, user_id)
|
||||
file = request.files.get("file", None)
|
||||
|
||||
# If no image is uploaded, return 404 error
|
||||
if not file:
|
||||
return jsonify({"error": "No file uploaded"}), 400
|
||||
if user.id != current_user.id:
|
||||
return jsonify({"error": "You are not allowed to do this, go away"}), 403
|
||||
|
||||
# Get file extension, generate random name and set file path
|
||||
img_ext = pathlib.Path(file.filename).suffix.replace(".", "").lower()
|
||||
img_name = str(user.id)
|
||||
img_path = os.path.join(current_app.config["PFP_FOLDER"], img_name + "." + img_ext)
|
||||
|
||||
# Check if file extension is allowed
|
||||
if img_ext not in current_app.config["ALLOWED_EXTENSIONS"].keys():
|
||||
logging.info("File extension not allowed: %s", img_ext)
|
||||
return jsonify({"error": "File extension not allowed"}), 403
|
||||
|
||||
if user.picture:
|
||||
# Delete cached files and old image
|
||||
os.remove(os.path.join(current_app.config["PFP_FOLDER"], user.picture))
|
||||
cache_name = user.picture.rsplit(".")[0]
|
||||
for cache_file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob(
|
||||
cache_name + "*"
|
||||
):
|
||||
os.remove(cache_file)
|
||||
|
||||
# Save file
|
||||
try:
|
||||
file.save(img_path)
|
||||
except OSError as err:
|
||||
logging.info("Error saving file %s because of %s", img_path, err)
|
||||
return jsonify({"error": "Error saving file"}), 500
|
||||
|
||||
img_colors = ColorThief(img_path).get_color()
|
||||
|
||||
# Save to database
|
||||
user.colour = img_colors
|
||||
user.picture = str(img_name + "." + img_ext)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({"message": "File uploaded"}), 200
|
||||
|
||||
|
||||
@blueprint.route("/account/username/<int:user_id>", methods=["POST"])
|
||||
@login_required
|
||||
def account_username(user_id):
|
||||
"""
|
||||
Returns the profile of a user
|
||||
"""
|
||||
user = db.get_or_404(Users, user_id)
|
||||
new_name = request.form["name"]
|
||||
|
||||
username_regex = re.compile(r"\b[A-Za-z0-9._-]+\b")
|
||||
|
||||
# Validate the form
|
||||
if not new_name or not username_regex.match(new_name):
|
||||
return jsonify({"error": "Username is invalid"}), 400
|
||||
if user.id != current_user.id:
|
||||
return jsonify({"error": "You are not allowed to do this, go away"}), 403
|
||||
|
||||
# Save to database
|
||||
user.username = new_name
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({"message": "Username changed"}), 200
|
||||
|
||||
|
||||
@blueprint.route("/media/<path:path>", methods=["GET"])
|
||||
def media(path):
|
||||
"""
|
||||
|
|
|
@ -105,5 +105,5 @@ def logout():
|
|||
Clear the current session, including the stored user id
|
||||
"""
|
||||
logout_user()
|
||||
flash(["Goodbye!!!", "4"])
|
||||
flash("Goodbye!!!", "4")
|
||||
return redirect(url_for("gallery.index"))
|
||||
|
|
|
@ -44,9 +44,10 @@ WEBSITE_CONF = conf["website"]
|
|||
|
||||
# Directories
|
||||
UPLOAD_FOLDER = os.path.join(APPLICATION_ROOT, "media", "uploads")
|
||||
MEDIA_FOLDER = os.path.join(APPLICATION_ROOT, "media")
|
||||
CACHE_FOLDER = os.path.join(APPLICATION_ROOT, "media", "cache")
|
||||
PFP_FOLDER = os.path.join(APPLICATION_ROOT, "media", "pfp")
|
||||
MEDIA_FOLDER = os.path.join(APPLICATION_ROOT, "media")
|
||||
BANNER_FOLDER = os.path.join(APPLICATION_ROOT, "media", "banner")
|
||||
|
||||
# Database
|
||||
INSTANCE_DIR = os.path.join(APPLICATION_ROOT, "instance")
|
||||
|
|
|
@ -18,7 +18,11 @@ def colour_contrast(colour):
|
|||
"color: var(--fg-white);" or "color: var(--fg-black);"
|
||||
"""
|
||||
colour_obj = colour_utils.Colour(colour)
|
||||
return "rgb(var(--fg-black));" if colour_obj.is_light() else "rgb(var(--fg-white));"
|
||||
return (
|
||||
"var(--foreground-black);"
|
||||
if colour_obj.is_light()
|
||||
else "var(--foreground-white);"
|
||||
)
|
||||
|
||||
|
||||
@blueprint.app_template_filter()
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
width: 100%
|
||||
height: 100%
|
||||
|
||||
background: linear-gradient(to right, var(--background-100), transparent)
|
||||
background: linear-gradient(to right, var(--background-100), transparent 80%, var(--background-100) 100%)
|
||||
|
||||
z-index: +1
|
||||
|
||||
|
@ -157,7 +157,7 @@
|
|||
margin-left: auto
|
||||
width: auto
|
||||
|
||||
@media (max-width: var(--breakpoint))
|
||||
@media (max-width: 800px)
|
||||
.banner,
|
||||
.banner-small
|
||||
&::after
|
||||
|
@ -169,7 +169,7 @@
|
|||
max-height: 30vh
|
||||
|
||||
.banner-filter
|
||||
background: linear-gradient(to bottom, var(--background-100), transparent)
|
||||
background: linear-gradient(to top, var(--background-100), transparent)
|
||||
|
||||
.banner-content
|
||||
padding: 0.5rem
|
||||
|
|
|
@ -39,35 +39,35 @@
|
|||
|
||||
&:hover, &:focus-visible
|
||||
background-color: var(--primary)
|
||||
color: var(--white)
|
||||
color: var(--black)
|
||||
&.success
|
||||
background-color: var(--success-transparent)
|
||||
color: var(--success)
|
||||
|
||||
&:hover, &:focus-visible
|
||||
background-color: var(--success)
|
||||
color: var(--white)
|
||||
color: var(--black)
|
||||
&.warning
|
||||
background-color: var(--warning-transparent)
|
||||
color: var(--warning)
|
||||
|
||||
&:hover, &:focus-visible
|
||||
background-color: var(--warning)
|
||||
color: var(--white)
|
||||
color: var(--black)
|
||||
&.critical
|
||||
background-color: var(--danger-transparent)
|
||||
color: var(--danger)
|
||||
|
||||
&:hover, &:focus-visible
|
||||
background-color: var(--danger)
|
||||
color: var(--white)
|
||||
color: var(--black)
|
||||
&.info
|
||||
background-color: var(--info-transparent)
|
||||
color: var(--info)
|
||||
|
||||
&:hover, &:focus-visible
|
||||
background-color: var(--info)
|
||||
color: var(--white)
|
||||
color: var(--black)
|
||||
&.black
|
||||
background-color: var(--black-transparent)
|
||||
color: var(--white)
|
||||
|
@ -201,4 +201,4 @@
|
|||
|
||||
&.error
|
||||
background-color: var(--danger)
|
||||
color: var(--danger)
|
||||
color: var(--black)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
justify-content: center
|
||||
align-items: center
|
||||
|
||||
background-color: var(--background-300)
|
||||
background-color: var(--background-100)
|
||||
color: var(--foreground-white)
|
||||
border-radius: calc(var(--rad) / 2)
|
||||
border: none
|
||||
|
@ -34,6 +34,6 @@
|
|||
right: 0.75rem
|
||||
opacity: 1
|
||||
|
||||
@media (max-width: var(--breakpoint))
|
||||
@media (max-width: 800px)
|
||||
.top-of-page
|
||||
bottom: 4.25rem
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
overflow: hidden
|
||||
|
||||
transition: transform 0.2s ease-in-out, opacity 0.2s ease-in-out
|
||||
transition: transform 0.35s var(--animation-bounce), opacity 0.35s var(--animation-bounce)
|
||||
transform-origin: center center
|
||||
opacity: 0.5
|
||||
transform: scale(0, 0)
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
box-sizing: border-box
|
||||
overflow: hidden
|
||||
transition: box-shadow 0.2s cubic-bezier(.79, .14, .15, .86)
|
||||
transition: box-shadow 0.2s var(--animation-smooth)
|
||||
|
||||
.image-filter
|
||||
margin: 0
|
||||
|
@ -47,7 +47,7 @@
|
|||
opacity: 0 // hide
|
||||
|
||||
z-index: +4
|
||||
transition: opacity 0.2s cubic-bezier(.79, .14, .15, .86)
|
||||
transition: opacity 0.2s var(--animation-smooth)
|
||||
|
||||
.image-title,
|
||||
.image-subtitle
|
||||
|
@ -163,7 +163,7 @@
|
|||
border-radius: calc(var(--rad) / 2)
|
||||
box-shadow: 0 0 0.4rem 0.25rem var(--black-transparent)
|
||||
|
||||
transition: transform 0.2s cubic-bezier(.79, .14, .15, .86)
|
||||
transition: transform 0.2s var(--animation-smooth)
|
||||
|
||||
&.size-1
|
||||
.data-1
|
||||
|
@ -218,5 +218,5 @@
|
|||
grid-template-columns: auto auto auto
|
||||
|
||||
.gallery-item
|
||||
margin: 0.35rem
|
||||
margin: 0.1rem
|
||||
position: relative
|
|
@ -58,13 +58,6 @@ details
|
|||
font-size: 1.1rem
|
||||
font-weight: 500
|
||||
|
||||
&[open]
|
||||
summary
|
||||
margin-bottom: 0.5rem
|
||||
|
||||
> i.collapse-indicator
|
||||
transform: rotate(90deg)
|
||||
|
||||
p
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
@ -137,6 +130,16 @@ details
|
|||
tr:last-of-type td
|
||||
padding-bottom: 0
|
||||
|
||||
&[open]
|
||||
summary
|
||||
margin-bottom: 0.5rem
|
||||
|
||||
> i.collapse-indicator
|
||||
transform: rotate(90deg)
|
||||
|
||||
&:last-of-type
|
||||
margin-bottom: 0
|
||||
|
||||
.img-colours
|
||||
width: 100%
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ nav
|
|||
background-color: currentColor
|
||||
border-radius: calc(var(--rad) / 2)
|
||||
|
||||
@media (max-width: var(--breakpoint))
|
||||
@media (max-width: 800px)
|
||||
nav
|
||||
width: 100vw
|
||||
height: 3.5rem
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
position: relative
|
||||
|
||||
background-color: var(--background-300)
|
||||
background-color: var(--background-400)
|
||||
border-radius: calc(var(--rad) / 2)
|
||||
color: var(--foreground-white)
|
||||
opacity: 0
|
||||
|
@ -97,7 +97,7 @@
|
|||
justify-content: center
|
||||
align-items: center
|
||||
|
||||
background-color: var(--background-200)
|
||||
background-color: var(--background-300)
|
||||
|
||||
i
|
||||
font-size: 1.25rem
|
||||
|
@ -119,7 +119,7 @@
|
|||
line-height: 1
|
||||
text-align: left
|
||||
|
||||
@media (max-width: var(--breakpoint))
|
||||
@media (max-width: 800px)
|
||||
.notifications
|
||||
bottom: 3.8rem
|
||||
width: calc(100vw - 0.6rem)
|
||||
|
|
|
@ -38,12 +38,12 @@
|
|||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
background-color: var(--background-200)
|
||||
background-color: var(--background-400)
|
||||
border-radius: var(--rad)
|
||||
overflow: hidden
|
||||
|
||||
z-index: +2
|
||||
transition: transform 0.2s var(--animation-smooth)
|
||||
transition: transform 0.2s var(--animation-bounce)
|
||||
|
||||
.pop-up-header
|
||||
margin: 0 0 0.5rem 0
|
||||
|
@ -133,7 +133,7 @@
|
|||
.pop-up-wrapper
|
||||
transform: translate(-50%, 50%) scale(1)
|
||||
|
||||
@media (max-width: var(--breakpoint))
|
||||
@media (max-width: 800px)
|
||||
.pop-up
|
||||
.pop-up-wrapper
|
||||
max-width: calc(100% - 0.75rem)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
border-radius: calc(var(--rad) / 2)
|
||||
border: none
|
||||
background-color: var(--primary--transparent)
|
||||
background-color: var(--primary-transparent)
|
||||
color: var(--primary)
|
||||
|
||||
cursor: pointer
|
||||
|
@ -23,4 +23,4 @@
|
|||
font-size: 1.15rem
|
||||
|
||||
&:hover
|
||||
background-color: var(--primary--transparent)
|
||||
background-color: var(--primary-transparent)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
|
||||
overflow: hidden
|
||||
z-index: 68
|
||||
transition: background-color 0.25s cubic-bezier(0.76, 0, 0.17, 1)
|
||||
transition: background-color 0.25s var(--animation-smooth)
|
||||
|
||||
h3
|
||||
margin: 0
|
||||
|
@ -70,7 +70,7 @@
|
|||
|
||||
z-index: +2
|
||||
|
||||
transition: left 0.25s cubic-bezier(0.76, 0, 0.17, 1), bottom 0.25s cubic-bezier(0.76, 0, 0.17, 1)
|
||||
transition: left 0.25s var(--animation-smooth), bottom 0.25s var(--animation-smooth)
|
||||
|
||||
#dragIndicator
|
||||
display: none
|
||||
|
@ -97,7 +97,7 @@
|
|||
background-color: var(--background-400)
|
||||
border-radius: calc(var(--rad) / 2)
|
||||
|
||||
transition: width 0.25s var(--animation-bounce)
|
||||
transition: width 0.25s var(--animation-smooth)
|
||||
|
||||
&.dragging #dragIndicator::after
|
||||
width: 9rem
|
||||
|
@ -163,7 +163,7 @@
|
|||
|
||||
z-index: +3
|
||||
|
||||
transition: color 0.25s cubic-bezier(0.76, 0, 0.17, 1)
|
||||
transition: color 0.25s var(--animation-smooth)
|
||||
|
||||
.progress
|
||||
width: 100%
|
||||
|
@ -175,10 +175,10 @@
|
|||
|
||||
background-color: var(--primary)
|
||||
|
||||
animation: uploadingLoop 1s cubic-bezier(0.76, 0, 0.17, 1) infinite
|
||||
animation: uploadingLoop 1s var(--animation-smooth) infinite
|
||||
|
||||
z-index: +5
|
||||
transition: left 1s cubic-bezier(0.76, 0, 0.17, 1)
|
||||
transition: left 1s var(--animation-smooth)
|
||||
|
||||
&.critical
|
||||
.job__status, .progress
|
||||
|
@ -203,7 +203,7 @@
|
|||
.container
|
||||
left: 0
|
||||
|
||||
@media (max-width: var(--breakpoint))
|
||||
@media (max-width: 800px)
|
||||
.upload-panel
|
||||
width: 100%
|
||||
height: calc(100vh - 3.5rem)
|
||||
|
|
|
@ -57,7 +57,7 @@ body
|
|||
color: var(--foreground-white)
|
||||
|
||||
overflow-x: hidden
|
||||
@media (max-width: var(--breakpoint))
|
||||
@media (max-width: 800px)
|
||||
body
|
||||
padding: 0 0 3.5rem 0
|
||||
|
||||
|
@ -71,10 +71,13 @@ main
|
|||
color: var(--foreground-black)
|
||||
border-radius: var(--rad)
|
||||
overflow: hidden
|
||||
@media (max-width: var(--breakpoint))
|
||||
@media (max-width: 800px)
|
||||
main
|
||||
margin: 0 0.5rem
|
||||
// border-radius: 0
|
||||
|
||||
header
|
||||
position: sticky
|
||||
top: 0
|
||||
|
||||
.error-page
|
||||
min-height: 100%
|
||||
|
@ -100,7 +103,7 @@ main
|
|||
font-size: 1.25rem
|
||||
font-weight: 400
|
||||
text-align: center
|
||||
@media (max-width: var(--breakpoint))
|
||||
@media (max-width: 800px)
|
||||
.error-page
|
||||
h1
|
||||
font-size: 4.5rem
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
\:root
|
||||
--background-hsl-hue: 0
|
||||
--background-hsl-saturation: 2%
|
||||
--background-hsl-hue: 69
|
||||
--background-hsl-saturation: 25%
|
||||
|
||||
--background-100: hsl(var(--background-hsl-hue), var(--background-hsl-saturation), 6%)
|
||||
--background-200: hsl(var(--background-hsl-hue), var(--background-hsl-saturation), 8%)
|
||||
|
@ -13,14 +13,15 @@
|
|||
|
||||
--background-shade: hsl(var(--background-hsl-hue), var(--background-hsl-saturation), 6%, 0.5)
|
||||
|
||||
--foreground-gray: rgb(102, 102, 102)
|
||||
--foreground-white: rgb(232, 227, 227)
|
||||
--foreground-gray: rgb(102, 102, 102)
|
||||
--foreground-black: rgb(16, 16, 16)
|
||||
|
||||
--black: rgb(20, 20, 20)
|
||||
--black-transparent: rgba(20, 20, 20, 0.2)
|
||||
--white: rgb(232, 227, 227)
|
||||
--white-transparent: rgba(232, 227, 227, 0.2)
|
||||
|
||||
--red: rgb(182, 100, 103)
|
||||
--red-transparent: rgba(182, 100, 103, 0.1)
|
||||
--orange: rgb(217, 140, 95)
|
||||
|
@ -33,12 +34,17 @@
|
|||
--blue-transparent: rgba(141, 163, 185, 0.1)
|
||||
--purple: rgb(169, 136, 176)
|
||||
--purple-transparent: rgba(169, 136, 176, 0.1)
|
||||
|
||||
--primary: rgb(183, 169, 151)
|
||||
--primary-transparent: rgba(183, 169, 151, 0.1)
|
||||
|
||||
--warning: var(--orange)
|
||||
--warning-transparent: var(--orange-transparent)
|
||||
--danger: var(--red)
|
||||
--danger-transparent: var(--red-transparent)
|
||||
--success: var(--green)
|
||||
--success-transparent: var(--green-transparent)
|
||||
--info: var(--blue)
|
||||
--info-transparent: var(--blue-transparent)
|
||||
|
||||
--rad: 0.4rem
|
||||
|
||||
|
@ -47,4 +53,4 @@
|
|||
|
||||
--breakpoint: 800px
|
||||
|
||||
--font-family: 'Rubik', sans-serif
|
||||
--font-family: 'Switzer', sans-serif
|
||||
|
|
|
@ -17,9 +17,7 @@
|
|||
<meta name="twitter:description" content="{{ config.WEBSITE_CONF.motto }}">
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap">
|
||||
<link href="https://api.fontshare.com/v2/css?f[]=switzer@101,600,701,800,501,601,900,100,700,901,400,201,401,200,300,301,801,500&display=swap" rel="stylesheet">
|
||||
|
||||
<!-- phosphor icons -->
|
||||
<script src="https://unpkg.com/@phosphor-icons/web"></script>
|
||||
|
@ -130,6 +128,10 @@
|
|||
<script type="text/javascript">
|
||||
keepSquare();
|
||||
|
||||
{% for message in get_flashed_messages() %}
|
||||
addNotification('{{ message[0] }}', {{ message[1] }});
|
||||
{% endfor %}
|
||||
|
||||
const times = document.querySelectorAll('.time');
|
||||
for (let i = 0; i < times.length; i++) {
|
||||
// Remove milliseconds
|
||||
|
@ -191,10 +193,6 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
{% for message in get_flashed_messages() %}
|
||||
addNotification('{{ message[0] }}', {{ message[1] }});
|
||||
{% endfor %}
|
||||
</script>
|
||||
|
||||
{% block script %}{% endblock %}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
{% block header %}
|
||||
<div class="banner">
|
||||
{% if user.banner %}
|
||||
<img src="{{ url_for('static', filename='icon.png') }}" alt="Profile Banner" onload="imgFade(this)" style="opacity:0;"/>
|
||||
<img src="{{ url_for('api.media', path='banner/' + user.banner) }}" alt="Profile Banner" onload="imgFade(this)" style="opacity:0;"/>
|
||||
{% else %}
|
||||
<img src="{{ url_for('static', filename='banner.png') }}" alt="Profile Banner" onload="imgFade(this)" style="opacity:0;"/>
|
||||
{% endif %}
|
||||
|
|
|
@ -22,15 +22,20 @@
|
|||
<i class="ph ph-caret-down collapse-indicator"></i>
|
||||
</summary>
|
||||
|
||||
<form method="POST" action="{{ url_for('api.account_picture', user_id=current_user.id) }}" enctype="multipart/form-data">
|
||||
<form method="POST" action="{{ url_for('settings.account_picture') }}" enctype="multipart/form-data">
|
||||
<h3>Profile Picture</h3>
|
||||
<input type="file" name="file" tab-index="-1"/>
|
||||
<input type="submit" value="Upload" class="btn-block">
|
||||
<button type="submit" class="btn-block">Change Profile Picture</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ url_for('api.account_username', user_id=current_user.id) }}" enctype="multipart/form-data">
|
||||
<form method="POST" action="{{ url_for('settings.account_banner') }}" enctype="multipart/form-data">
|
||||
<h3>Profile Banner</h3>
|
||||
<input type="file" name="file" tab-index="-1"/>
|
||||
<button type="submit" class="btn-block">Change Profile Banner</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ url_for('settings.account_username') }}" enctype="multipart/form-data">
|
||||
<h3>Username</h3>
|
||||
<input type="text" name="name" class="input-block" value="{{ current_user.username }}" />
|
||||
<input type="submit" value="Upload" class="btn-block"/>
|
||||
<button type="submit" class="btn-block">Change Username</button>
|
||||
</form>
|
||||
</details>
|
||||
|
||||
|
@ -40,10 +45,27 @@
|
|||
<i class="ph ph-caret-down collapse-indicator"></i>
|
||||
</summary>
|
||||
|
||||
<form method="POST" action="" enctype="multipart/form-data">
|
||||
<h3>Email</h3>
|
||||
<input type="text" name="email" class="input-block" value="{{ current_user.email }}" />
|
||||
<input type="submit" value="Upload" class="btn-block"/>
|
||||
</form>
|
||||
<form method="POST" action="{{ url_for('settings.account_email') }}" enctype="multipart/form-data">
|
||||
<h3>Email</h3>
|
||||
<input type="text" name="email" class="input-block" value="{{ current_user.email }}" />
|
||||
<input type="password" name="current" class="input-block" placeholder="Current Password" />
|
||||
<button type="submit" class="btn-block">Change Email</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ url_for('settings.account_password') }}" enctype="multipart/form-data">
|
||||
<h3>Password</h3>
|
||||
<input type="password" name="current" class="input-block" placeholder="Current Password" />
|
||||
<input type="password" name="password" class="input-block" placeholder="New Password" />
|
||||
<input type="password" name="confirm" class="input-block" placeholder="Confirm Password" />
|
||||
<button type="submit" class="btn-block">Change Password</button>
|
||||
</form>
|
||||
</details>
|
||||
|
||||
<details open>
|
||||
<summary>
|
||||
<i class="ph ph-info"></i><h2>Server</h2><span style="width: 100%"></span>
|
||||
<i class="ph ph-caret-down collapse-indicator"></i>
|
||||
</summary>
|
||||
|
||||
<p>Nothing here :3</p>
|
||||
</details>
|
||||
{% endblock %}
|
||||
|
|
|
@ -16,6 +16,7 @@ REQUIRED_DIRS = {
|
|||
"uploads": os.path.join(APPLICATION_ROOT, "media", "uploads"),
|
||||
"cache": os.path.join(APPLICATION_ROOT, "media", "cache"),
|
||||
"pfp": os.path.join(APPLICATION_ROOT, "media", "pfp"),
|
||||
"banner": os.path.join(APPLICATION_ROOT, "media", "banner"),
|
||||
}
|
||||
|
||||
EMAIL_REGEX = re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b")
|
||||
|
@ -28,11 +29,10 @@ def check_dirs():
|
|||
"""
|
||||
|
||||
for directory in REQUIRED_DIRS.values():
|
||||
if os.path.exists(directory):
|
||||
print("User directory already exists at:", directory)
|
||||
return
|
||||
os.makedirs(directory)
|
||||
print("Created directory at:", directory)
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
print("Created directory at:", directory)
|
||||
print("User directory already exists at:", directory)
|
||||
|
||||
|
||||
def check_env():
|
||||
|
|
|
@ -1,17 +1,159 @@
|
|||
"""
|
||||
OnlyLegs - Settings page
|
||||
"""
|
||||
from flask import Blueprint, render_template
|
||||
from flask_login import login_required
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import logging
|
||||
from colorthief import ColorThief
|
||||
from flask import (
|
||||
Blueprint,
|
||||
request,
|
||||
current_app,
|
||||
render_template,
|
||||
flash,
|
||||
redirect,
|
||||
url_for,
|
||||
)
|
||||
from flask_login import login_required, current_user
|
||||
from werkzeug.security import check_password_hash, generate_password_hash
|
||||
from onlylegs.extensions import db
|
||||
from onlylegs.models import Users
|
||||
|
||||
|
||||
blueprint = Blueprint("settings", __name__, url_prefix="/settings")
|
||||
|
||||
|
||||
@blueprint.route("/")
|
||||
@blueprint.route("/", methods=["GET"])
|
||||
@login_required
|
||||
def general():
|
||||
"""
|
||||
General settings page
|
||||
"""
|
||||
return render_template("settings.html")
|
||||
|
||||
|
||||
@blueprint.route("/account/pfp", methods=["POST"])
|
||||
@login_required
|
||||
def account_picture():
|
||||
user_record = Users.query.filter_by(id=current_user.id).first()
|
||||
uploaded_file = request.files.get("file", None)
|
||||
if not uploaded_file:
|
||||
return "No file uploaded!", 400
|
||||
|
||||
image_mime = pathlib.Path(uploaded_file.filename).suffix.replace(".", "").lower()
|
||||
image_name = str(user_record.id) + "_pfp." + image_mime
|
||||
image_path = os.path.join(current_app.config["PFP_FOLDER"], image_name)
|
||||
|
||||
if image_mime not in current_app.config["ALLOWED_EXTENSIONS"].keys():
|
||||
logging.info("File extension not allowed: %s", image_mime)
|
||||
return "File extension not allowed", 403
|
||||
|
||||
if user_record.picture:
|
||||
os.remove(os.path.join(current_app.config["PFP_FOLDER"], user_record.picture))
|
||||
cache_name = user_record.picture.rsplit(".")[0]
|
||||
for file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob(
|
||||
cache_name + "*"
|
||||
):
|
||||
os.remove(file)
|
||||
|
||||
uploaded_file.save(image_path)
|
||||
image_colours = ColorThief(image_path).get_color()
|
||||
|
||||
user_record.colour = image_colours
|
||||
user_record.picture = image_name
|
||||
db.session.commit()
|
||||
|
||||
return "File uploaded", 200
|
||||
|
||||
|
||||
@blueprint.route("/account/banner", methods=["POST"])
|
||||
@login_required
|
||||
def account_banner():
|
||||
user_record = Users.query.filter_by(id=current_user.id).first()
|
||||
uploaded_file = request.files.get("file", None)
|
||||
if not uploaded_file:
|
||||
return "No file uploaded!", 400
|
||||
|
||||
image_mime = pathlib.Path(uploaded_file.filename).suffix.replace(".", "").lower()
|
||||
image_name = str(user_record.id) + "_banner." + image_mime
|
||||
image_path = os.path.join(current_app.config["BANNER_FOLDER"], image_name)
|
||||
|
||||
if image_mime not in current_app.config["ALLOWED_EXTENSIONS"].keys():
|
||||
logging.info("File extension not allowed: %s", image_mime)
|
||||
return "File extension not allowed", 403
|
||||
|
||||
if user_record.banner:
|
||||
os.remove(os.path.join(current_app.config["BANNER_FOLDER"], user_record.banner))
|
||||
cache_name = user_record.banner.rsplit(".")[0]
|
||||
for file in pathlib.Path(current_app.config["CACHE_FOLDER"]).glob(
|
||||
cache_name + "*"
|
||||
):
|
||||
os.remove(file)
|
||||
|
||||
uploaded_file.save(image_path)
|
||||
user_record.banner = image_name
|
||||
db.session.commit()
|
||||
|
||||
return "File uploaded", 200
|
||||
|
||||
|
||||
@blueprint.route("/account/username", methods=["POST"])
|
||||
@login_required
|
||||
def account_username():
|
||||
user_record = Users.query.filter_by(id=current_user.id).first()
|
||||
new_username = request.form.get("username", "").strip()
|
||||
|
||||
username_regex = re.compile(r"\b[A-Za-z0-9._-]+\b")
|
||||
|
||||
if not new_username or not username_regex.match(new_username):
|
||||
return "Username is invalid", 400
|
||||
|
||||
user_record.username = new_username
|
||||
db.session.commit()
|
||||
|
||||
return "Username changed", 200
|
||||
|
||||
|
||||
@blueprint.route("/account/email", methods=["POST"])
|
||||
@login_required
|
||||
def account_email():
|
||||
user_record = Users.query.filter_by(id=current_user.id).first()
|
||||
current_password = request.form.get("current", "").strip()
|
||||
new_email = request.form.get("email", "").strip()
|
||||
|
||||
email_regex = re.compile(r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b")
|
||||
|
||||
if not current_password or not new_email:
|
||||
return "Fill in all the fields!", 400
|
||||
if not email_regex.match(new_email):
|
||||
return "Email is invalid!", 400
|
||||
if not check_password_hash(user_record.password, current_password):
|
||||
return "Incorrect password!", 400
|
||||
|
||||
user_record.email = new_email
|
||||
db.session.commit()
|
||||
|
||||
return "Email changed", 200
|
||||
|
||||
|
||||
@blueprint.route("/account/password", methods=["POST"])
|
||||
@login_required
|
||||
def account_password():
|
||||
user_record = Users.query.filter_by(id=current_user.id).first()
|
||||
current_password = request.form.get("current", "").strip()
|
||||
new_password = request.form.get("password", "").strip()
|
||||
new_confirm = request.form.get("confirm", "").strip()
|
||||
|
||||
if not current_password or not new_password or not new_confirm:
|
||||
return "Fill in all the fields!", 400
|
||||
if new_password != new_confirm:
|
||||
return "Passwords do not match!", 400
|
||||
if not check_password_hash(user_record.password, current_password):
|
||||
return "Incorrect password!", 400
|
||||
|
||||
user_record.password = generate_password_hash(new_password, method="scrypt")
|
||||
db.session.commit()
|
||||
|
||||
flash(["Password changed! You must login now", 0])
|
||||
return redirect(url_for("auth.logout"))
|
||||
|
|
Loading…
Reference in a new issue