feat: add database connection with modified endpoints

This commit is contained in:
2025-07-22 17:05:21 +00:00
parent 28314e3865
commit 83d828c935
10 changed files with 111 additions and 17 deletions

4
.env.example Normal file
View File

@@ -0,0 +1,4 @@
DATABASE_URL=postgresql://user:password@localhost:5432/mydatabase
# [dev, stage, prod]
ENVIRONMENT=dev

6
.gitignore vendored
View File

@@ -131,7 +131,11 @@ dmypy.json
.profiling/
# vscode settings
.vscode/
.vscode/*
!.vscode/extensions.json
!.vscode/rest-client.env.json
!.vscode/http
!.vscode/sql
# FastAPI specifics
# .env files used for storing environment variables should not be committed to version control

12
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,12 @@
{
"recommendations": [
"ms-python.python",
"ms-python.debugpy",
"mtxr.sqltools",
"mtxr.sqltools-driver-pg",
"genieai.chatgpt-vscode",
"humao.rest-client"
],
"unwantedRecommendations": [
]
}

10
.vscode/http/url.rest vendored Normal file
View File

@@ -0,0 +1,10 @@
### redirect_to_original_url
GET http://localhost:8000/
### create_url_shortcode
POST http://localhost:8000/shorten
Content-Type: application/json
{
"url": "https://google.com/"
}

1
.vscode/sql/minxadb-dev.session.sql vendored Normal file
View File

@@ -0,0 +1 @@
SELECT * FROM url_mapping

16
dependencies/database.py vendored Normal file
View File

@@ -0,0 +1,16 @@
import os
from databases import Database
from sqlalchemy import create_engine, MetaData
from sqlalchemy.orm import declarative_base, sessionmaker
DATABASE_URL = os.getenv("DATABASE_URL")
# for Alembic migrations and sync operations
engine = create_engine(DATABASE_URL)
# to use with async requests
database = Database(DATABASE_URL)
metadata = MetaData()
Base = declarative_base(metadata=metadata)

9
dependencies/models.py vendored Normal file
View File

@@ -0,0 +1,9 @@
from sqlalchemy import Column, Integer, String
from dependencies.database import Base
class UrlMapping(Base):
__tablename__ = "url_mapping"
id = Column(Integer, primary_key=True, index=True)
url = Column(String, nullable=False)
shortcode = Column(String, index=True, unique=True, nullable=False)

62
main.py
View File

@@ -1,27 +1,57 @@
from fastapi import FastAPI
from .models import UrlPayload
from .utils import generate_shortcode
import os
from sqlalchemy.exc import IntegrityError
from fastapi import FastAPI, HTTPException
from fastapi.responses import RedirectResponse
from dependencies.models import UrlMapping
from dependencies.database import database, Base, engine
from schemas import UrlPayload
from utils import generate_shortcode
app = FastAPI()
# temporary data store
url_mapping = {}
@app.on_event("startup")
async def startup():
await database.connect()
Base.metadata.create_all(bind=engine) # TODO: create all tables if not present (ONLY DEVELOPMENT)
@app.on_event("shutdown")
async def shutdown():
await database.disconnect()
@app.get("/{shortcode}")
async def get_original_url(shortcode: str):
if shortcode in url_mapping:
return {"original_url": url_mapping[shortcode]}
async def redirect_to_original_url(shortcode: str):
# TODO: do validation check for valid shortcode
if not shortcode:
raise HTTPException(status_code=400, detail="Shortcode is not valid")
query = UrlMapping.__table__.select().where(UrlMapping.shortcode == shortcode)
url_mapping = await database.fetch_one(query)
if url_mapping:
return RedirectResponse(url=url_mapping.url, status_code=302)
else:
raise HTTPException(status_code=404, detail="Shortcode not found")
@app.post("/shorten/")
async def create_short_url(url_payload: UrlPayload):
original_url = url_payload.url
@app.post("/shorten")
async def create_url_shortcode(url_payload: UrlPayload):
original_url = str(url_payload.url) # convert to string from HttpUrl
shortcode = generate_shortcode()
if shortcode in url_mapping:
raise HTTPException(status_code=409, detail="Shortcode already in use")
try:
query = UrlMapping.__table__.insert().values({
"url": original_url,
"shortcode": shortcode
})
await database.execute(query)
url_mapping[shortcode] = str(original_url)
return {"shortcode": shortcode, "original_url": original_url}
return {"shortcode": shortcode, "original_url": original_url}
except IntegrityError as e:
if 'shortcode' in str(e):
raise HTTPException(status_code=409, detail="Shortcode already in use")
else:
raise HTTPException(status_code=500, detail="Internal Server Error")

View File

@@ -1,12 +1,20 @@
alembic==1.16.4
annotated-types==0.7.0
anyio==4.9.0
asyncpg==0.30.0
click==8.2.1
databases==0.9.0
fastapi==0.116.1
greenlet==3.2.3
h11==0.16.0
idna==3.10
Mako==1.3.10
MarkupSafe==3.0.2
psycopg2-binary==2.9.10
pydantic==2.11.7
pydantic_core==2.33.2
sniffio==1.3.1
SQLAlchemy==2.0.41
starlette==0.47.2
typing-inspection==0.4.1
typing_extensions==4.14.1