From b47ad46e41dc74b017d04b969fb7eac49dafbb7d Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 19 Jul 2026 21:19:24 -0700 Subject: [PATCH 1/6] Add paste create endpoint --- modules/sqlite_helpers.py | 18 ++++++++ requirements.txt | 3 +- test/test_pastes.py | 95 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 test/test_pastes.py diff --git a/modules/sqlite_helpers.py b/modules/sqlite_helpers.py index 45d0629..cd880db 100644 --- a/modules/sqlite_helpers.py +++ b/modules/sqlite_helpers.py @@ -45,6 +45,24 @@ def maybe_create_table(sqlite_file: str) -> bool: logger.exception("Unable to create urls table") return False +def insert_paste(sqlite_file: str, paste_id: str, title: str): + db = sqlite3.connect(sqlite_file) + cursor = db.cursor() + + try: + sql = "INSERT INTO pastes(paste_id, title) VALUES (?, ?)" + val = (paste_id, title) + cursor.execute(sql, val) + db.commit() + return True + except sqlite3.IntegrityError: + return False + except Exception: + logger.exception("Inserting paste had an error") + return False + finally: + cursor.close() + db.close() def insert_url(sqlite_file: str, url: str, alias: str, expiration_date: typing.Union[str, None] = None): db = sqlite3.connect(sqlite_file) diff --git a/requirements.txt b/requirements.txt index 2f47ee2..aad0663 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ mysql-connector-python==8.0.33 py-grpc-prometheus==0.7.0 pyqrcode==1.2.1 pypng==0.20220715.0 -pillow==10.2.0 \ No newline at end of file +pillow==10.2.0 +python-multipart==0.0.9 \ No newline at end of file diff --git a/test/test_pastes.py b/test/test_pastes.py new file mode 100644 index 0000000..6720c82 --- /dev/null +++ b/test/test_pastes.py @@ -0,0 +1,95 @@ +import os +import sqlite3 +import sys +import tempfile +import unittest +from pathlib import Path +from unittest import mock + +# this allows imports from the modules folder to work +sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +from fastapi.testclient import TestClient +from modules import sqlite_helpers + +# server.py requires command-line args when imported. +# These fake args let the test import server.py without crashing. +TEST_ROOT_DIR = tempfile.TemporaryDirectory() +TEST_DB_PATH = os.path.join(TEST_ROOT_DIR.name, "test.db") +TEST_QR_CACHE_DIR = tempfile.TemporaryDirectory() + +with mock.patch.object( + sys, + "argv", + [ + "server.py", + "--database-file-path", + TEST_DB_PATH, + "--qr-code-cache-path", + TEST_QR_CACHE_DIR.name, + "--qr-code-base-url", + "http://localhost:8000", + ], +): + import server + + +class TestPasteEndpoints(unittest.TestCase): + def test_create_paste_rejects_file_larger_than_10mb(self): + large_content = b"a" * ((10 * 1024 * 1024) + 1) + + with TestClient(server.app) as client: + response = client.post( + "/paste/create", + files={"file": ("large.txt", large_content, "text/plain")}, + ) + + self.assertEqual(response.status_code, 400) + + def test_create_paste_creates_file_and_database_row(self): + with tempfile.TemporaryDirectory() as tmp_root: + tmp_db_path = os.path.join(tmp_root, "test.db") + tmp_pastes_dir = os.path.join(tmp_root, "pastes") + + sqlite_helpers.maybe_create_table(tmp_db_path) + + with mock.patch.object(server, "DATABASE_FILE", tmp_db_path): + with mock.patch.object(server, "PASTES_DIR", Path(tmp_pastes_dir)): + with mock.patch("server.secrets.token_hex", return_value="fe80df"): + with TestClient(server.app) as client: + response = client.post( + "/paste/create", + files={ + "file": ( + "example.txt", + b"hello paste", + "text/plain", + ) + }, + ) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json()["paste_id"], "fe80df") + self.assertEqual(response.json()["filename"], "example.txt") + + paste_path = Path(tmp_pastes_dir) / "fe80df" + self.assertTrue(paste_path.exists()) + self.assertEqual(paste_path.read_bytes(), b"hello paste") + + db = sqlite3.connect(tmp_db_path) + cursor = db.cursor() + cursor.execute( + "SELECT paste_id, title FROM pastes WHERE paste_id = ?", + ("fe80df",), + ) + row = cursor.fetchone() + cursor.close() + db.close() + + self.assertIsNotNone(row) + self.assertEqual(row[0], "fe80df") + self.assertEqual(row[1], "example.txt") + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file From 1e3d00f0e8395b8e9409cfc0bfc2da9376f2175a Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 19 Jul 2026 21:35:30 -0700 Subject: [PATCH 2/6] Add paste create endpoint --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index aad0663..280a957 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,4 +6,5 @@ py-grpc-prometheus==0.7.0 pyqrcode==1.2.1 pypng==0.20220715.0 pillow==10.2.0 -python-multipart==0.0.9 \ No newline at end of file +python-multipart==0.0.9 +httpx==0.24.1 \ No newline at end of file From 948b5a0172b6296a0086a712dc1edd759ff05e04 Mon Sep 17 00:00:00 2001 From: evan Date: Fri, 28 Aug 2026 08:30:18 -0700 Subject: [PATCH 3/6] rebase moment --- modules/sqlite_helpers.py | 19 -------- test/test_pastes.py | 95 --------------------------------------- 2 files changed, 114 deletions(-) delete mode 100644 test/test_pastes.py diff --git a/modules/sqlite_helpers.py b/modules/sqlite_helpers.py index cd880db..c30b98f 100644 --- a/modules/sqlite_helpers.py +++ b/modules/sqlite_helpers.py @@ -45,25 +45,6 @@ def maybe_create_table(sqlite_file: str) -> bool: logger.exception("Unable to create urls table") return False -def insert_paste(sqlite_file: str, paste_id: str, title: str): - db = sqlite3.connect(sqlite_file) - cursor = db.cursor() - - try: - sql = "INSERT INTO pastes(paste_id, title) VALUES (?, ?)" - val = (paste_id, title) - cursor.execute(sql, val) - db.commit() - return True - except sqlite3.IntegrityError: - return False - except Exception: - logger.exception("Inserting paste had an error") - return False - finally: - cursor.close() - db.close() - def insert_url(sqlite_file: str, url: str, alias: str, expiration_date: typing.Union[str, None] = None): db = sqlite3.connect(sqlite_file) cursor = db.cursor() diff --git a/test/test_pastes.py b/test/test_pastes.py deleted file mode 100644 index 6720c82..0000000 --- a/test/test_pastes.py +++ /dev/null @@ -1,95 +0,0 @@ -import os -import sqlite3 -import sys -import tempfile -import unittest -from pathlib import Path -from unittest import mock - -# this allows imports from the modules folder to work -sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) - -from fastapi.testclient import TestClient -from modules import sqlite_helpers - -# server.py requires command-line args when imported. -# These fake args let the test import server.py without crashing. -TEST_ROOT_DIR = tempfile.TemporaryDirectory() -TEST_DB_PATH = os.path.join(TEST_ROOT_DIR.name, "test.db") -TEST_QR_CACHE_DIR = tempfile.TemporaryDirectory() - -with mock.patch.object( - sys, - "argv", - [ - "server.py", - "--database-file-path", - TEST_DB_PATH, - "--qr-code-cache-path", - TEST_QR_CACHE_DIR.name, - "--qr-code-base-url", - "http://localhost:8000", - ], -): - import server - - -class TestPasteEndpoints(unittest.TestCase): - def test_create_paste_rejects_file_larger_than_10mb(self): - large_content = b"a" * ((10 * 1024 * 1024) + 1) - - with TestClient(server.app) as client: - response = client.post( - "/paste/create", - files={"file": ("large.txt", large_content, "text/plain")}, - ) - - self.assertEqual(response.status_code, 400) - - def test_create_paste_creates_file_and_database_row(self): - with tempfile.TemporaryDirectory() as tmp_root: - tmp_db_path = os.path.join(tmp_root, "test.db") - tmp_pastes_dir = os.path.join(tmp_root, "pastes") - - sqlite_helpers.maybe_create_table(tmp_db_path) - - with mock.patch.object(server, "DATABASE_FILE", tmp_db_path): - with mock.patch.object(server, "PASTES_DIR", Path(tmp_pastes_dir)): - with mock.patch("server.secrets.token_hex", return_value="fe80df"): - with TestClient(server.app) as client: - response = client.post( - "/paste/create", - files={ - "file": ( - "example.txt", - b"hello paste", - "text/plain", - ) - }, - ) - - self.assertEqual(response.status_code, 200) - self.assertEqual(response.json()["paste_id"], "fe80df") - self.assertEqual(response.json()["filename"], "example.txt") - - paste_path = Path(tmp_pastes_dir) / "fe80df" - self.assertTrue(paste_path.exists()) - self.assertEqual(paste_path.read_bytes(), b"hello paste") - - db = sqlite3.connect(tmp_db_path) - cursor = db.cursor() - cursor.execute( - "SELECT paste_id, title FROM pastes WHERE paste_id = ?", - ("fe80df",), - ) - row = cursor.fetchone() - cursor.close() - db.close() - - self.assertIsNotNone(row) - self.assertEqual(row[0], "fe80df") - self.assertEqual(row[1], "example.txt") - - -if __name__ == "__main__": - unittest.main() \ No newline at end of file From cacc9c201908fc3ead2d4a925372aadaa974a229 Mon Sep 17 00:00:00 2001 From: evan Date: Fri, 28 Aug 2026 08:30:55 -0700 Subject: [PATCH 4/6] rebase moment 2 --- requirements.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 280a957..2f47ee2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,4 @@ mysql-connector-python==8.0.33 py-grpc-prometheus==0.7.0 pyqrcode==1.2.1 pypng==0.20220715.0 -pillow==10.2.0 -python-multipart==0.0.9 -httpx==0.24.1 \ No newline at end of file +pillow==10.2.0 \ No newline at end of file From c8a714ce105f0f08cd308a734939c89858c65e47 Mon Sep 17 00:00:00 2001 From: evan Date: Fri, 28 Aug 2026 08:34:06 -0700 Subject: [PATCH 5/6] return id and bytes in success --- modules/sqlite_helpers.py | 1 + server.py | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/sqlite_helpers.py b/modules/sqlite_helpers.py index c30b98f..45d0629 100644 --- a/modules/sqlite_helpers.py +++ b/modules/sqlite_helpers.py @@ -45,6 +45,7 @@ def maybe_create_table(sqlite_file: str) -> bool: logger.exception("Unable to create urls table") return False + def insert_url(sqlite_file: str, url: str, alias: str, expiration_date: typing.Union[str, None] = None): db = sqlite3.connect(sqlite_file) cursor = db.cursor() diff --git a/server.py b/server.py index bd72314..15586e4 100644 --- a/server.py +++ b/server.py @@ -171,7 +171,7 @@ async def create_paste(request: Request): api_key = request.headers.get("x-api-key") if CLEEZY_PASTE_API_KEY is None: - logging.warning("CLEEZY_PASTE_API_KEY isn't set, skipping api key check") + logging.warning("CLEEZY_PASTE_API_KEY isn't set, skipping api key check for /paste/create") elif api_key != CLEEZY_PASTE_API_KEY: raise HTTPException(status_code=401, detail=f"Invalid API Key '{api_key}'") @@ -203,9 +203,8 @@ async def create_paste(request: Request): paste_path.write_bytes(text_bytes) return { - "status": "success", "id": paste_id, - "url": f"/paste/{paste_id}" + "size_bytes": len(text_bytes) } From 91c03017bec17afc106c9196e8cdc23c949d3542 Mon Sep 17 00:00:00 2001 From: evan Date: Fri, 28 Aug 2026 08:45:59 -0700 Subject: [PATCH 6/6] return html formatted response, see #77 --- README.md | 3 ++- modules/sqlite_helpers.py | 13 +++++++++++++ server.py | 13 ++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index be37285..59997d2 100644 --- a/README.md +++ b/README.md @@ -51,12 +51,13 @@ curl -X POST "http://localhost:8000/paste/create" \ -d '{"title": "My First Paste", "text": "hello2"}' # example response is -# {"status":"success","id":"6556e","url":"/paste/6556e"} +# {"id":"6556e","size_bytes":6} ``` ### To view a paste ```sh # put the paste id after the `/paste/` in the url, like below +# you can also open the url in the browser curl http://localhost:8000/paste/6556e ``` diff --git a/modules/sqlite_helpers.py b/modules/sqlite_helpers.py index 45d0629..5bb39b4 100644 --- a/modules/sqlite_helpers.py +++ b/modules/sqlite_helpers.py @@ -215,3 +215,16 @@ def insert_paste(sqlite_file: str, paste_id: str, title: str): finally: cursor.close() db.close() + + +def get_paste(sqlite_file: str, paste_id: str): + db = sqlite3.connect(sqlite_file) + cursor = db.cursor() + try: + sql = "SELECT title FROM pastes WHERE id = ?" + cursor.execute(sql, (paste_id,)) + result = cursor.fetchone() + return result[0] + except Exception: + logger.exception(f"Getting paste {paste_id} had an error") + return None diff --git a/server.py b/server.py index 15586e4..9b59a76 100644 --- a/server.py +++ b/server.py @@ -213,7 +213,18 @@ async def view_paste(paste_id: str): paste_path = PASTES_DIR / paste_id if not paste_path.exists(): raise HTTPException(status_code=HttpResponse.NOT_FOUND.code) - return PlainTextResponse(paste_path.read_text(encoding="utf-8")) + paste_title = sqlite_helpers.get_paste(DATABASE_FILE, paste_id) + + return HTMLResponse( + f""" + + + {paste_title} + + +
{paste_path.read_text(encoding="utf-8")}
+ +""") @app.get("/qr/{alias}") async def qr(alias: str):