Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ MODE=local
MONGO_URI=mongodb://<username>:<password>@127.0.0.1:27017/?authSource=admin&retryWrites=true&w=majority
DOMAIN=https://localhost:8001
PORT=8001
API_VERSION=""
APP_NAMe="LOCAL"
API_VERSION="/api/v1"
APP_NAME="LOCAL"
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,6 @@ poetry.lock
*.tmp
*.temp
*.bak


assets/images/qr/*
34 changes: 26 additions & 8 deletions app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,18 @@
from contextlib import asynccontextmanager
from pathlib import Path
import logging
import traceback
import asyncio

from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

# from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from starlette.middleware.sessions import SessionMiddleware

# from fastapi.exceptions import RequestValidationError
# from starlette.exceptions import HTTPException as StarletteHTTPException
from fastapi.templating import Jinja2Templates

from app.routes import ui_router
from app.utils import db
from app.utils.cache import cleanup_expired
Expand Down Expand Up @@ -90,22 +94,36 @@ async def lifespan(app: FastAPI):

app = FastAPI(title="TinyURL", lifespan=lifespan)
app.add_middleware(SessionMiddleware, secret_key=SESSION_SECRET)
templates = Jinja2Templates(directory="app/templates")

BASE_DIR = Path(__file__).resolve().parent
STATIC_DIR = BASE_DIR / "static"

app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")

# QR codes are now served from /qr, which maps to assets/images/qr in the project root
PROJECT_ROOT = BASE_DIR.parent
QR_DIR = PROJECT_ROOT / "assets" / "images" / "qr"
app.mount("/qr", StaticFiles(directory=QR_DIR), name="qr")

# -----------------------------
# Global error handler
# -----------------------------
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
traceback.print_exc()
return JSONResponse(
status_code=500,
content={"success": False, "error": "INTERNAL_SERVER_ERROR"},
# app.exception_handler(Exception)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reason to comment these code?

# sync def global_exception_handler(request: Request, exc: Exception):
# traceback.print_exc()
# return JSONResponse(
# status_code=500,
# content={"success": False, "error": "INTERNAL_SERVER_ERROR"},
# )


@app.exception_handler(404)
async def custom_404_handler(request: Request, exc):
return templates.TemplateResponse(
"404.html",
{"request": request},
status_code=404,
)


Expand Down
10 changes: 7 additions & 3 deletions app/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from fastapi.templating import Jinja2Templates
from pydantic import BaseModel, Field


from app import __version__
from app.utils import db
from app.utils.cache import (
Expand Down Expand Up @@ -67,10 +68,11 @@ async def index(request: Request):
if qr_enabled and new_short_url and short_code:
qr_data = new_short_url
qr_filename = f"{short_code}.png"
qr_dir = BASE_DIR / "static" / "qr"
PROJECT_ROOT = BASE_DIR.parent # go from app/ → project root
qr_dir = PROJECT_ROOT / "assets" / "images" / "qr"
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create QR_DIR constant and use

qr_dir.mkdir(parents=True, exist_ok=True)
generate_qr_with_logo(qr_data, str(qr_dir / qr_filename))
qr_image = f"/static/qr/{qr_filename}"
qr_image = f"/qr/{qr_filename}"

recent_urls = db.get_recent_urls(MAX_RECENT_URLS) or get_recent_from_cache(
MAX_RECENT_URLS
Expand Down Expand Up @@ -138,6 +140,7 @@ async def create_short_url(


@ui_router.get("/recent", response_class=HTMLResponse)
@ui_router.get("/history", response_class=HTMLResponse)
async def recent_urls(request: Request):
recent_urls_list = db.get_recent_urls(MAX_RECENT_URLS) or get_recent_from_cache(
MAX_RECENT_URLS
Expand Down Expand Up @@ -222,7 +225,8 @@ def redirect_short_ui(short_code: str, background_tasks: BackgroundTasks):
set_cache_pair(short_code, original_url)
return RedirectResponse(original_url)

return PlainTextResponse("Invalid short URL", status_code=404)
# return PlainTextResponse("Invalid short URL", status_code=404)
raise HTTPException(status_code=404, detail="Page not found")


@ui_router.delete("/recent/{short_code}")
Expand Down
Loading