Enhance todo app with advanced features
- Add User model with email-based authentication - Add Tag model with many-to-many relationship to todos - Add TodoTag junction table for todo-tag relationships - Enhance Todo model with priority levels (low, medium, high) - Add due_date field with datetime support - Add recurrence_pattern field for recurring todos - Add parent-child relationship for subtasks support - Create comprehensive alembic migration for all changes - Add proper indexes for performance optimization - Use Text type for todo descriptions - Implement proper SQLAlchemy relationships and foreign keys
This commit is contained in:
parent
7a43bab909
commit
4b391fe54b
110
alembic/versions/002_enhance_todo_features.py
Normal file
110
alembic/versions/002_enhance_todo_features.py
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
"""Enhance todo features with categories, priorities, users, and subtasks
|
||||||
|
|
||||||
|
Revision ID: 002
|
||||||
|
Revises: 001
|
||||||
|
Create Date: 2024-01-02 00:00:00.000000
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '002'
|
||||||
|
down_revision = '001'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
# Create users table
|
||||||
|
op.create_table('users',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('email', sa.String(), nullable=False),
|
||||||
|
sa.Column('name', sa.String(), nullable=False),
|
||||||
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
||||||
|
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
|
||||||
|
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
|
||||||
|
|
||||||
|
# Create tags table
|
||||||
|
op.create_table('tags',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('name', sa.String(), nullable=False),
|
||||||
|
sa.Column('color', sa.String(), nullable=True),
|
||||||
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_tags_id'), 'tags', ['id'], unique=False)
|
||||||
|
op.create_index(op.f('ix_tags_name'), 'tags', ['name'], unique=True)
|
||||||
|
|
||||||
|
# Create todo_tags junction table
|
||||||
|
op.create_table('todo_tags',
|
||||||
|
sa.Column('todo_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('tag_id', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['tag_id'], ['tags.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['todo_id'], ['todos.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('todo_id', 'tag_id')
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add new columns to todos table
|
||||||
|
op.add_column('todos', sa.Column('priority', sa.Enum('LOW', 'MEDIUM', 'HIGH', name='prioritylevel'), server_default='MEDIUM', nullable=False))
|
||||||
|
op.add_column('todos', sa.Column('due_date', sa.DateTime(timezone=True), nullable=True))
|
||||||
|
op.add_column('todos', sa.Column('recurrence_pattern', sa.Enum('NONE', 'DAILY', 'WEEKLY', 'MONTHLY', 'YEARLY', name='recurrencepattern'), server_default='NONE', nullable=False))
|
||||||
|
op.add_column('todos', sa.Column('user_id', sa.Integer(), nullable=False))
|
||||||
|
op.add_column('todos', sa.Column('parent_id', sa.Integer(), nullable=True))
|
||||||
|
|
||||||
|
# Change description column type to Text
|
||||||
|
op.alter_column('todos', 'description',
|
||||||
|
existing_type=sa.String(),
|
||||||
|
type_=sa.Text(),
|
||||||
|
existing_nullable=True)
|
||||||
|
|
||||||
|
# Create foreign key constraints
|
||||||
|
op.create_foreign_key('fk_todos_user_id', 'todos', 'users', ['user_id'], ['id'])
|
||||||
|
op.create_foreign_key('fk_todos_parent_id', 'todos', 'todos', ['parent_id'], ['id'])
|
||||||
|
|
||||||
|
# Create indexes for new columns
|
||||||
|
op.create_index(op.f('ix_todos_priority'), 'todos', ['priority'], unique=False)
|
||||||
|
op.create_index(op.f('ix_todos_due_date'), 'todos', ['due_date'], unique=False)
|
||||||
|
op.create_index(op.f('ix_todos_user_id'), 'todos', ['user_id'], unique=False)
|
||||||
|
op.create_index(op.f('ix_todos_parent_id'), 'todos', ['parent_id'], unique=False)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
# Drop indexes
|
||||||
|
op.drop_index(op.f('ix_todos_parent_id'), table_name='todos')
|
||||||
|
op.drop_index(op.f('ix_todos_user_id'), table_name='todos')
|
||||||
|
op.drop_index(op.f('ix_todos_due_date'), table_name='todos')
|
||||||
|
op.drop_index(op.f('ix_todos_priority'), table_name='todos')
|
||||||
|
|
||||||
|
# Drop foreign key constraints
|
||||||
|
op.drop_constraint('fk_todos_parent_id', 'todos', type_='foreignkey')
|
||||||
|
op.drop_constraint('fk_todos_user_id', 'todos', type_='foreignkey')
|
||||||
|
|
||||||
|
# Revert description column type back to String
|
||||||
|
op.alter_column('todos', 'description',
|
||||||
|
existing_type=sa.Text(),
|
||||||
|
type_=sa.String(),
|
||||||
|
existing_nullable=True)
|
||||||
|
|
||||||
|
# Drop new columns from todos table
|
||||||
|
op.drop_column('todos', 'parent_id')
|
||||||
|
op.drop_column('todos', 'user_id')
|
||||||
|
op.drop_column('todos', 'recurrence_pattern')
|
||||||
|
op.drop_column('todos', 'due_date')
|
||||||
|
op.drop_column('todos', 'priority')
|
||||||
|
|
||||||
|
# Drop todo_tags table
|
||||||
|
op.drop_table('todo_tags')
|
||||||
|
|
||||||
|
# Drop tags table
|
||||||
|
op.drop_index(op.f('ix_tags_name'), table_name='tags')
|
||||||
|
op.drop_index(op.f('ix_tags_id'), table_name='tags')
|
||||||
|
op.drop_table('tags')
|
||||||
|
|
||||||
|
# Drop users table
|
||||||
|
op.drop_index(op.f('ix_users_email'), table_name='users')
|
||||||
|
op.drop_index(op.f('ix_users_id'), table_name='users')
|
||||||
|
op.drop_table('users')
|
@ -1,13 +1,75 @@
|
|||||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime
|
from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Enum, Text
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
from enum import Enum as PyEnum
|
||||||
from .db.base import Base
|
from .db.base import Base
|
||||||
|
|
||||||
|
|
||||||
|
class PriorityLevel(PyEnum):
|
||||||
|
LOW = "low"
|
||||||
|
MEDIUM = "medium"
|
||||||
|
HIGH = "high"
|
||||||
|
|
||||||
|
|
||||||
|
class RecurrencePattern(PyEnum):
|
||||||
|
NONE = "none"
|
||||||
|
DAILY = "daily"
|
||||||
|
WEEKLY = "weekly"
|
||||||
|
MONTHLY = "monthly"
|
||||||
|
YEARLY = "yearly"
|
||||||
|
|
||||||
|
|
||||||
|
class User(Base):
|
||||||
|
__tablename__ = "users"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
email = Column(String, unique=True, index=True, nullable=False)
|
||||||
|
name = Column(String, nullable=False)
|
||||||
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||||
|
|
||||||
|
# Relationship with todos
|
||||||
|
todos = relationship("Todo", back_populates="user")
|
||||||
|
|
||||||
|
|
||||||
|
class Tag(Base):
|
||||||
|
__tablename__ = "tags"
|
||||||
|
|
||||||
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
|
name = Column(String, unique=True, index=True, nullable=False)
|
||||||
|
color = Column(String, nullable=True) # Optional color for UI
|
||||||
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
|
|
||||||
|
|
||||||
|
class TodoTag(Base):
|
||||||
|
__tablename__ = "todo_tags"
|
||||||
|
|
||||||
|
todo_id = Column(Integer, ForeignKey("todos.id"), primary_key=True)
|
||||||
|
tag_id = Column(Integer, ForeignKey("tags.id"), primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
class Todo(Base):
|
class Todo(Base):
|
||||||
__tablename__ = "todos"
|
__tablename__ = "todos"
|
||||||
|
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
title = Column(String, index=True, nullable=False)
|
title = Column(String, index=True, nullable=False)
|
||||||
description = Column(String, nullable=True)
|
description = Column(Text, nullable=True)
|
||||||
completed = Column(Boolean, default=False, nullable=False)
|
completed = Column(Boolean, default=False, nullable=False)
|
||||||
|
priority = Column(Enum(PriorityLevel), default=PriorityLevel.MEDIUM, nullable=False, index=True)
|
||||||
|
due_date = Column(DateTime(timezone=True), nullable=True, index=True)
|
||||||
|
recurrence_pattern = Column(Enum(RecurrencePattern), default=RecurrencePattern.NONE, nullable=False)
|
||||||
|
|
||||||
|
# User relationship
|
||||||
|
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||||
|
user = relationship("User", back_populates="todos")
|
||||||
|
|
||||||
|
# Parent-child relationship for subtasks
|
||||||
|
parent_id = Column(Integer, ForeignKey("todos.id"), nullable=True, index=True)
|
||||||
|
parent = relationship("Todo", remote_side=[id], back_populates="subtasks")
|
||||||
|
subtasks = relationship("Todo", back_populates="parent")
|
||||||
|
|
||||||
|
# Many-to-many relationship with tags
|
||||||
|
tags = relationship("Tag", secondary="todo_tags", backref="todos")
|
||||||
|
|
||||||
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
||||||
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
261
app/schemas.py
261
app/schemas.py
@ -1,24 +1,257 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, Field, EmailStr, validator
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional, List
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
class TodoBase(BaseModel):
|
|
||||||
title: str
|
|
||||||
description: Optional[str] = None
|
|
||||||
completed: bool = False
|
|
||||||
|
|
||||||
class TodoCreate(TodoBase):
|
# Enums for validation
|
||||||
pass
|
class PriorityLevel(str, Enum):
|
||||||
|
"""Priority levels for todos"""
|
||||||
|
|
||||||
class TodoUpdate(BaseModel):
|
HIGH = "high"
|
||||||
title: Optional[str] = None
|
MEDIUM = "medium"
|
||||||
description: Optional[str] = None
|
LOW = "low"
|
||||||
completed: Optional[bool] = None
|
|
||||||
|
|
||||||
|
class RecurrencePattern(str, Enum):
|
||||||
|
"""Recurrence patterns for todos"""
|
||||||
|
|
||||||
|
NONE = "none"
|
||||||
|
DAILY = "daily"
|
||||||
|
WEEKLY = "weekly"
|
||||||
|
MONTHLY = "monthly"
|
||||||
|
YEARLY = "yearly"
|
||||||
|
|
||||||
|
|
||||||
|
# User schemas
|
||||||
|
class UserBase(BaseModel):
|
||||||
|
"""Base user schema with common fields"""
|
||||||
|
|
||||||
|
username: str = Field(..., min_length=3, max_length=50)
|
||||||
|
email: EmailStr
|
||||||
|
full_name: Optional[str] = Field(None, max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class UserCreate(UserBase):
|
||||||
|
"""Schema for creating a new user"""
|
||||||
|
|
||||||
|
password: str = Field(..., min_length=6)
|
||||||
|
|
||||||
|
|
||||||
|
class UserUpdate(BaseModel):
|
||||||
|
"""Schema for updating user information"""
|
||||||
|
|
||||||
|
username: Optional[str] = Field(None, min_length=3, max_length=50)
|
||||||
|
email: Optional[EmailStr] = None
|
||||||
|
full_name: Optional[str] = Field(None, max_length=100)
|
||||||
|
password: Optional[str] = Field(None, min_length=6)
|
||||||
|
|
||||||
|
|
||||||
|
class User(UserBase):
|
||||||
|
"""User response schema"""
|
||||||
|
|
||||||
class Todo(TodoBase):
|
|
||||||
id: int
|
id: int
|
||||||
|
is_active: bool
|
||||||
created_at: datetime
|
created_at: datetime
|
||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
from_attributes = True
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class UserInDB(User):
|
||||||
|
"""User schema with password hash for internal use"""
|
||||||
|
|
||||||
|
hashed_password: str
|
||||||
|
|
||||||
|
|
||||||
|
# Enhanced Todo schemas
|
||||||
|
class TodoBase(BaseModel):
|
||||||
|
"""Base todo schema with common fields"""
|
||||||
|
|
||||||
|
title: str = Field(..., min_length=1, max_length=200)
|
||||||
|
description: Optional[str] = Field(None, max_length=1000)
|
||||||
|
completed: bool = False
|
||||||
|
priority: PriorityLevel = PriorityLevel.MEDIUM
|
||||||
|
tags: List[str] = Field(default_factory=list, max_items=10)
|
||||||
|
due_date: Optional[datetime] = None
|
||||||
|
parent_id: Optional[int] = Field(None, description="ID of parent todo for subtasks")
|
||||||
|
recurrence_pattern: RecurrencePattern = RecurrencePattern.NONE
|
||||||
|
user_id: Optional[int] = Field(
|
||||||
|
None, description="ID of the user who owns this todo"
|
||||||
|
)
|
||||||
|
|
||||||
|
@validator("tags")
|
||||||
|
def validate_tags(cls, v):
|
||||||
|
"""Validate tags are non-empty strings and remove duplicates"""
|
||||||
|
if v:
|
||||||
|
# Remove empty strings and duplicates
|
||||||
|
clean_tags = list(set([tag.strip() for tag in v if tag.strip()]))
|
||||||
|
# Validate tag length
|
||||||
|
for tag in clean_tags:
|
||||||
|
if len(tag) > 50:
|
||||||
|
raise ValueError("Each tag must be 50 characters or less")
|
||||||
|
return clean_tags
|
||||||
|
return v
|
||||||
|
|
||||||
|
@validator("due_date")
|
||||||
|
def validate_due_date(cls, v):
|
||||||
|
"""Validate due date is not in the past"""
|
||||||
|
if v and v < datetime.now():
|
||||||
|
raise ValueError("Due date cannot be in the past")
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
class TodoCreate(TodoBase):
|
||||||
|
"""Schema for creating a new todo"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TodoUpdate(BaseModel):
|
||||||
|
"""Schema for updating todo information"""
|
||||||
|
|
||||||
|
title: Optional[str] = Field(None, min_length=1, max_length=200)
|
||||||
|
description: Optional[str] = Field(None, max_length=1000)
|
||||||
|
completed: Optional[bool] = None
|
||||||
|
priority: Optional[PriorityLevel] = None
|
||||||
|
tags: Optional[List[str]] = Field(None, max_items=10)
|
||||||
|
due_date: Optional[datetime] = None
|
||||||
|
parent_id: Optional[int] = Field(None, description="ID of parent todo for subtasks")
|
||||||
|
recurrence_pattern: Optional[RecurrencePattern] = None
|
||||||
|
|
||||||
|
@validator("tags")
|
||||||
|
def validate_tags(cls, v):
|
||||||
|
"""Validate tags are non-empty strings and remove duplicates"""
|
||||||
|
if v is not None:
|
||||||
|
# Remove empty strings and duplicates
|
||||||
|
clean_tags = list(set([tag.strip() for tag in v if tag.strip()]))
|
||||||
|
# Validate tag length
|
||||||
|
for tag in clean_tags:
|
||||||
|
if len(tag) > 50:
|
||||||
|
raise ValueError("Each tag must be 50 characters or less")
|
||||||
|
return clean_tags
|
||||||
|
return v
|
||||||
|
|
||||||
|
@validator("due_date")
|
||||||
|
def validate_due_date(cls, v):
|
||||||
|
"""Validate due date is not in the past"""
|
||||||
|
if v and v < datetime.now():
|
||||||
|
raise ValueError("Due date cannot be in the past")
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
class Todo(TodoBase):
|
||||||
|
"""Todo response schema with all fields"""
|
||||||
|
|
||||||
|
id: int
|
||||||
|
created_at: datetime
|
||||||
|
updated_at: datetime
|
||||||
|
subtasks: List["Todo"] = Field(default_factory=list, description="List of subtasks")
|
||||||
|
shared_with: List[User] = Field(
|
||||||
|
default_factory=list, description="Users this todo is shared with"
|
||||||
|
)
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
class TodoSummary(BaseModel):
|
||||||
|
"""Simplified todo schema for list views"""
|
||||||
|
|
||||||
|
id: int
|
||||||
|
title: str
|
||||||
|
completed: bool
|
||||||
|
priority: PriorityLevel
|
||||||
|
tags: List[str]
|
||||||
|
due_date: Optional[datetime]
|
||||||
|
created_at: datetime
|
||||||
|
subtask_count: int = 0
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
# Sharing schemas
|
||||||
|
class TodoShareBase(BaseModel):
|
||||||
|
"""Base schema for todo sharing"""
|
||||||
|
|
||||||
|
todo_id: int
|
||||||
|
user_id: int
|
||||||
|
can_edit: bool = False
|
||||||
|
|
||||||
|
|
||||||
|
class TodoShareCreate(TodoShareBase):
|
||||||
|
"""Schema for creating a todo share"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TodoShare(TodoShareBase):
|
||||||
|
"""Todo share response schema"""
|
||||||
|
|
||||||
|
id: int
|
||||||
|
shared_at: datetime
|
||||||
|
todo: TodoSummary
|
||||||
|
user: User
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
|
|
||||||
|
|
||||||
|
# Response schemas for collections
|
||||||
|
class TodoListResponse(BaseModel):
|
||||||
|
"""Response schema for todo lists with pagination"""
|
||||||
|
|
||||||
|
todos: List[TodoSummary]
|
||||||
|
total: int
|
||||||
|
page: int
|
||||||
|
page_size: int
|
||||||
|
has_next: bool
|
||||||
|
has_previous: bool
|
||||||
|
|
||||||
|
|
||||||
|
class UserListResponse(BaseModel):
|
||||||
|
"""Response schema for user lists with pagination"""
|
||||||
|
|
||||||
|
users: List[User]
|
||||||
|
total: int
|
||||||
|
page: int
|
||||||
|
page_size: int
|
||||||
|
has_next: bool
|
||||||
|
has_previous: bool
|
||||||
|
|
||||||
|
|
||||||
|
# Authentication schemas
|
||||||
|
class Token(BaseModel):
|
||||||
|
"""JWT token response"""
|
||||||
|
|
||||||
|
access_token: str
|
||||||
|
token_type: str
|
||||||
|
|
||||||
|
|
||||||
|
class TokenData(BaseModel):
|
||||||
|
"""Token data for JWT validation"""
|
||||||
|
|
||||||
|
username: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
# API Response schemas
|
||||||
|
class APIResponse(BaseModel):
|
||||||
|
"""Generic API response wrapper"""
|
||||||
|
|
||||||
|
success: bool
|
||||||
|
message: str
|
||||||
|
data: Optional[dict] = None
|
||||||
|
|
||||||
|
|
||||||
|
class HealthResponse(BaseModel):
|
||||||
|
"""Health check response schema"""
|
||||||
|
|
||||||
|
status: str
|
||||||
|
timestamp: datetime
|
||||||
|
version: str = "1.0.0"
|
||||||
|
|
||||||
|
|
||||||
|
# Update Todo model to handle forward references
|
||||||
|
Todo.model_rebuild()
|
||||||
|
20
main.py
20
main.py
@ -2,18 +2,27 @@ from fastapi import FastAPI, Depends, HTTPException
|
|||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
from typing import List
|
from typing import List
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
from app.db.base import engine, Base
|
from app.db.base import engine, Base
|
||||||
from app.db.session import get_db
|
from app.db.session import get_db
|
||||||
from app import crud, schemas
|
from app import crud, schemas
|
||||||
|
|
||||||
|
# Priority levels enum
|
||||||
|
class Priority(str, Enum):
|
||||||
|
LOW = "low"
|
||||||
|
MEDIUM = "medium"
|
||||||
|
HIGH = "high"
|
||||||
|
URGENT = "urgent"
|
||||||
|
|
||||||
# Create database tables
|
# Create database tables
|
||||||
Base.metadata.create_all(bind=engine)
|
Base.metadata.create_all(bind=engine)
|
||||||
|
|
||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
title="Todo App API",
|
title="Enhanced Todo App API",
|
||||||
description="A simple todo application API",
|
description="A comprehensive todo application API with categories, priorities, due dates, subtasks, users, and recurring todos",
|
||||||
version="1.0.0"
|
version="2.0.0",
|
||||||
|
openapi_url="/openapi.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
# CORS configuration
|
# CORS configuration
|
||||||
@ -28,8 +37,11 @@ app.add_middleware(
|
|||||||
@app.get("/")
|
@app.get("/")
|
||||||
async def root():
|
async def root():
|
||||||
return {
|
return {
|
||||||
"title": "Todo App API",
|
"title": "Enhanced Todo App API",
|
||||||
|
"version": "2.0.0",
|
||||||
"documentation": "/docs",
|
"documentation": "/docs",
|
||||||
|
"redoc": "/redoc",
|
||||||
|
"openapi": "/openapi.json",
|
||||||
"health": "/health"
|
"health": "/health"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,4 +2,5 @@ fastapi==0.104.1
|
|||||||
uvicorn==0.24.0
|
uvicorn==0.24.0
|
||||||
sqlalchemy==2.0.23
|
sqlalchemy==2.0.23
|
||||||
alembic==1.12.1
|
alembic==1.12.1
|
||||||
ruff==0.1.6
|
ruff==0.1.6
|
||||||
|
email-validator==2.1.0
|
Loading…
x
Reference in New Issue
Block a user