From d50fdc5d16c95e7a93dca286b74f0d037302e415 Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 28 Jan 2024 17:57:30 +0530 Subject: [PATCH] URL Expiration --- liberate.db | Bin 16384 -> 0 bytes pdm.lock | 13 ++++++++++++- pyproject.toml | 3 ++- requirements.txt | 2 ++ src/link_liberate/database.py | 16 ++++++++++++++++ src/link_liberate/main.py | 5 +++-- src/link_liberate/templates/base.html | 16 ++++++++-------- tests/test_main.py | 5 ++--- 8 files changed, 45 insertions(+), 15 deletions(-) delete mode 100644 liberate.db diff --git a/liberate.db b/liberate.db deleted file mode 100644 index df453ba343c968624b2764af81213d66c3616cc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI$O-sWt7zgmQvCbAW-o{SL(Sl6JK$u>vT9{f}tyAEC4y%0KcPgsH7iUJ7&5P$##AOHafKmY;|fB*y_@GF4^O{I&vbfx&s zn%DRHmB2so+SSOb1%C6QQ|WenQ5*d(u_KI6~O)fAO2Kq83GW100bZa0SG_<0uX=z1RyXb@YDVO71A`Qw*UYD diff --git a/pdm.lock b/pdm.lock index 99b9d3e..38cb569 100644 --- a/pdm.lock +++ b/pdm.lock @@ -5,7 +5,18 @@ groups = ["default", "hooks", "style", "tests", "typing"] strategy = ["cross_platform", "inherit_metadata"] lock_version = "4.4.1" -content_hash = "sha256:f00869933960b9690aa0bd8bc3f9dbcdd356591acf3a21be2298597a80db06ff" +content_hash = "sha256:1bcc74c1878e0617b03e13969e187d0fee770d59e5f68872c054654cb43954b6" + +[[package]] +name = "aiosqlite" +version = "0.19.0" +requires_python = ">=3.7" +summary = "asyncio bridge to the standard sqlite3 module" +groups = ["default"] +files = [ + {file = "aiosqlite-0.19.0-py3-none-any.whl", hash = "sha256:edba222e03453e094a3ce605db1b970c4b3376264e56f32e2a4959f948d66a96"}, + {file = "aiosqlite-0.19.0.tar.gz", hash = "sha256:95ee77b91c8d2808bd08a59fbebf66270e9090c3d92ffbf260dc0db0b979577d"}, +] [[package]] name = "annotated-types" diff --git a/pyproject.toml b/pyproject.toml index ae21840..a887d1b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,13 +16,14 @@ dependencies = [ "SQLAlchemy>=2.0.25", "psycopg2-binary>=2.9.9", "validators>=0.22.0", + "aiosqlite>=0.19.0", ] requires-python = ">=3.11.6" readme = "README.md" license = {text = "MIT"} [tool.pdm.scripts] -start = "uvicorn src.link_liberate.main:app --host 0.0.0.0 --port 8080 --workers 1 --reload" +start = "uvicorn src.link_liberate.main:app --host 0.0.0.0 --port 8080 --workers 4 --reload" dev = "uvicorn src.link_liberate.main:app --host 0.0.0.0 --port 8080 --reload" test = "pytest" mypy = "mypy src/link_liberate" diff --git a/requirements.txt b/requirements.txt index e297652..99cd7f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -62,3 +62,5 @@ wrapt==1.16.0 SQLAlchemy~=2.0.25 validators~=0.22.0 + +aiosqlite~=0.19.0 diff --git a/src/link_liberate/database.py b/src/link_liberate/database.py index 87dd619..f6db66b 100644 --- a/src/link_liberate/database.py +++ b/src/link_liberate/database.py @@ -2,6 +2,9 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker +import aiosqlite +import asyncio + SQLALCHEMY_DATABASE_URL = "sqlite:///./liberate.db" engine = create_engine( @@ -19,3 +22,16 @@ def get_db(): yield db finally: db.close() + + +async def create_async_session(): + async with aiosqlite.connect(SQLALCHEMY_DATABASE_URL) as db: + yield db + + +async def expire_uuid(uuid): + print("Expiring uuid:", uuid) + await asyncio.sleep(60) # Default expiration time is 60 mins + async with create_async_session() as db: + await db.execute("DELETE FROM liberatedlinks WHERE uuid = ?", (uuid,)) + await db.commit() diff --git a/src/link_liberate/main.py b/src/link_liberate/main.py index bb74f87..0b03628 100644 --- a/src/link_liberate/main.py +++ b/src/link_liberate/main.py @@ -16,12 +16,12 @@ from slowapi.util import get_remote_address from sqlalchemy.orm import Session - +from asyncio import create_task from starlette.templating import _TemplateResponse from .utils import generate_uuid, make_proper_url, check_link from .models import Base, LiberatedLink -from .database import engine, get_db +from .database import engine, get_db, expire_uuid limiter = Limiter(key_func=get_remote_address) app: FastAPI = FastAPI(title="link-liberate") @@ -77,6 +77,7 @@ async def web_post( db.add(new_liberated_link) db.commit() db.refresh(new_liberated_link) + await create_task(expire_uuid(uuid)) context = {"link": link, "short": f"{BASE_URL}/{uuid}"} except Exception as e: raise HTTPException( diff --git a/src/link_liberate/templates/base.html b/src/link_liberate/templates/base.html index 0a8f0cf..e7815c0 100644 --- a/src/link_liberate/templates/base.html +++ b/src/link_liberate/templates/base.html @@ -11,7 +11,7 @@ margin: 0; padding: 0; } - + .container { max-width: 600px; align-content: center; @@ -21,20 +21,20 @@ border-radius: 8px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); } - + a { text-decoration: none !important; } - + h1 { text-align: center; color: #333; } - + .form-container { text-align: center; } - + .link-input { width: 100%; padding: 10px; @@ -43,7 +43,7 @@ border: 1px solid #ccc; border-radius: 4px; } - + .submit-button { background-color: #4CAF50; color: #fff; @@ -53,11 +53,11 @@ cursor: pointer; font-size: 16px; } - + .submit-button:hover { background-color: #45a049; } - + {% block title %} diff --git a/tests/test_main.py b/tests/test_main.py index 6e0e6f9..a3bfe3f 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -21,16 +21,15 @@ def test_web(): def test_web_post(mocker): # Patch the get_db function to return the db mock - mocker.patch('src.link_liberate.database.Session_Local', return_value=Mock()) + mocker.patch("src.link_liberate.database.Session_Local", return_value=Mock()) client_mock = TestClient(app) response = client_mock.post("/liberate", data={"content": "valid_content"}) assert response.status_code == 200 - def test_get_link(mocker): # Patch the get_db function to return the db mock - mocker.patch('src.link_liberate.database.Session_Local', return_value=Mock()) + mocker.patch("src.link_liberate.database.Session_Local", return_value=Mock()) client_mock = TestClient(app) response = client_mock.get("/valid_uuid", follow_redirects=False) assert response.status_code == 301