Skip to content

Commit 363a6b5

Browse files
[RTY-260025]: refactor(api): unify API definition for dev and api commands
1 parent d17e0c6 commit 363a6b5

6 files changed

Lines changed: 395 additions & 449 deletions

File tree

app/api/fast_api.py

Lines changed: 9 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,20 @@
1-
import os
2-
import re
1+
# app/api/fast_api.py
32
import traceback
4-
from datetime import datetime, timezone
5-
from typing import TYPE_CHECKING
6-
7-
from fastapi import APIRouter, FastAPI, Request
8-
from fastapi.responses import HTMLResponse, JSONResponse
9-
from pydantic import BaseModel, Field
10-
11-
if TYPE_CHECKING:
12-
from pymongo.errors import PyMongoError
13-
else:
14-
try:
15-
from pymongo.errors import PyMongoError
16-
except ImportError:
17-
18-
class PyMongoError(Exception):
19-
pass
20-
3+
from fastapi import FastAPI, Request
4+
from fastapi.responses import JSONResponse
215

226
from app import __version__
23-
from app.utils import db
24-
from app.utils.cache import get_short_from_cache, set_cache_pair
25-
from app.utils.helper import generate_code, is_valid_url, sanitize_url
26-
27-
SHORT_CODE_PATTERN = re.compile(r"^[A-Za-z0-9]{6}$")
28-
MAX_URL_LENGTH = 2048
7+
from app.routes import api_router
298

309
app = FastAPI(
3110
title="Tiny API",
3211
version=__version__,
3312
description="Tiny URL Shortener API built with FastAPI",
13+
docs_url="/docs",
14+
redoc_url="/redoc",
15+
openapi_url="/openapi.json",
3416
)
3517

36-
api_v1 = APIRouter(prefix=os.getenv("API_VERSION", "/api/v1"), tags=["v1"])
37-
3818

3919
@app.exception_handler(Exception)
4020
async def global_exception_handler(request: Request, exc: Exception):
@@ -45,167 +25,5 @@ async def global_exception_handler(request: Request, exc: Exception):
4525
)
4626

4727

48-
class ShortenRequest(BaseModel):
49-
url: str = Field(..., examples=["https://abcdkbd.com"])
50-
51-
52-
class ShortenResponse(BaseModel):
53-
success: bool = True
54-
input_url: str
55-
short_code: str
56-
created_on: datetime
57-
58-
59-
class ErrorResponse(BaseModel):
60-
success: bool = False
61-
error: str
62-
input_url: str
63-
message: str
64-
65-
66-
class VersionResponse(BaseModel):
67-
version: str
68-
69-
70-
# -------------------------------------------------
71-
# Home
72-
# -------------------------------------------------
73-
@app.get("/", response_class=HTMLResponse, tags=["Home"])
74-
async def read_root(_: Request):
75-
return """
76-
<html>
77-
<head>
78-
<title>🌙 tiny API 🌙</title>
79-
<style>
80-
body {
81-
margin: 0;
82-
height: 100vh;
83-
display: flex;
84-
align-items: center;
85-
justify-content: center;
86-
background: linear-gradient(180deg, #0b1220, #050b14);
87-
font-family: "Poppins", system-ui, Arial, sans-serif;
88-
color: #f8fafc;
89-
}
90-
.card {
91-
background: rgba(255, 255, 255, 0.06);
92-
backdrop-filter: blur(12px);
93-
border-radius: 16px;
94-
padding: 50px 40px;
95-
text-align: center;
96-
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
97-
max-width: 520px;
98-
width: 90%;
99-
}
100-
h1 {
101-
font-size: 2.8em;
102-
margin-bottom: 12px;
103-
background: linear-gradient(90deg, #5ab9ff, #4cb39f);
104-
-webkit-background-clip: text;
105-
-webkit-text-fill-color: transparent;
106-
}
107-
p {
108-
font-size: 1.1em;
109-
color: #cbd5e1;
110-
margin-bottom: 30px;
111-
}
112-
a {
113-
display: inline-block;
114-
padding: 14px 26px;
115-
border-radius: 12px;
116-
background: linear-gradient(90deg, #4cb39f, #5ab9ff);
117-
color: #fff;
118-
text-decoration: none;
119-
font-weight: 700;
120-
}
121-
</style>
122-
</head>
123-
<body>
124-
<div class="card">
125-
<h1>🚀 tiny API</h1>
126-
<p>FastAPI backend for the Tiny URL shortener</p>
127-
<a href="/docs">View API Documentation</a>
128-
</div>
129-
</body>
130-
</html>
131-
"""
132-
133-
134-
@api_v1.post("/shorten", response_model=ShortenResponse, status_code=201)
135-
def shorten_url(payload: ShortenRequest):
136-
print(" SHORTEN ENDPOINT HIT ", payload.url)
137-
raw_url = payload.url.strip()
138-
139-
if len(raw_url) > MAX_URL_LENGTH:
140-
return JSONResponse(
141-
status_code=413, content={"success": False, "input_url": payload.url}
142-
)
143-
144-
original_url = sanitize_url(raw_url)
145-
146-
if not is_valid_url(original_url):
147-
return JSONResponse(
148-
status_code=400,
149-
content={
150-
"success": False,
151-
"error": "INVALID_URL",
152-
"input_url": payload.url,
153-
"message": "Invalid URL",
154-
},
155-
)
156-
157-
if db.collection is None:
158-
cached_short = get_short_from_cache(original_url)
159-
short_code = cached_short or generate_code()
160-
set_cache_pair(short_code, original_url)
161-
return {
162-
"success": True,
163-
"input_url": original_url,
164-
"short_code": short_code,
165-
"created_on": datetime.now(timezone.utc),
166-
}
167-
168-
try:
169-
existing = db.collection.find_one({"original_url": original_url})
170-
except PyMongoError:
171-
existing = None
172-
173-
if existing:
174-
return {
175-
"success": True,
176-
"input_url": original_url,
177-
"short_code": existing["short_code"],
178-
"created_on": existing["created_at"],
179-
}
180-
181-
short_code = generate_code()
182-
try:
183-
db.collection.insert_one(
184-
{
185-
"short_code": short_code,
186-
"original_url": original_url,
187-
"created_at": datetime.now(timezone.utc),
188-
}
189-
)
190-
except PyMongoError:
191-
pass
192-
193-
return {
194-
"success": True,
195-
"input_url": original_url,
196-
"short_code": short_code,
197-
"created_on": datetime.now(timezone.utc),
198-
}
199-
200-
201-
@app.get("/version")
202-
def api_version():
203-
return {"version": __version__}
204-
205-
206-
@api_v1.get("/help")
207-
def get_help():
208-
return {"message": "Welcome to Tiny API. Visit /docs for API documentation."}
209-
210-
211-
app.include_router(api_v1)
28+
# ✅ Single source of truth for API routes only
29+
app.include_router(api_router)

0 commit comments

Comments
 (0)