Refactoring file structure, not tested yet
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
# app/services/url_service.py
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import select
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from app.db.models import URL
|
||||
from app.utils.short_code import generate_short_code
|
||||
from app.core.config import settings
|
||||
|
||||
|
||||
SHORT_CODE_LENGTH = 6
|
||||
MAX_GENERATION_ATTEMPTS = 5
|
||||
|
||||
|
||||
def create_short_url(db: Session, original_url: str) -> str:
|
||||
"""
|
||||
Create a shortened URL or return existing one if already present.
|
||||
"""
|
||||
|
||||
# 1️⃣ Check if URL already exists
|
||||
existing = db.execute(
|
||||
select(URL).where(URL.original_url == original_url)
|
||||
).scalar_one_or_none()
|
||||
|
||||
if existing:
|
||||
return f"{settings.base_url}/{existing.short_code}"
|
||||
|
||||
# 2️⃣ Generate unique short code with collision handling
|
||||
for _ in range(MAX_GENERATION_ATTEMPTS):
|
||||
short_code = generate_short_code(SHORT_CODE_LENGTH)
|
||||
|
||||
collision = db.execute(
|
||||
select(URL).where(URL.short_code == short_code)
|
||||
).scalar_one_or_none()
|
||||
|
||||
if not collision:
|
||||
new_url = URL(
|
||||
short_code=short_code,
|
||||
original_url=original_url,
|
||||
)
|
||||
db.add(new_url)
|
||||
db.commit()
|
||||
db.refresh(new_url)
|
||||
|
||||
return f"{settings.base_url}/{new_url.short_code}"
|
||||
|
||||
# If we somehow fail multiple times
|
||||
raise RuntimeError("Failed to generate unique short code")
|
||||
|
||||
|
||||
def get_original_url(db: Session, short_code: str) -> Optional[str]:
|
||||
"""
|
||||
Retrieve original URL for redirection.
|
||||
"""
|
||||
|
||||
url_entry = db.execute(
|
||||
select(URL).where(URL.short_code == short_code)
|
||||
).scalar_one_or_none()
|
||||
|
||||
if not url_entry:
|
||||
return None
|
||||
|
||||
return url_entry.original_url
|
||||
|
||||
|
||||
def increment_clicks(db: Session, short_code: str) -> bool:
|
||||
"""
|
||||
Increment click counter and update last_accessed timestamp.
|
||||
"""
|
||||
|
||||
url_entry = db.execute(
|
||||
select(URL).where(URL.short_code == short_code)
|
||||
).scalar_one_or_none()
|
||||
|
||||
if not url_entry:
|
||||
return False
|
||||
|
||||
url_entry.clicks += 1
|
||||
url_entry.last_accessed = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
return True
|
||||
|
||||
|
||||
def get_stats(db: Session, short_code: str) -> Optional[dict]:
|
||||
"""
|
||||
Return URL statistics.
|
||||
"""
|
||||
|
||||
url_entry = db.execute(
|
||||
select(URL).where(URL.short_code == short_code)
|
||||
).scalar_one_or_none()
|
||||
|
||||
if not url_entry:
|
||||
return None
|
||||
|
||||
return {
|
||||
"original_url": url_entry.original_url,
|
||||
"clicks": url_entry.clicks,
|
||||
"created_at": url_entry.created_at,
|
||||
"last_accessed": url_entry.last_accessed,
|
||||
}
|
||||
Reference in New Issue
Block a user