Skip to content

Commit 75e6b57

Browse files
Merge pull request #30 from recursivezero/bugfix/RTY-260028
[WIP] UI changes
2 parents c95db2a + c2a52eb commit 75e6b57

File tree

18 files changed

+1801
-1316
lines changed

18 files changed

+1801
-1316
lines changed

.env.sample

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ MODE=local
22
MONGO_URI=mongodb://<username>:<password>@127.0.0.1:27017/?authSource=admin&retryWrites=true&w=majority
33
DOMAIN=https://localhost:8001
44
PORT=8001
5-
API_VERSION=""
6-
APP_NAMe="LOCAL"
5+
API_VERSION="/api/v1"
6+
APP_NAME="LOCAL"

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,6 @@ poetry.lock
5959
*.tmp
6060
*.temp
6161
*.bak
62+
63+
64+
assets/images/qr/*

app/main.py

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22
from contextlib import asynccontextmanager
33
from pathlib import Path
44
import logging
5-
import traceback
65
import asyncio
76

87
from fastapi import FastAPI, Request
8+
99
from fastapi.responses import JSONResponse
1010
from fastapi.staticfiles import StaticFiles
1111
from starlette.middleware.sessions import SessionMiddleware
1212

13+
# from fastapi.exceptions import RequestValidationError
14+
# from starlette.exceptions import HTTPException as StarletteHTTPException
15+
from fastapi.exceptions import HTTPException as FastAPIHTTPException
16+
from fastapi.templating import Jinja2Templates
17+
1318
from app.routes import ui_router
1419
from app.utils import db
1520
from app.utils.cache import cleanup_expired
@@ -20,6 +25,7 @@
2025
from app.utils.config import (
2126
CACHE_TTL,
2227
SESSION_SECRET,
28+
QR_DIR,
2329
)
2430

2531

@@ -90,22 +96,67 @@ async def lifespan(app: FastAPI):
9096

9197
app = FastAPI(title="TinyURL", lifespan=lifespan)
9298
app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET)
99+
templates = Jinja2Templates(directory="app/templates")
93100

101+
# Mount QR static files
94102
BASE_DIR = Path(__file__).resolve().parent
95-
STATIC_DIR = BASE_DIR / "static"
96-
97-
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
98103

104+
# Mount QR static files
105+
app.mount(
106+
"/static",
107+
StaticFiles(directory=str(BASE_DIR / "static")),
108+
name="static",
109+
)
110+
# Ensure QR directory exists at startup
111+
QR_DIR.mkdir(parents=True, exist_ok=True)
112+
app.mount(
113+
"/qr",
114+
StaticFiles(directory=str(QR_DIR)),
115+
name="qr",
116+
)
99117

100118
# -----------------------------
101119
# Global error handler
102120
# -----------------------------
103-
@app.exception_handler(Exception)
104-
async def global_exception_handler(request: Request, exc: Exception):
105-
traceback.print_exc()
121+
# @app.exception_handler(Exception)
122+
# async def global_exception_handler(request: Request, exc: Exception):
123+
# traceback.print_exc()
124+
# return JSONResponse(
125+
# status_code=500,
126+
# content={"success": False, "error": "INTERNAL_SERVER_ERROR"},
127+
# )
128+
129+
130+
# @app.exception_handler(404)
131+
# async def custom_404_handler(request: Request, exc):
132+
# return templates.TemplateResponse(
133+
# "404.html",
134+
# {"request": request},
135+
# status_code=404,
136+
# )
137+
138+
139+
@app.exception_handler(FastAPIHTTPException)
140+
async def http_exception_handler(request: Request, exc: FastAPIHTTPException):
141+
142+
# If it's API/UI route → return JSON
143+
if request.url.path.startswith("/cache") or request.url.path.startswith("/api"):
144+
return JSONResponse(
145+
status_code=exc.status_code,
146+
content={"error": exc.detail},
147+
)
148+
149+
# If it's browser route → return HTML page
150+
if exc.status_code == 404:
151+
return templates.TemplateResponse(
152+
"404.html",
153+
{"request": request},
154+
status_code=404,
155+
)
156+
106157
return JSONResponse(
107-
status_code=500,
108-
content={"success": False, "error": "INTERNAL_SERVER_ERROR"},
158+
status_code=exc.status_code,
159+
content={"success": False, "error": exc.detail},
109160
)
110161

111162

app/routes.py

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import os
22
from datetime import datetime, timezone
3-
from pathlib import Path
43
from typing import Optional
54
from app.utils.cache import list_cache_clean, clear_cache
65
from fastapi import (
@@ -22,6 +21,7 @@
2221
from fastapi.templating import Jinja2Templates
2322
from pydantic import BaseModel, Field
2423

24+
2525
from app import __version__
2626
from app.utils import db
2727
from app.utils.cache import (
@@ -34,13 +34,12 @@
3434
remove_cache_key,
3535
rev_cache,
3636
)
37-
from app.utils.config import DOMAIN, MAX_RECENT_URLS, CACHE_PURGE_TOKEN
37+
from app.utils.config import DOMAIN, MAX_RECENT_URLS, CACHE_PURGE_TOKEN, QR_DIR
3838
from app.utils.helper import generate_code, is_valid_url, sanitize_url, format_date
3939
from app.utils.qr import generate_qr_with_logo
4040

41-
BASE_DIR = Path(__file__).resolve().parent
42-
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
43-
41+
# templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
42+
templates = Jinja2Templates(directory="app/templates")
4443
# Routers
4544
ui_router = APIRouter()
4645
api_router = APIRouter()
@@ -67,10 +66,8 @@ async def index(request: Request):
6766
if qr_enabled and new_short_url and short_code:
6867
qr_data = new_short_url
6968
qr_filename = f"{short_code}.png"
70-
qr_dir = BASE_DIR / "static" / "qr"
71-
qr_dir.mkdir(parents=True, exist_ok=True)
72-
generate_qr_with_logo(qr_data, str(qr_dir / qr_filename))
73-
qr_image = f"/static/qr/{qr_filename}"
69+
generate_qr_with_logo(qr_data, str(QR_DIR / qr_filename))
70+
qr_image = f"/qr/{qr_filename}"
7471

7572
recent_urls = db.get_recent_urls(MAX_RECENT_URLS) or get_recent_from_cache(
7673
MAX_RECENT_URLS
@@ -137,7 +134,7 @@ async def create_short_url(
137134
return RedirectResponse("/", status_code=status.HTTP_303_SEE_OTHER)
138135

139136

140-
@ui_router.get("/recent", response_class=HTMLResponse)
137+
@ui_router.get("/history", response_class=HTMLResponse)
141138
async def recent_urls(request: Request):
142139
recent_urls_list = db.get_recent_urls(MAX_RECENT_URLS) or get_recent_from_cache(
143140
MAX_RECENT_URLS
@@ -222,17 +219,19 @@ def redirect_short_ui(short_code: str, background_tasks: BackgroundTasks):
222219
set_cache_pair(short_code, original_url)
223220
return RedirectResponse(original_url)
224221

225-
return PlainTextResponse("Invalid short URL", status_code=404)
222+
# return PlainTextResponse("Invalid short URL", status_code=404)
223+
raise HTTPException(status_code=404, detail="Page not found")
226224

227225

228-
@ui_router.delete("/recent/{short_code}")
226+
@ui_router.delete("/history/{short_code}")
229227
def delete_recent_api(short_code: str):
230228
recent = get_recent_from_cache(MAX_RECENT_URLS) or []
231229
removed_from_cache = False
232230

233231
for i, item in enumerate(recent):
234232
code = item.get("short_code") or item.get("code")
235233
if code == short_code:
234+
recent.pop(i) # remove from cache
236235
removed_from_cache = True
237236
break
238237

@@ -242,7 +241,6 @@ def delete_recent_api(short_code: str):
242241
if db_available:
243242
db_deleted = db.delete_by_short_code(short_code)
244243

245-
# ✅ If nothing was deleted anywhere → 404
246244
if not removed_from_cache and not db_deleted:
247245
raise HTTPException(
248246
status_code=404, detail=f"short_code '{short_code}' not found"

0 commit comments

Comments
 (0)