diff --git a/alembic/versions/20250501_101826_12aeca18_update_shortened_url.py b/alembic/versions/20250501_101826_12aeca18_update_shortened_url.py new file mode 100644 index 0000000..34fbc3e --- /dev/null +++ b/alembic/versions/20250501_101826_12aeca18_update_shortened_url.py @@ -0,0 +1,32 @@ +"""create table for shortened_urls + +Revision ID: 4d2c8a75d9c4 +Revises: 0001 +Create Date: 2023-05-26 12:34:56 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.sql import func + +# revision identifiers, used by Alembic. +revision = '4d2c8a75d9c4' +down_revision = '0001' +branch_labels = None +depends_on = None + +def upgrade(): + op.create_table( + 'shortened_urls', + sa.Column('id', sa.String(36), primary_key=True, default=func.uuid_generate_v4()), + sa.Column('original_url', sa.String(), nullable=False), + sa.Column('shortened_url', sa.String(), nullable=False, unique=True), + sa.Column('user_id', sa.String(36), nullable=False), + sa.Column('created_at', sa.DateTime(), server_default=func.now()), + sa.Column('updated_at', sa.DateTime(), server_default=func.now(), onupdate=func.now()) + ) + op.create_index(op.f('ix_shortened_urls_shortened_url'), 'shortened_urls', ['shortened_url'], unique=True) + +def downgrade(): + op.drop_index(op.f('ix_shortened_urls_shortened_url'), table_name='shortened_urls') + op.drop_table('shortened_urls') \ No newline at end of file diff --git a/endpoints/api/urls.get.py b/endpoints/api/urls.get.py index e69de29..fa9eb6a 100644 --- a/endpoints/api/urls.get.py +++ b/endpoints/api/urls.get.py @@ -0,0 +1,19 @@ +from fastapi import APIRouter, Depends +from typing import List +from uuid import UUID +from sqlalchemy.orm import Session +from core.database import get_db +from schemas.shortened_url import ShortenedUrlSchema +from helpers.shortened_url_helpers import get_user_shortened_urls + +router = APIRouter() + +@router.get("/api/urls", response_model=List[ShortenedUrlSchema]) +async def get_user_urls( + user_id: UUID, + skip: int = 0, + limit: int = 100, + db: Session = Depends(get_db) +): + urls = get_user_shortened_urls(db, user_id, skip, limit) + return urls \ No newline at end of file diff --git a/helpers/shortened_url_helpers.py b/helpers/shortened_url_helpers.py new file mode 100644 index 0000000..ff5d1ce --- /dev/null +++ b/helpers/shortened_url_helpers.py @@ -0,0 +1,59 @@ +from typing import List, Optional +from uuid import UUID +from sqlalchemy.orm import Session +from models.shortened_url import ShortenedUrl +from schemas.shortened_url import ShortenedUrlSchema, ShortenedUrlCreate +from fastapi import HTTPException + +def get_user_shortened_urls(db: Session, user_id: UUID, skip: int = 0, limit: int = 100) -> List[ShortenedUrlSchema]: + """ + Retrieves a paginated list of shortened URLs for a given user. + + Args: + db (Session): The database session. + user_id (UUID): The ID of the user. + skip (int): The number of items to skip (for pagination). + limit (int): The maximum number of items to return. + + Returns: + List[ShortenedUrlSchema]: A list of ShortenedUrlSchema objects. + """ + urls = db.query(ShortenedUrl).filter(ShortenedUrl.user_id == user_id).offset(skip).limit(limit).all() + return [ShortenedUrlSchema.from_orm(url) for url in urls] + +def get_shortened_url_by_id(db: Session, url_id: UUID) -> Optional[ShortenedUrlSchema]: + """ + Retrieves a shortened URL by its ID. + + Args: + db (Session): The database session. + url_id (UUID): The ID of the shortened URL. + + Returns: + Optional[ShortenedUrlSchema]: The ShortenedUrlSchema object if found, otherwise None. + """ + url = db.query(ShortenedUrl).filter(ShortenedUrl.id == url_id).first() + if not url: + return None + return ShortenedUrlSchema.from_orm(url) + +def create_shortened_url(db: Session, url_data: ShortenedUrlCreate) -> ShortenedUrlSchema: + """ + Creates a new shortened URL in the database. + + Args: + db (Session): The database session. + url_data (ShortenedUrlCreate): The data for the shortened URL to create. + + Returns: + ShortenedUrlSchema: The newly created ShortenedUrlSchema object. + """ + existing_url = db.query(ShortenedUrl).filter(ShortenedUrl.shortened_url == url_data.shortened_url).first() + if existing_url: + raise HTTPException(status_code=400, detail="Shortened URL already exists") + + db_url = ShortenedUrl(**url_data.dict()) + db.add(db_url) + db.commit() + db.refresh(db_url) + return ShortenedUrlSchema.from_orm(db_url) \ No newline at end of file diff --git a/models/shortened_url.py b/models/shortened_url.py new file mode 100644 index 0000000..7e6fdbc --- /dev/null +++ b/models/shortened_url.py @@ -0,0 +1,15 @@ +from sqlalchemy import Column, String, DateTime +from sqlalchemy.dialects.postgresql import UUID +from sqlalchemy.sql import func +from core.database import Base +import uuid + +class ShortenedUrl(Base): + __tablename__ = "shortened_urls" + + id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) + original_url = Column(String, nullable=False) + shortened_url = Column(String, nullable=False, unique=True, index=True) + user_id = Column(UUID(as_uuid=True), nullable=False) + created_at = Column(DateTime, default=func.now()) + updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) \ No newline at end of file diff --git a/schemas/shortened_url.py b/schemas/shortened_url.py new file mode 100644 index 0000000..1ee7844 --- /dev/null +++ b/schemas/shortened_url.py @@ -0,0 +1,28 @@ +from pydantic import BaseModel, HttpUrl +from typing import Optional +from datetime import datetime +from uuid import UUID + +# Base schema for ShortenedUrl, used for inheritance +class ShortenedUrlBase(BaseModel): + original_url: HttpUrl + user_id: UUID + +# Schema for creating a new ShortenedUrl +class ShortenedUrlCreate(ShortenedUrlBase): + pass + +# Schema for updating an existing ShortenedUrl (all fields optional) +class ShortenedUrlUpdate(ShortenedUrlBase): + original_url: Optional[HttpUrl] = None + user_id: Optional[UUID] = None + +# Schema for representing a ShortenedUrl in responses +class ShortenedUrlSchema(ShortenedUrlBase): + id: UUID + shortened_url: str + created_at: datetime + updated_at: datetime + + class Config: + orm_mode = True \ No newline at end of file