add(backend): possibility to delete paste
All checks were successful
ci/woodpecker/push/lint Pipeline was successful
All checks were successful
ci/woodpecker/push/lint Pipeline was successful
This commit is contained in:
parent
df2837d3ff
commit
fc8527d69f
4 changed files with 61 additions and 18 deletions
|
@ -26,9 +26,10 @@ def check_content_exist(key: str) -> bool:
|
||||||
return db.exists(key)
|
return db.exists(key)
|
||||||
|
|
||||||
|
|
||||||
def insert_content_db(url_id: str, expiration: int, content: str) -> None:
|
def insert_content_db(url_id: str, expiration: int, content: str, secret: str) -> None:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
:param secret: Secret key for delete url
|
||||||
:param url_id: key for access to content.
|
:param url_id: key for access to content.
|
||||||
:param expiration: Content expiration in second.
|
:param expiration: Content expiration in second.
|
||||||
:param content: Paste content
|
:param content: Paste content
|
||||||
|
@ -36,7 +37,9 @@ def insert_content_db(url_id: str, expiration: int, content: str) -> None:
|
||||||
"""
|
"""
|
||||||
if not check_content_exist(url_id):
|
if not check_content_exist(url_id):
|
||||||
db = connect_redis()
|
db = connect_redis()
|
||||||
db.set(url_id, content)
|
data = {"content": content, "secret": secret}
|
||||||
|
for key, key_content in data.items():
|
||||||
|
db.hset(url_id, key, key_content)
|
||||||
db.expire(url_id, expiration)
|
db.expire(url_id, expiration)
|
||||||
else:
|
else:
|
||||||
logging.error(f"Key : {url_id} already exist") # noqa: G004
|
logging.error(f"Key : {url_id} already exist") # noqa: G004
|
||||||
|
|
|
@ -1,31 +1,35 @@
|
||||||
"""Manage view and create paste."""
|
"""Manage view and create paste."""
|
||||||
|
|
||||||
from flask import (
|
from flask import (
|
||||||
Blueprint,
|
Blueprint,
|
||||||
|
abort,
|
||||||
flash,
|
flash,
|
||||||
redirect,
|
redirect,
|
||||||
render_template,
|
render_template,
|
||||||
request,
|
request,
|
||||||
url_for, abort,
|
url_for,
|
||||||
)
|
)
|
||||||
from werkzeug import Response
|
from werkzeug import Response
|
||||||
|
|
||||||
from paste.db import check_content_exist, connect_redis, insert_content_db
|
from paste.db import check_content_exist, connect_redis, insert_content_db
|
||||||
from paste.utils import generate_id
|
from paste.utils import generate_id, generate_secret
|
||||||
|
|
||||||
home = Blueprint("home", __name__, url_prefix="/")
|
home = Blueprint("home", __name__, url_prefix="/")
|
||||||
|
|
||||||
|
|
||||||
def create_paste(content: str, time:int) -> str:
|
def create_paste(content: str, time: int) -> dict[str, str]:
|
||||||
"""
|
"""
|
||||||
Create paste in DB.
|
Create paste in DB.
|
||||||
|
:param time: Expiration time in second
|
||||||
:param content: Content to add in redis.
|
:param content: Content to add in redis.
|
||||||
:return: None.
|
:return: A dict with url_id and delete secret.
|
||||||
"""
|
"""
|
||||||
|
secret = generate_secret()
|
||||||
url_id = generate_id()
|
url_id = generate_id()
|
||||||
while check_content_exist(url_id):
|
while check_content_exist(url_id):
|
||||||
url_id = generate_id()
|
url_id = generate_id()
|
||||||
insert_content_db(url_id, time, content)
|
insert_content_db(url_id, time, content, secret)
|
||||||
return url_id
|
return {"url_id": url_id, "secret": secret}
|
||||||
|
|
||||||
|
|
||||||
@home.route("/")
|
@home.route("/")
|
||||||
|
@ -47,11 +51,28 @@ def create() -> Response:
|
||||||
time = request.form.get("time")
|
time = request.form.get("time")
|
||||||
time = 86400 if time == "" else int(time)
|
time = 86400 if time == "" else int(time)
|
||||||
|
|
||||||
url_id = create_paste(content, time)
|
flash_data = create_paste(content, time)
|
||||||
flash(f"{request.host_url}{url_id}")
|
flash(flash_data["url_id"], category="create")
|
||||||
|
flash(flash_data["secret"], category="create")
|
||||||
return redirect(url_for("home.homepage"))
|
return redirect(url_for("home.homepage"))
|
||||||
|
|
||||||
|
|
||||||
|
@home.route("/delete/<path:path>")
|
||||||
|
def delete_paste(path: str) -> Response:
|
||||||
|
"""
|
||||||
|
Delete a paste
|
||||||
|
:param path: path fetched from url
|
||||||
|
:return: forbidden (403) or homepage.
|
||||||
|
"""
|
||||||
|
db = connect_redis()
|
||||||
|
secret = request.args.get("secret")
|
||||||
|
if db.hget(path, "secret") == secret:
|
||||||
|
db.delete(path)
|
||||||
|
flash(path, category="delete")
|
||||||
|
return redirect(url_for("home.homepage"))
|
||||||
|
return abort(403)
|
||||||
|
|
||||||
|
|
||||||
@home.route("/<path:path>")
|
@home.route("/<path:path>")
|
||||||
def get_content(path: str) -> str:
|
def get_content(path: str) -> str:
|
||||||
"""
|
"""
|
||||||
|
@ -60,9 +81,9 @@ def get_content(path: str) -> str:
|
||||||
:return: Paste content.
|
:return: Paste content.
|
||||||
"""
|
"""
|
||||||
db = connect_redis()
|
db = connect_redis()
|
||||||
data = db.get(path)
|
data = db.hgetall(path)
|
||||||
if check_content_exist(path):
|
if check_content_exist(path):
|
||||||
flash(data)
|
flash(data["content"])
|
||||||
return render_template("content.html.j2")
|
return render_template("content.html.j2")
|
||||||
else:
|
|
||||||
abort(404)
|
return abort(404)
|
||||||
|
|
|
@ -4,8 +4,18 @@
|
||||||
<input id="time" name="time">
|
<input id="time" name="time">
|
||||||
<input type="submit">
|
<input type="submit">
|
||||||
</form>
|
</form>
|
||||||
{% with messages = get_flashed_messages() %}
|
{% with messages = get_flashed_messages(category_filter=["create"]) %}
|
||||||
{% if messages %}
|
{% if messages[0] %}
|
||||||
<p>Paste available at : <a href="{{ messages[0] }}">{{ messages[0] }}</a></p>
|
<p>Paste available at : <a href="{{ request.base_url }}{{ messages[0] }}">{{ messages[0] }}</a></p>
|
||||||
|
{% endif %}
|
||||||
|
{% if messages[1] %}
|
||||||
|
<p>You can delete paste with : <a href="{{ request.base_url }}delete/{{ messages[0] }}?secret={{ messages[1] }}">delete</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
|
||||||
|
{% with messages = get_flashed_messages(category_filter=["delete"]) %}
|
||||||
|
{% if messages[0] %}
|
||||||
|
<p>Paste {{ messages[0] }} deleted </p>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
# ruff: noqa: S311
|
# ruff: noqa: S311
|
||||||
"""Shared utils function."""
|
"""Shared utils function."""
|
||||||
import random
|
import random
|
||||||
|
import secrets
|
||||||
import string
|
import string
|
||||||
|
|
||||||
from paste import config
|
from paste import config
|
||||||
|
@ -15,3 +16,11 @@ def generate_id() -> str:
|
||||||
random.choices(string.ascii_letters + string.digits,
|
random.choices(string.ascii_letters + string.digits,
|
||||||
k=config.URL_LENGTH)
|
k=config.URL_LENGTH)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_secret() -> str:
|
||||||
|
"""
|
||||||
|
Generate hex secret for delete paste.
|
||||||
|
:return: The secret.
|
||||||
|
"""
|
||||||
|
return secrets.token_hex()
|
||||||
|
|
Reference in a new issue