Skip to content

Commit c2a52eb

Browse files
[RTY-260028]: make qr constant
1 parent b10c2f0 commit c2a52eb

File tree

6 files changed

+92
-64
lines changed

6 files changed

+92
-64
lines changed

app/main.py

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@
66

77
from fastapi import FastAPI, Request
88

9-
# from fastapi.responses import JSONResponse
9+
from fastapi.responses import JSONResponse
1010
from fastapi.staticfiles import StaticFiles
1111
from starlette.middleware.sessions import SessionMiddleware
1212

1313
# from fastapi.exceptions import RequestValidationError
1414
# from starlette.exceptions import HTTPException as StarletteHTTPException
15+
from fastapi.exceptions import HTTPException as FastAPIHTTPException
1516
from fastapi.templating import Jinja2Templates
1617

1718
from app.routes import ui_router
@@ -24,6 +25,7 @@
2425
from app.utils.config import (
2526
CACHE_TTL,
2627
SESSION_SECRET,
28+
QR_DIR,
2729
)
2830

2931

@@ -96,34 +98,65 @@ async def lifespan(app: FastAPI):
9698
app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET)
9799
templates = Jinja2Templates(directory="app/templates")
98100

101+
# Mount QR static files
99102
BASE_DIR = Path(__file__).resolve().parent
100-
STATIC_DIR = BASE_DIR / "static"
101103

102-
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")
103-
104-
# QR codes are now served from /qr, which maps to assets/images/qr in the project root
105-
PROJECT_ROOT = BASE_DIR.parent
106-
QR_DIR = PROJECT_ROOT / "assets" / "images" / "qr"
107-
app.mount("/qr", StaticFiles(directory=QR_DIR), name="qr")
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+
)
108117

109118
# -----------------------------
110119
# Global error handler
111120
# -----------------------------
112-
# app.exception_handler(Exception)
113-
# sync def global_exception_handler(request: Request, exc: Exception):
114-
# traceback.print_exc()
115-
# return JSONResponse(
121+
# @app.exception_handler(Exception)
122+
# async def global_exception_handler(request: Request, exc: Exception):
123+
# traceback.print_exc()
124+
# return JSONResponse(
116125
# status_code=500,
117126
# content={"success": False, "error": "INTERNAL_SERVER_ERROR"},
118127
# )
119128

120129

121-
@app.exception_handler(404)
122-
async def custom_404_handler(request: Request, exc):
123-
return templates.TemplateResponse(
124-
"404.html",
125-
{"request": request},
126-
status_code=404,
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+
157+
return JSONResponse(
158+
status_code=exc.status_code,
159+
content={"success": False, "error": exc.detail},
127160
)
128161

129162

app/routes.py

Lines changed: 6 additions & 12 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 (
@@ -35,13 +34,12 @@
3534
remove_cache_key,
3635
rev_cache,
3736
)
38-
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
3938
from app.utils.helper import generate_code, is_valid_url, sanitize_url, format_date
4039
from app.utils.qr import generate_qr_with_logo
4140

42-
BASE_DIR = Path(__file__).resolve().parent
43-
templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
44-
41+
# templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
42+
templates = Jinja2Templates(directory="app/templates")
4543
# Routers
4644
ui_router = APIRouter()
4745
api_router = APIRouter()
@@ -68,10 +66,7 @@ async def index(request: Request):
6866
if qr_enabled and new_short_url and short_code:
6967
qr_data = new_short_url
7068
qr_filename = f"{short_code}.png"
71-
PROJECT_ROOT = BASE_DIR.parent # go from app/ → project root
72-
qr_dir = PROJECT_ROOT / "assets" / "images" / "qr"
73-
qr_dir.mkdir(parents=True, exist_ok=True)
74-
generate_qr_with_logo(qr_data, str(qr_dir / qr_filename))
69+
generate_qr_with_logo(qr_data, str(QR_DIR / qr_filename))
7570
qr_image = f"/qr/{qr_filename}"
7671

7772
recent_urls = db.get_recent_urls(MAX_RECENT_URLS) or get_recent_from_cache(
@@ -139,7 +134,6 @@ async def create_short_url(
139134
return RedirectResponse("/", status_code=status.HTTP_303_SEE_OTHER)
140135

141136

142-
@ui_router.get("/recent", response_class=HTMLResponse)
143137
@ui_router.get("/history", response_class=HTMLResponse)
144138
async def recent_urls(request: Request):
145139
recent_urls_list = db.get_recent_urls(MAX_RECENT_URLS) or get_recent_from_cache(
@@ -229,14 +223,15 @@ def redirect_short_ui(short_code: str, background_tasks: BackgroundTasks):
229223
raise HTTPException(status_code=404, detail="Page not found")
230224

231225

232-
@ui_router.delete("/recent/{short_code}")
226+
@ui_router.delete("/history/{short_code}")
233227
def delete_recent_api(short_code: str):
234228
recent = get_recent_from_cache(MAX_RECENT_URLS) or []
235229
removed_from_cache = False
236230

237231
for i, item in enumerate(recent):
238232
code = item.get("short_code") or item.get("code")
239233
if code == short_code:
234+
recent.pop(i) # remove from cache
240235
removed_from_cache = True
241236
break
242237

@@ -246,7 +241,6 @@ def delete_recent_api(short_code: str):
246241
if db_available:
247242
db_deleted = db.delete_by_short_code(short_code)
248243

249-
# ✅ If nothing was deleted anywhere → 404
250244
if not removed_from_cache and not db_deleted:
251245
raise HTTPException(
252246
status_code=404, detail=f"short_code '{short_code}' not found"

app/static/css/tiny.css

Lines changed: 23 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,7 @@ body.dark-theme .app-header {
334334
justify-content: space-between;
335335
margin-bottom: 1rem;
336336
padding: 0 0.5rem;
337+
align-items: center;
337338
}
338339

339340
.scroll-container {
@@ -364,15 +365,6 @@ body.dark-theme .app-header {
364365
font-weight: bold;
365366
}
366367

367-
/*.original-url {
368-
color: var(--text-secondary);
369-
font-size: 0.8rem;
370-
white-space: nowrap;
371-
overflow: hidden;
372-
text-overflow: ellipsis;
373-
}*/
374-
375-
376368
.hero {
377369
text-align: center;
378370
margin: 40px 0;
@@ -748,22 +740,6 @@ body.dark-theme .footer-bottom a {
748740
}
749741

750742

751-
/* ===============================
752-
HOME PAGE FIX — LONG URL WRAP
753-
(does NOT affect table)
754-
================================= */
755-
/*.recent-tray .recent-item .original-url,
756-
.recent-tray .recent-item .original-url a {
757-
white-space: normal !important;
758-
word-break: break-word !important;
759-
overflow-wrap: anywhere !important;
760-
line-break: anywhere !important;
761-
display: block;
762-
}
763-
764-
.recent-tray .recent-item {
765-
max-width: 20px;
766-
}*/
767743

768744

769745
/* allow wrapping */
@@ -789,4 +765,26 @@ body.dark-theme .footer-bottom a {
789765
min-width: 0;
790766
/* allows shrinking inside flex/grid */
791767
max-width: 100%;
768+
}
769+
770+
/* ===============================
771+
VIEW HISTORY COLOR BY THEME
772+
================================= */
773+
774+
body.dark-theme .history-link {
775+
color: #ffffff;
776+
}
777+
778+
body.light-theme .history-link {
779+
color: #000000;
780+
}
781+
782+
.history-link {
783+
text-decoration: line;
784+
font-weight: 600;
785+
transition: 0.2s ease;
786+
}
787+
788+
.history-link:hover {
789+
opacity: 0.7;
792790
}

app/templates/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ <h1>Shorten Your Links</h1>
3333
</div> {% endif %} <div class="recent-tray">
3434
<div class="recent-header">
3535
<h3>Recently Shortened</h3>
36-
<a href="/recent" class="history-link" target="_blank">View History →</a>
36+
<a href="/history" class="history-link" target="_blank">View History →</a>
3737
</div>
3838
<div class="scroll-container"> {% for url in urls %} <div class="recent-item">
3939
<div class="short-code">/{{ url.short_code }}</div>

app/templates/recent.html

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ <h1>Recent Shortened URLs</h1>
4242
function deleteUrl(shortCode, btn) {
4343
if (!confirm("Are you sure you want to delete this URL?")) return;
4444

45-
fetch(`/recent/${shortCode}`, {
45+
fetch(`/history/${shortCode}`, {
4646
method: "DELETE"
4747
})
4848
.then(async (res) => {
@@ -52,20 +52,15 @@ <h1>Recent Shortened URLs</h1>
5252
throw new Error("Delete failed");
5353
}
5454

55-
return fetch(`/delete/${shortCode}`, { method: "POST" });
56-
})
57-
.then(res => {
58-
if (!res.ok) throw new Error("Delete failed");
59-
55+
// ✅ remove row from table
6056
const row = btn.closest("tr");
6157
if (row) row.remove();
6258
})
6359
.catch((err) => {
6460
console.error(err);
6561
alert("Failed to delete URL");
6662
});
67-
} // ✅ <-- THIS WAS MISSING
68-
63+
}
6964

7065
function toggleRecent() {
7166
const box = document.getElementById('recentList');

app/utils/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import os
2+
from pathlib import Path
23

34
# -------------------------
45
# Helpers
@@ -79,3 +80,10 @@ def _get_int(key: str, default: int) -> int:
7980
# -------------------------
8081
SHORT_CODE_LENGTH = 6
8182
MAX_URL_LENGTH = 2048
83+
84+
# for making the qr constant
85+
# Base project paths
86+
BASE_DIR = Path(__file__).resolve().parent.parent # app/
87+
PROJECT_ROOT = BASE_DIR.parent # project root
88+
# QR directory constant
89+
QR_DIR = PROJECT_ROOT / "assets" / "images" / "qr"

0 commit comments

Comments
 (0)