Add URL shortening functionality with FastAPI
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
app/__pycache__
|
app/__pycache__
|
||||||
venv/
|
venv/
|
||||||
|
test.txt
|
||||||
@@ -12,11 +12,11 @@ source venv/bin/activate
|
|||||||
```bash
|
```bash
|
||||||
uvicorn app.main:app --reload
|
uvicorn app.main:app --reload
|
||||||
```
|
```
|
||||||
Then check:
|
Then check:<br>
|
||||||
http://127.0.0.1:8000
|
http://127.0.0.1:8000<br>
|
||||||
http://127.0.0.1:8000/docs
|
http://127.0.0.1:8000/docs
|
||||||
|
|
||||||
Test Short URL Endpoint
|
Test Short URL Endpoint:<br>
|
||||||
http://127.0.0.1:8000/shorten
|
http://127.0.0.1:8000/shorten
|
||||||
|
|
||||||
## Running a test
|
## Running a test
|
||||||
|
|||||||
+52
-22
@@ -1,13 +1,18 @@
|
|||||||
from fastapi import FastAPI, HTTPException
|
from fastapi import FastAPI, HTTPException
|
||||||
from fastapi.responses import RedirectResponse
|
from fastapi.responses import RedirectResponse
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from pydantic import BaseModel
|
|
||||||
from app.utils import generate_short_code
|
from app.utils import generate_short_code
|
||||||
from dotenv import load_dotenv
|
from pydantic import BaseModel, HttpUrl
|
||||||
import os
|
from pydantic_settings import BaseSettings
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
class AppSettings(BaseSettings):
|
||||||
|
base_url: str = "http://localhost:8000"
|
||||||
|
database_url: str = "urls.db"
|
||||||
|
|
||||||
|
settings = AppSettings() # Load environment variables
|
||||||
|
|
||||||
# Set up logging configuration
|
# Set up logging configuration
|
||||||
logging.basicConfig(level=logging.INFO,
|
logging.basicConfig(level=logging.INFO,
|
||||||
format="%(asctime)s - %(levelname)s - %(message)s")
|
format="%(asctime)s - %(levelname)s - %(message)s")
|
||||||
@@ -17,8 +22,18 @@ logger = logging.getLogger(__name__)
|
|||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
conn = sqlite3.connect('urls.db')
|
conn = sqlite3.connect('urls.db')
|
||||||
conn.execute('''CREATE TABLE IF NOT EXISTS urls
|
conn = sqlite3.connect(settings.database_url)
|
||||||
(id INTEGER PRIMARY KEY, short_code TEXT, original_url TEXT, clicks INTEGER)''')
|
conn.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS urls (
|
||||||
|
id INTEGER PRIMARY KEY,
|
||||||
|
short_code TEXT UNIQUE,
|
||||||
|
original_url TEXT NOT NULL,
|
||||||
|
clicks INTEGER DEFAULT 0
|
||||||
|
);
|
||||||
|
""")
|
||||||
|
conn.execute("""
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_short_code ON urls(short_code);
|
||||||
|
""")
|
||||||
conn.commit()
|
conn.commit()
|
||||||
logger.info("Database initialized or already exists.")
|
logger.info("Database initialized or already exists.")
|
||||||
yield
|
yield
|
||||||
@@ -28,11 +43,10 @@ async def lifespan(app: FastAPI):
|
|||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan)
|
||||||
|
|
||||||
def get_db_connection():
|
def get_db_connection():
|
||||||
conn = sqlite3.connect('urls.db')
|
return sqlite3.connect(settings.database_url, check_same_thread=False)
|
||||||
return conn
|
|
||||||
|
|
||||||
class URLRequest(BaseModel):
|
class URLRequest(BaseModel):
|
||||||
url: str
|
url: HttpUrl
|
||||||
|
|
||||||
@app.get("/")
|
@app.get("/")
|
||||||
def home():
|
def home():
|
||||||
@@ -41,36 +55,52 @@ def home():
|
|||||||
|
|
||||||
@app.post("/shorten")
|
@app.post("/shorten")
|
||||||
def shorten_url(request: URLRequest):
|
def shorten_url(request: URLRequest):
|
||||||
logger.info(f"Request received to shorten URL: {request.url}")
|
|
||||||
short_code = generate_short_code()
|
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
|
|
||||||
while conn.execute("SELECT 1 FROM urls WHERE short_code = ?", (short_code,)).fetchone():
|
existing = conn.execute(
|
||||||
|
"SELECT short_code FROM urls WHERE original_url = ?",
|
||||||
|
(str(request.url),)
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
if existing:
|
||||||
|
conn.close()
|
||||||
|
return {"short_url": f"{settings.base_url}/{existing[0]}"}
|
||||||
|
|
||||||
|
short_code = generate_short_code()
|
||||||
|
while conn.execute(
|
||||||
|
"SELECT 1 FROM urls WHERE short_code = ?",
|
||||||
|
(short_code,)
|
||||||
|
).fetchone():
|
||||||
short_code = generate_short_code()
|
short_code = generate_short_code()
|
||||||
|
|
||||||
conn.execute("INSERT INTO urls (short_code, original_url, clicks) VALUES (?, ?, ?)",
|
conn.execute(
|
||||||
(short_code, request.url, 0))
|
"INSERT INTO urls (short_code, original_url, clicks) VALUES (?, ?, 0)",
|
||||||
|
(short_code, str(request.url))
|
||||||
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
short_url = f"{os.getenv('BASE_URL', 'http://localhost:8000')}/{short_code}"
|
return {"short_url": f"{settings.base_url}/{short_code}"}
|
||||||
logger.info(f"Shortened URL created: {short_url}")
|
|
||||||
return {"short_url": short_url}
|
|
||||||
|
|
||||||
@app.get("/{short_code}")
|
@app.get("/{short_code}")
|
||||||
def redirect_to_url(short_code: str):
|
def redirect_to_url(short_code: str):
|
||||||
logger.info(f"Redirect request received for short_code: {short_code}")
|
|
||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
url_data = conn.execute("SELECT original_url, clicks FROM urls WHERE short_code = ?", (short_code,)).fetchone()
|
url_data = conn.execute(
|
||||||
|
"SELECT original_url FROM urls WHERE short_code = ?",
|
||||||
|
(short_code,)
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
if url_data is None:
|
if url_data is None:
|
||||||
logger.warning(f"Shortened URL for {short_code} not found.")
|
conn.close()
|
||||||
raise HTTPException(status_code=404, detail="Shortened URL not found")
|
raise HTTPException(status_code=404, detail="Shortened URL not found")
|
||||||
|
|
||||||
original_url, clicks = url_data
|
original_url = url_data[0]
|
||||||
conn.execute("UPDATE urls SET clicks = ? WHERE short_code = ?", (clicks + 1, short_code))
|
|
||||||
|
conn.execute(
|
||||||
|
"UPDATE urls SET clicks = clicks + 1 WHERE short_code = ?",
|
||||||
|
(short_code,)
|
||||||
|
)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
logger.info(f"Redirecting to {original_url}. Total clicks: {clicks + 1}")
|
|
||||||
return RedirectResponse(url=original_url)
|
return RedirectResponse(url=original_url)
|
||||||
Reference in New Issue
Block a user