Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/pvnm4/Social-Media-Backend/llms.txt

Use this file to discover all available pages before exploring further.

The API is built around three database tables — posts, users, and votes — each defined as a SQLAlchemy ORM model that maps directly to a PostgreSQL table. Alongside each ORM model there are one or more Pydantic schemas that govern what data is accepted in request bodies and what is returned in responses. This two-layer approach (ORM for persistence, Pydantic for the API boundary) ensures that internal implementation details such as hashed passwords never leak to API consumers.

User model

The User model represents an account in the system. Its email column carries a uniqueness constraint, and the password column stores a bcrypt hash — the plain-text password is never persisted.
from .database import Base
from sqlalchemy import Column, Integer, String
from sqlalchemy.sql.expression import text
from sqlalchemy.sql.sqltypes import TIMESTAMP


class User(Base):
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, nullable=False)
    email = Column(String, nullable=False, unique=True)
    password = Column(String, nullable=False)
    created_at = Column(
        TIMESTAMP(timezone=True),
        nullable=False,
        server_default=text('now()')
    )
    phone_number = Column(String)

Columns

ColumnTypeConstraintsNotes
idINTEGERPrimary key, not nullAuto-incremented by PostgreSQL
emailVARCHARNot null, uniqueUsed as the OAuth2 username at login
passwordVARCHARNot nullbcrypt hash; never returned in responses
created_atTIMESTAMP WITH TIME ZONENot nullDefaults to now() set by the database server
phone_numberVARCHARNullableOptional; no format validation enforced

Pydantic schemas

SchemaFieldsUsed for
UserCreateemail: EmailStr, password: strPOST /users/ request body
UserOutid: int, email: EmailStr, created_at: datetimeAll API responses that include a user
from pydantic import BaseModel, EmailStr
from datetime import datetime


class UserCreate(BaseModel):
    email: EmailStr
    password: str


class UserOut(BaseModel):
    id: int
    email: EmailStr
    created_at: datetime

    class Config:
        orm_mode = True
The password field is deliberately absent from UserOut. FastAPI uses the declared response model to serialise the output, so the hashed password is never returned in any API response, even though it lives on the underlying ORM object.

Post model

The Post model represents a piece of content created by a user. It links back to its author via a foreign key on owner_id and exposes the full User object through a SQLAlchemy relationship.
from .database import Base
from sqlalchemy import Column, Integer, String, Boolean, ForeignKey
from sqlalchemy.sql.expression import text
from sqlalchemy.sql.sqltypes import TIMESTAMP
from sqlalchemy.orm import relationship


class Post(Base):
    __tablename__ = "posts"

    id = Column(Integer, primary_key=True, nullable=False)
    title = Column(String, nullable=False)
    content = Column(String, nullable=False)
    published = Column(Boolean, server_default='TRUE', nullable=False)
    created_at = Column(
        TIMESTAMP(timezone=True),
        nullable=False,
        server_default=text('now()')
    )
    owner_id = Column(
        Integer,
        ForeignKey("users.id", ondelete="CASCADE"),
        nullable=False
    )

    owner = relationship("User")

Columns

ColumnTypeConstraintsNotes
idINTEGERPrimary key, not nullAuto-incremented by PostgreSQL
titleVARCHARNot nullPost headline
contentVARCHARNot nullPost body text
publishedBOOLEANNot nullDefaults to TRUE at the database level
created_atTIMESTAMP WITH TIME ZONENot nullDefaults to now() set by the database server
owner_idINTEGERForeign key → users.id CASCADE, not nullDeleting a user cascades to delete all their posts

Relationship

The owner attribute is a SQLAlchemy relationship("User"). When a post is queried with the relationship loaded, post.owner returns the full User ORM instance — which is then serialised as a nested UserOut object in API responses.

Pydantic schemas

SchemaFieldsUsed for
PostBasetitle: str, content: str, published: bool = TrueShared base
CreatePostInherits PostBase (no extra fields)POST /posts/ request body
PostPostBase + id, created_at, owner: UserOutIndividual post responses
PostOutPost: Post, vote: intList responses that include vote counts
from pydantic import BaseModel
from datetime import datetime


class PostBase(BaseModel):
    title: str
    content: str
    published: bool = True


class CreatePost(PostBase):
    pass


class Post(PostBase):
    id: int
    created_at: datetime
    owner: UserOut

    class Config:
        orm_mode = True


class PostOut(BaseModel):
    Post: Post
    vote: int

    class Config:
        orm_mode = True
PostOut wraps a full Post schema together with an integer vote count. This is the shape returned by the posts list endpoint, which JOINs the votes table to aggregate vote totals per post.

Vote model

The Vote model implements a simple upvote/downvote system using a composite primary key. Because (user_id, post_id) is the primary key, the database enforces at most one vote per user per post at the schema level — no application-side uniqueness check is needed.
from .database import Base
from sqlalchemy import Column, Integer, ForeignKey


class Vote(Base):
    __tablename__ = "votes"

    user_id = Column(
        Integer,
        ForeignKey("users.id", ondelete="CASCADE"),
        primary_key=True
    )
    post_id = Column(
        Integer,
        ForeignKey("posts.id", ondelete="CASCADE"),
        primary_key=True
    )

Columns & constraints

ColumnTypeConstraintsNotes
user_idINTEGERPrimary key (composite), FK → users.id CASCADEDeleting a user removes all their votes
post_idINTEGERPrimary key (composite), FK → posts.id CASCADEDeleting a post removes all votes on it
Both foreign keys are configured with ondelete="CASCADE", so vote rows are cleaned up automatically when either the referenced user or the referenced post is deleted.

Pydantic schema

The Vote request schema is used by POST /vote/ to determine which post to vote on and whether the action is an upvote (dir=1) or a removal of an existing vote (dir=0).
from pydantic import BaseModel, Field
from typing import Annotated


class Vote(BaseModel):
    post_id: int
    dir: Annotated[int, Field(le=1)]
dir is constrained to be at most 1 by the Field(le=1) annotation. No lower-bound constraint is declared in the schema — the route handler logic treats dir=1 as a vote cast and any other value as a vote retraction.
Send dir=1 to cast a vote and dir=0 to retract an existing vote. Attempting to vote on a post you have already voted on (or retract a vote that doesn’t exist) will return a 409 Conflict or 404 Not Found from the route handler.

Auth schemas

Two additional Pydantic schemas support the authentication flow. Token is the response model returned by POST /login, and TokenData carries the decoded user_id claim extracted from a verified JWT.
from pydantic import BaseModel
from typing import Optional


class Token(BaseModel):
    access_token: str
    token_type: str


class TokenData(BaseModel):
    id: Optional[int] = None
Token is returned directly from the login endpoint after successful credential verification. TokenData is used internally by verify_access_token() to pass the extracted user_id back to get_current_user() — it is never serialised into an API response.

Schema hierarchy

The Pydantic schemas build on each other to avoid repetition and to ensure that the same field definitions are reused consistently across request and response models.
UserCreate          (email + password)          ← POST /users/ body
    └─ [no inheritance]
UserOut             (id + email + created_at)   ← embedded in Post responses

PostBase            (title + content + published)
    └── CreatePost  (no extra fields)            ← POST /posts/ body
    └── Post        (+ id + created_at + owner)  ← single post response
            └── PostOut  (Post + vote count)     ← list post response

Vote                (post_id + dir)              ← POST /vote/ body

Token               (access_token + token_type)  ← POST /login response
TokenData           (id)                         ← internal JWT claim holder
UserOut is defined twice in schemas.py — the second definition shadows the first. Both are identical, so there is no behavioural difference, but the duplication can be cleaned up by removing the first occurrence.

Build docs developers (and LLMs) love