Compare commits
3 commits
main
...
ada/better
Author | SHA1 | Date | |
---|---|---|---|
67cad30e87 | |||
97ac3b1175 | |||
190e4ac2b9 |
10 changed files with 134 additions and 59 deletions
11
README.md
11
README.md
|
@ -1,11 +0,0 @@
|
||||||
# A code share paste
|
|
||||||
|
|
||||||
## Run
|
|
||||||
Require redis and python, install python requirements with `pip install -r requirements.txt`. Run with `flask --app paste run` (add `--debug` for devlopement)
|
|
||||||
You can configure with multiple environnement variables.
|
|
||||||
- PASTE_REDIS_HOST: redis host (default localhost)
|
|
||||||
- PASTE_REDIS_PORT: redis port (default 6379)
|
|
||||||
- PASTE_REDIS_USER: redis username (default none)
|
|
||||||
- PASTE_REDIS_PASSWORD: redis password (default none)
|
|
||||||
- PASTE_URL_LENGTH: length for url used by paste
|
|
||||||
- PASTE_SECRET_KEY: secret key for flask, don't use the default value (generate a safe value with `openssl rand -hex 16`).
|
|
|
@ -2,7 +2,7 @@
|
||||||
from os import getenv
|
from os import getenv
|
||||||
|
|
||||||
REDIS_HOST = getenv("PASTE_REDIS_HOST") or "localhost"
|
REDIS_HOST = getenv("PASTE_REDIS_HOST") or "localhost"
|
||||||
REDIS_PORT = getenv("PASTE_REDIS_PORT") or "6379"
|
REDIS_PORT = getenv("PASTE_REDIS_HOST") or "6379"
|
||||||
REDIS_USER = getenv("PASTE_REDIS_USER") or None
|
REDIS_USER = getenv("PASTE_REDIS_USER") or None
|
||||||
REDIS_PASSWORD = getenv("PASTE_REDIS_PASSWORD") or None
|
REDIS_PASSWORD = getenv("PASTE_REDIS_PASSWORD") or None
|
||||||
URL_LENGTH = getenv("PASTE_URL_LENGTH") or 4
|
URL_LENGTH = getenv("PASTE_URL_LENGTH") or 4
|
||||||
|
|
|
@ -31,7 +31,7 @@ def insert_content_db(url_id: str, expiration: int, content: str, secret: str) -
|
||||||
|
|
||||||
:param secret: Secret key for delete url
|
: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. Set 0 if no expiration.
|
:param expiration: Content expiration in second.
|
||||||
:param content: Paste content
|
:param content: Paste content
|
||||||
:return: None
|
:return: None
|
||||||
"""
|
"""
|
||||||
|
@ -40,7 +40,6 @@ def insert_content_db(url_id: str, expiration: int, content: str, secret: str) -
|
||||||
data = {"content": content, "secret": secret}
|
data = {"content": content, "secret": secret}
|
||||||
for key, key_content in data.items():
|
for key, key_content in data.items():
|
||||||
db.hset(url_id, key, key_content)
|
db.hset(url_id, key, key_content)
|
||||||
if expiration != 0:
|
|
||||||
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
|
||||||
|
|
|
@ -12,7 +12,7 @@ from flask import (
|
||||||
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, generate_secret, get_expiration_time
|
from paste.utils import generate_id, generate_secret
|
||||||
|
|
||||||
home = Blueprint("home", __name__, url_prefix="/")
|
home = Blueprint("home", __name__, url_prefix="/")
|
||||||
|
|
||||||
|
@ -49,12 +49,11 @@ def create() -> Response:
|
||||||
"""
|
"""
|
||||||
content = request.form.get("content")
|
content = request.form.get("content")
|
||||||
time = request.form.get("time")
|
time = request.form.get("time")
|
||||||
time = 0 if time == "" else int(time)
|
time = 86400 if time == "" else int(time)
|
||||||
|
|
||||||
flash_data = create_paste(content, time)
|
flash_data = create_paste(content, time)
|
||||||
flash(flash_data["url_id"], category="create")
|
flash(flash_data["url_id"], category="create")
|
||||||
flash(flash_data["secret"], category="create")
|
flash(flash_data["secret"], category="create")
|
||||||
|
|
||||||
return redirect(url_for("home.homepage"))
|
return redirect(url_for("home.homepage"))
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,7 +74,7 @@ def delete_paste(path: str) -> Response:
|
||||||
|
|
||||||
|
|
||||||
@home.route("/<path:path>")
|
@home.route("/<path:path>")
|
||||||
def get_content(path: str) -> Response | str:
|
def get_content(path: str) -> str:
|
||||||
"""
|
"""
|
||||||
Return paste for asked url.
|
Return paste for asked url.
|
||||||
:param path: Requested path.
|
:param path: Requested path.
|
||||||
|
@ -85,13 +84,10 @@ def get_content(path: str) -> Response | str:
|
||||||
data = db.hgetall(path)
|
data = db.hgetall(path)
|
||||||
if check_content_exist(path):
|
if check_content_exist(path):
|
||||||
flash(data["content"])
|
flash(data["content"])
|
||||||
cache_time = get_expiration_time(path)
|
return render_template("content.html.j2")
|
||||||
current_response = Response(render_template("content.html.j2"))
|
|
||||||
if cache_time == -1:
|
|
||||||
current_response.headers["Cache-Control"] = "public"
|
|
||||||
|
|
||||||
else:
|
|
||||||
current_response.headers["Cache-Control"] = f"public, {cache_time}"
|
|
||||||
return current_response
|
|
||||||
|
|
||||||
return abort(404)
|
return abort(404)
|
||||||
|
|
||||||
|
@home.route("/about")
|
||||||
|
def about() -> str:
|
||||||
|
return render_template("about.html.j2")
|
65
paste/static/css/main.css
Normal file
65
paste/static/css/main.css
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
:root {
|
||||||
|
--main-width: 85vw;
|
||||||
|
--bg-color: white;
|
||||||
|
--text-color: #212121;
|
||||||
|
--a-color: #7e22ce;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--bg-color: #212121;
|
||||||
|
--text-color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* large screen */
|
||||||
|
@media only screen and (min-width: 868px) {
|
||||||
|
:root {
|
||||||
|
--main-width: 50vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: sans-serif;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
color: var(--text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
body {
|
||||||
|
max-width: var(--main-width);
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
background-color: var(--bg-color);
|
||||||
|
line-height: 1.65
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu li {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--a-color);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: black;
|
||||||
|
border-bottom: .2rem solid dimgrey;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
background-color: transparent;
|
||||||
|
resize: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 80vh;
|
||||||
|
}
|
4
paste/templates/about.html.j2
Normal file
4
paste/templates/about.html.j2
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{% extends "base.html.j2" %}
|
||||||
|
{% block content %}
|
||||||
|
<p>Gnous/Paste</p>
|
||||||
|
{% endblock %}
|
22
paste/templates/base.html.j2
Normal file
22
paste/templates/base.html.j2
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Gnous/Paste</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}"/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="menu">
|
||||||
|
<ul class="menu">
|
||||||
|
<li class="menu" {% if request.path == "/" %}><a href="{{ url_for("home.about") }}">About</a>{% elif request.path == "/about" %}<a href="{{ url_for("home.homepage") }}">Home</a>{% endif %}</li>
|
||||||
|
|
|
||||||
|
<li class="menu"><a href="https://git.gnous.eu/gnouseu/paste">Source</a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<main>
|
||||||
|
{% block content %}
|
||||||
|
{% endblock %}
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,21 +1,31 @@
|
||||||
<form id="paste-content" method="POST" accept-charset="UTF-8" action="{{ url_for('home.create') }}">
|
{% extends "base.html.j2" %}
|
||||||
<textarea name="content" form="paste-content">Enter text here...</textarea>
|
{% block content %}
|
||||||
<label for="time">Time in second</label>
|
{% with messages = get_flashed_messages(category_filter=["create"]) %}
|
||||||
<input id="time" name="time">
|
<div class="infobox">
|
||||||
<input type="submit">
|
|
||||||
</form>
|
|
||||||
{% with messages = get_flashed_messages(category_filter=["create"]) %}
|
|
||||||
{% if messages[0] %}
|
{% if messages[0] %}
|
||||||
<p>Paste available at : <a href="{{ request.base_url }}{{ messages[0] }}">{{ messages[0] }}</a></p>
|
<p>Paste : <a href="{{ request.base_url }}{{ messages[0] }}">{{ messages[0] }}</a> | <a
|
||||||
|
href="{{ request.base_url }}delete/{{ messages[0] }}?secret={{ messages[1] }}">delete</a></p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if messages[1] %}
|
</div>
|
||||||
<p>You can delete paste with : <a href="{{ request.base_url }}delete/{{ messages[0] }}?secret={{ messages[1] }}">delete</a></p>
|
{% endwith %}
|
||||||
{% endif %}
|
|
||||||
{% endwith %}
|
|
||||||
|
|
||||||
{% with messages = get_flashed_messages(category_filter=["delete"]) %}
|
{% with messages = get_flashed_messages(category_filter=["delete"]) %}
|
||||||
{% if messages[0] %}
|
{% if messages[0] %}
|
||||||
<p>Paste {{ messages[0] }} deleted </p>
|
<p>Paste {{ messages[0] }} deleted </p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
|
<form class="container" id="paste-content" method="POST" accept-charset="UTF-8"
|
||||||
|
action="{{ url_for('home.create') }}">
|
||||||
|
<textarea name="content" form="paste-content" placeholder="Enter your text here"></textarea>
|
||||||
|
<input list="time" name="time">
|
||||||
|
<datalist id="time">
|
||||||
|
<option value="604800">7 days</option>
|
||||||
|
<option value="86400">1 day</option>
|
||||||
|
<option value="43200">12 hours</option>
|
||||||
|
<option value="21600">6 hours</option>
|
||||||
|
<option value="3600">1 hour</option>
|
||||||
|
<option value="600">10 minutes</option>
|
||||||
|
</datalist>
|
||||||
|
<input type="submit">
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import secrets
|
||||||
import string
|
import string
|
||||||
|
|
||||||
from paste import config
|
from paste import config
|
||||||
from paste.db import connect_redis
|
|
||||||
|
|
||||||
|
|
||||||
def generate_id() -> str:
|
def generate_id() -> str:
|
||||||
|
@ -25,13 +24,3 @@ def generate_secret() -> str:
|
||||||
:return: The secret.
|
:return: The secret.
|
||||||
"""
|
"""
|
||||||
return secrets.token_hex()
|
return secrets.token_hex()
|
||||||
|
|
||||||
|
|
||||||
def get_expiration_time(key: str) -> int:
|
|
||||||
"""
|
|
||||||
Get key expiration time
|
|
||||||
:param key: Key to check
|
|
||||||
:return: Expiration time in second.
|
|
||||||
"""
|
|
||||||
db = connect_redis()
|
|
||||||
return db.ttl(key)
|
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
-r requirements.txt
|
flask==2.3.3
|
||||||
|
redis==5.0.0
|
||||||
ruff==0.0.291
|
ruff==0.0.291
|
||||||
|
|
Reference in a new issue