From e1d514f589a8f2bc509a303e3f8f23f8ad0890fc Mon Sep 17 00:00:00 2001 From: johnnyn <125083987+johnnyn7@users.noreply.github.com> Date: Thu, 19 Jun 2025 20:54:32 -0700 Subject: [PATCH 1/8] Added Expiration Date and Handling backend stores expiration date and proper handling --- modules/sqlite_helpers.py | 22 +++++++++++++++++++++- server.py | 10 ++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/modules/sqlite_helpers.py b/modules/sqlite_helpers.py index 2849a1a..6578594 100644 --- a/modules/sqlite_helpers.py +++ b/modules/sqlite_helpers.py @@ -9,6 +9,25 @@ logger = logging.getLogger(__name__) +# def parse_datetime(dt_str): +# for format in ("%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%d %H:%M:%S"): +# try: +# return datetime.strptime(dt_str, format) +# except ValueError: +# continue +# raise ValueError(f"time data {dt_str!r} doesn't match expected format") + +def delete_expired_urls(sqlite_file: str): + db = sqlite3.connect(sqlite_file) + cursor = db.cursor() + + cursor.execute(""" + DELETE FROM urls WHERE expires_at IS NOT NULL + AND expires_at < datetime('now') + """) + db.commit() + # db.close() + def maybe_create_table(sqlite_file: str) -> bool: db = sqlite3.connect(sqlite_file) cursor = db.cursor() @@ -83,6 +102,7 @@ def get_urls(sqlite_file, page=0, search=None, sort_by="created_at", order="DESC "alias": row[2], "created_at": row[3], "used": row[4], + "expires_at": row[5] } url_array.append(url_data) except KeyError: @@ -128,7 +148,7 @@ def maybe_delete_expired_url(sqlite_file, sqlite_row) -> bool: #returns True if expiration_datetime = None # sqlite_row[5] represents the expiration datetime e.g., "2024-11-04 18:05:24.006593" if sqlite_row[5] is not None: - expiration_datetime = datetime.strptime(sqlite_row[5], "%Y-%m-%d %H:%M:%S.%f") + expiration_datetime = datetime.fromisoformat(sqlite_row[5]) expiration_datetime = expiration_datetime.replace(tzinfo=utc_tz) now = datetime.now(tz=utc_tz) diff --git a/server.py b/server.py index 1740502..e8b99d1 100644 --- a/server.py +++ b/server.py @@ -14,7 +14,7 @@ import modules.sqlite_helpers as sqlite_helpers from modules.constants import HttpResponse, http_code_to_enum from modules.metrics import MetricsHandler -from modules.sqlite_helpers import increment_used_column +from modules.sqlite_helpers import increment_used_column, delete_expired_urls from modules.cache import Cache from modules.qr_code import QRCode @@ -68,7 +68,7 @@ async def create_url(request: Request): alias = generate_alias(urljson["url"]) if not alias.isalnum(): raise ValueError("alias must only contain alphanumeric characters") - expiration_date = urljson.get("expiration_date") + expiration_date = urljson.get("expires_at") with MetricsHandler.query_time.labels("create").time(): response = sqlite_helpers.insert_url( @@ -99,6 +99,7 @@ async def get_urls( sort_by: str = "created_at", order: str = "DESC", ): + sqlite_helpers.delete_expired_urls(DATABASE_FILE) valid_sort_attributes = {"id", "url", "alias", "created_at", "used"} if order not in {"DESC", "ASC"}: raise HTTPException(status_code=400, detail="Invalid order") @@ -125,9 +126,14 @@ async def get_urls( @app.get("/find/{alias}") async def get_url(alias: str): + sqlite_helpers.delete_expired_urls(DATABASE_FILE) logging.debug(f"/find called with alias: {alias}") url_output = cache.find(alias) # try to find url in cache if url_output is not None: + valid = sqlite_helpers.get_url(DATABASE_FILE, alias) + if valid is None: + cache.delete(alias) + raise HTTPException(status_code=HttpResponse.NOT_FOUND.code) alias_queue.put(alias) return RedirectResponse(url_output) From 6c6a7b9d2d2577fc342792f8f1b5c3db862cbeec Mon Sep 17 00:00:00 2001 From: johnnyn <125083987+johnnyn7@users.noreply.github.com> Date: Thu, 19 Jun 2025 20:58:02 -0700 Subject: [PATCH 2/8] fixed small formatting issues --- modules/sqlite_helpers.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/modules/sqlite_helpers.py b/modules/sqlite_helpers.py index 6578594..74a043b 100644 --- a/modules/sqlite_helpers.py +++ b/modules/sqlite_helpers.py @@ -9,24 +9,16 @@ logger = logging.getLogger(__name__) -# def parse_datetime(dt_str): -# for format in ("%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%d %H:%M:%S"): -# try: -# return datetime.strptime(dt_str, format) -# except ValueError: -# continue -# raise ValueError(f"time data {dt_str!r} doesn't match expected format") - def delete_expired_urls(sqlite_file: str): db = sqlite3.connect(sqlite_file) cursor = db.cursor() cursor.execute(""" - DELETE FROM urls WHERE expires_at IS NOT NULL + DELETE FROM urls + WHERE expires_at IS NOT NULL AND expires_at < datetime('now') """) db.commit() - # db.close() def maybe_create_table(sqlite_file: str) -> bool: db = sqlite3.connect(sqlite_file) From bd6f092dce087531a1c31573f453fce147125957 Mon Sep 17 00:00:00 2001 From: johnnyn <125083987+johnnyn7@users.noreply.github.com> Date: Thu, 26 Jun 2025 12:47:10 -0700 Subject: [PATCH 3/8] Changes to Handling Expired URLs Removed purge deletes and changed to checking if url is expired and if so, deletes and displays 404 --- modules/sqlite_helpers.py | 11 ----------- server.py | 4 +--- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/modules/sqlite_helpers.py b/modules/sqlite_helpers.py index 74a043b..d956992 100644 --- a/modules/sqlite_helpers.py +++ b/modules/sqlite_helpers.py @@ -9,17 +9,6 @@ logger = logging.getLogger(__name__) -def delete_expired_urls(sqlite_file: str): - db = sqlite3.connect(sqlite_file) - cursor = db.cursor() - - cursor.execute(""" - DELETE FROM urls - WHERE expires_at IS NOT NULL - AND expires_at < datetime('now') - """) - db.commit() - def maybe_create_table(sqlite_file: str) -> bool: db = sqlite3.connect(sqlite_file) cursor = db.cursor() diff --git a/server.py b/server.py index e8b99d1..f0d875f 100644 --- a/server.py +++ b/server.py @@ -14,7 +14,7 @@ import modules.sqlite_helpers as sqlite_helpers from modules.constants import HttpResponse, http_code_to_enum from modules.metrics import MetricsHandler -from modules.sqlite_helpers import increment_used_column, delete_expired_urls +from modules.sqlite_helpers import increment_used_column from modules.cache import Cache from modules.qr_code import QRCode @@ -99,7 +99,6 @@ async def get_urls( sort_by: str = "created_at", order: str = "DESC", ): - sqlite_helpers.delete_expired_urls(DATABASE_FILE) valid_sort_attributes = {"id", "url", "alias", "created_at", "used"} if order not in {"DESC", "ASC"}: raise HTTPException(status_code=400, detail="Invalid order") @@ -126,7 +125,6 @@ async def get_urls( @app.get("/find/{alias}") async def get_url(alias: str): - sqlite_helpers.delete_expired_urls(DATABASE_FILE) logging.debug(f"/find called with alias: {alias}") url_output = cache.find(alias) # try to find url in cache if url_output is not None: From aad0cfca0db9c5e9fd3549b0d0ee535dbb1f328d Mon Sep 17 00:00:00 2001 From: johnnyn <125083987+johnnyn7@users.noreply.github.com> Date: Sun, 29 Jun 2025 20:10:20 -0700 Subject: [PATCH 4/8] fixed expiration datetime format comment --- modules/sqlite_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sqlite_helpers.py b/modules/sqlite_helpers.py index d956992..abef103 100644 --- a/modules/sqlite_helpers.py +++ b/modules/sqlite_helpers.py @@ -127,7 +127,7 @@ def maybe_delete_expired_url(sqlite_file, sqlite_row) -> bool: #returns True if utc_tz = ZoneInfo('UTC') expiration_datetime = None - # sqlite_row[5] represents the expiration datetime e.g., "2024-11-04 18:05:24.006593" + # sqlite_row[5] represents the expiration datetime in UTC timezone e.g., "2024-11-04 18:05:24" if sqlite_row[5] is not None: expiration_datetime = datetime.fromisoformat(sqlite_row[5]) expiration_datetime = expiration_datetime.replace(tzinfo=utc_tz) From 9f02dbec4c0513e379aeba80dddd22c05efa319c Mon Sep 17 00:00:00 2001 From: johnnyn <125083987+johnnyn7@users.noreply.github.com> Date: Tue, 1 Jul 2025 19:18:52 -0700 Subject: [PATCH 5/8] Accept Dates in ISOString Format Added small formatting to accept ISOStrings --- modules/sqlite_helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/sqlite_helpers.py b/modules/sqlite_helpers.py index abef103..7bab433 100644 --- a/modules/sqlite_helpers.py +++ b/modules/sqlite_helpers.py @@ -43,6 +43,8 @@ def insert_url(sqlite_file: str, url: str, alias: str, expiration_date: typing.U cursor = db.cursor() timestamp = datetime.now() if expiration_date is not None: + if isinstance(expiration_date, str) and expiration_date.endswith('Z'): + expiration_date = expiration_date[:-1] + '+00:00' expiration_date = datetime.fromisoformat(expiration_date) try: sql = "INSERT INTO urls(url, alias, created_at, expires_at) VALUES (?, ?, ?, ?)" From 40f19da9dac04ff18018d9346f7ebcc4b5a8c966 Mon Sep 17 00:00:00 2001 From: johnnyn <125083987+johnnyn7@users.noreply.github.com> Date: Fri, 4 Jul 2025 20:08:16 -0700 Subject: [PATCH 6/8] Multiline comment for parsed date formats --- modules/sqlite_helpers.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/sqlite_helpers.py b/modules/sqlite_helpers.py index 7bab433..c05ffbb 100644 --- a/modules/sqlite_helpers.py +++ b/modules/sqlite_helpers.py @@ -129,7 +129,12 @@ def maybe_delete_expired_url(sqlite_file, sqlite_row) -> bool: #returns True if utc_tz = ZoneInfo('UTC') expiration_datetime = None - # sqlite_row[5] represents the expiration datetime in UTC timezone e.g., "2024-11-04 18:05:24" + # sqlite_row[5] represents the expiration datetime in UTC timezone, e.g. 2024-11-04 05:09:00+00:00 + # The following date and datetime formats are now supported: + # 2024-11-04 + # 2024-11-04T18:05:24 + # 2024-11-04T18:05:24.123456 + # 2024-11-04T18:05:24+02:00 if sqlite_row[5] is not None: expiration_datetime = datetime.fromisoformat(sqlite_row[5]) expiration_datetime = expiration_datetime.replace(tzinfo=utc_tz) From ae6a3ecfbb7509f681aedb02f9d60f58584d3013 Mon Sep 17 00:00:00 2001 From: johnnyn <125083987+johnnyn7@users.noreply.github.com> Date: Fri, 4 Jul 2025 20:24:30 -0700 Subject: [PATCH 7/8] small comment format fix --- modules/sqlite_helpers.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/sqlite_helpers.py b/modules/sqlite_helpers.py index c05ffbb..d44b1ff 100644 --- a/modules/sqlite_helpers.py +++ b/modules/sqlite_helpers.py @@ -129,7 +129,7 @@ def maybe_delete_expired_url(sqlite_file, sqlite_row) -> bool: #returns True if utc_tz = ZoneInfo('UTC') expiration_datetime = None - # sqlite_row[5] represents the expiration datetime in UTC timezone, e.g. 2024-11-04 05:09:00+00:00 + # sqlite_row[5] represents the expiration datetime in UTC timezone e.g., "2024-11-04 05:09:00+00:00" # The following date and datetime formats are now supported: # 2024-11-04 # 2024-11-04T18:05:24 From c1e8809ab414dee38ab0a4ed1563063aeba3466a Mon Sep 17 00:00:00 2001 From: johnnyn <125083987+johnnyn7@users.noreply.github.com> Date: Mon, 7 Jul 2025 13:29:03 -0700 Subject: [PATCH 8/8] Sorting feature for Expires At column --- server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server.py b/server.py index f0d875f..044d0ac 100644 --- a/server.py +++ b/server.py @@ -99,7 +99,7 @@ async def get_urls( sort_by: str = "created_at", order: str = "DESC", ): - valid_sort_attributes = {"id", "url", "alias", "created_at", "used"} + valid_sort_attributes = {"id", "url", "alias", "created_at", "expires_at", "used"} if order not in {"DESC", "ASC"}: raise HTTPException(status_code=400, detail="Invalid order") if sort_by not in valid_sort_attributes: