feat: implement initial structure for activities, analytics, auth, contacts, deals, organizations, tasks, and users APIs with placeholder endpoints
This commit is contained in:
parent
6d9387d1b4
commit
666e0c49f8
|
|
@ -1,9 +1,24 @@
|
|||
"""Root API router that aggregates versioned routers."""
|
||||
from fastapi import APIRouter
|
||||
|
||||
from app.api.v1 import auth, users
|
||||
from app.api.v1 import (
|
||||
activities,
|
||||
analytics,
|
||||
auth,
|
||||
contacts,
|
||||
deals,
|
||||
organizations,
|
||||
tasks,
|
||||
users,
|
||||
)
|
||||
from app.core.config import settings
|
||||
|
||||
api_router = APIRouter()
|
||||
api_router.include_router(users.router, prefix=settings.api_v1_prefix)
|
||||
api_router.include_router(auth.router, prefix=settings.api_v1_prefix)
|
||||
api_router.include_router(users.router, prefix=settings.api_v1_prefix)
|
||||
api_router.include_router(organizations.router, prefix=settings.api_v1_prefix)
|
||||
api_router.include_router(contacts.router, prefix=settings.api_v1_prefix)
|
||||
api_router.include_router(deals.router, prefix=settings.api_v1_prefix)
|
||||
api_router.include_router(tasks.router, prefix=settings.api_v1_prefix)
|
||||
api_router.include_router(activities.router, prefix=settings.api_v1_prefix)
|
||||
api_router.include_router(analytics.router, prefix=settings.api_v1_prefix)
|
||||
|
|
|
|||
|
|
@ -1 +1,22 @@
|
|||
"""Version 1 API routers."""
|
||||
from . import (
|
||||
activities,
|
||||
analytics,
|
||||
auth,
|
||||
contacts,
|
||||
deals,
|
||||
organizations,
|
||||
tasks,
|
||||
users,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"activities",
|
||||
"analytics",
|
||||
"auth",
|
||||
"contacts",
|
||||
"deals",
|
||||
"organizations",
|
||||
"tasks",
|
||||
"users",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
"""Activities API package."""
|
||||
from .views import router
|
||||
|
||||
__all__ = ["router"]
|
||||
|
|
@ -0,0 +1 @@
|
|||
"""CRUD helpers for activities (to be implemented)."""
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
"""Pydantic schemas for activity endpoints."""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ActivityCommentPayload(BaseModel):
|
||||
type: Literal["comment"]
|
||||
payload: dict[str, Any]
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
"""Activity timeline API stubs."""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, status
|
||||
|
||||
from .models import ActivityCommentPayload
|
||||
|
||||
router = APIRouter(prefix="/deals/{deal_id}/activities", tags=["activities"])
|
||||
|
||||
|
||||
def _stub(endpoint: str) -> dict[str, str]:
|
||||
return {"detail": f"{endpoint} is not implemented yet"}
|
||||
|
||||
|
||||
@router.get("/", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def list_activities(deal_id: int) -> dict[str, str]:
|
||||
"""Placeholder for listing deal activities."""
|
||||
_ = deal_id
|
||||
return _stub("GET /deals/{deal_id}/activities")
|
||||
|
||||
|
||||
@router.post("/", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def create_activity_comment(deal_id: int, payload: ActivityCommentPayload) -> dict[str, str]:
|
||||
"""Placeholder for adding a comment activity to a deal."""
|
||||
_ = (deal_id, payload)
|
||||
return _stub("POST /deals/{deal_id}/activities")
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
"""Analytics API package."""
|
||||
from .views import router
|
||||
|
||||
__all__ = ["router"]
|
||||
|
|
@ -0,0 +1 @@
|
|||
"""Analytics CRUD/query helpers placeholder."""
|
||||
|
|
@ -0,0 +1 @@
|
|||
"""Analytics schemas placeholder."""
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
"""Analytics API stubs (deal summary and funnel)."""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Query, status
|
||||
|
||||
router = APIRouter(prefix="/analytics", tags=["analytics"])
|
||||
|
||||
|
||||
def _stub(endpoint: str) -> dict[str, str]:
|
||||
return {"detail": f"{endpoint} is not implemented yet"}
|
||||
|
||||
|
||||
@router.get("/deals/summary", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def deals_summary(days: int = Query(30, ge=1, le=180)) -> dict[str, str]:
|
||||
"""Placeholder for aggregated deal statistics."""
|
||||
_ = days
|
||||
return _stub("GET /analytics/deals/summary")
|
||||
|
||||
|
||||
@router.get("/deals/funnel", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def deals_funnel() -> dict[str, str]:
|
||||
"""Placeholder for funnel analytics."""
|
||||
return _stub("GET /analytics/deals/funnel")
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
from app.api.deps import get_auth_service
|
||||
from app.models.token import LoginRequest, TokenResponse
|
||||
|
|
@ -10,6 +11,29 @@ from app.services.auth_service import AuthService, InvalidCredentialsError
|
|||
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||
|
||||
|
||||
class RegisterRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
name: str
|
||||
organization_name: str
|
||||
|
||||
|
||||
def _stub(detail: str) -> dict[str, str]:
|
||||
return {"detail": detail}
|
||||
|
||||
|
||||
@router.post("/register", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def register_user(_: RegisterRequest) -> dict[str, str]:
|
||||
"""Placeholder for user plus organization registration flow."""
|
||||
return _stub("POST /auth/register is not implemented yet")
|
||||
|
||||
|
||||
@router.post("/login", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def login(_: LoginRequest) -> dict[str, str]:
|
||||
"""Placeholder for login shortcut endpoint defined in the spec."""
|
||||
return _stub("POST /auth/login is not implemented yet")
|
||||
|
||||
|
||||
@router.post("/token", response_model=TokenResponse)
|
||||
async def login_for_access_token(
|
||||
credentials: LoginRequest,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
"""Auth API package."""
|
||||
from .views import router
|
||||
|
||||
__all__ = ["router"]
|
||||
|
|
@ -0,0 +1 @@
|
|||
"""Auth CRUD/service helpers placeholder."""
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
"""Auth-specific Pydantic schemas."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
|
||||
class RegisterRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
name: str
|
||||
organization_name: str
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
"""Authentication API endpoints."""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
|
||||
from app.api.deps import get_auth_service
|
||||
from app.models.token import LoginRequest, TokenResponse
|
||||
from app.services.auth_service import AuthService, InvalidCredentialsError
|
||||
|
||||
from .models import RegisterRequest
|
||||
|
||||
router = APIRouter(prefix="/auth", tags=["auth"])
|
||||
|
||||
|
||||
def _stub(detail: str) -> dict[str, str]:
|
||||
return {"detail": detail}
|
||||
|
||||
|
||||
@router.post("/register", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def register_user(_: RegisterRequest) -> dict[str, str]:
|
||||
"""Placeholder for user plus organization registration flow."""
|
||||
return _stub("POST /auth/register is not implemented yet")
|
||||
|
||||
|
||||
@router.post("/login", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def login(_: LoginRequest) -> dict[str, str]:
|
||||
"""Placeholder for login shortcut endpoint defined in the spec."""
|
||||
return _stub("POST /auth/login is not implemented yet")
|
||||
|
||||
|
||||
@router.post("/token", response_model=TokenResponse)
|
||||
async def login_for_access_token(
|
||||
credentials: LoginRequest,
|
||||
service: AuthService = Depends(get_auth_service),
|
||||
) -> TokenResponse:
|
||||
try:
|
||||
user = await service.authenticate(credentials.email, credentials.password)
|
||||
except InvalidCredentialsError as exc: # pragma: no cover - thin API
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=str(exc)) from exc
|
||||
return service.create_access_token(user)
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
"""Contacts API package."""
|
||||
from .views import router
|
||||
|
||||
__all__ = ["router"]
|
||||
|
|
@ -0,0 +1 @@
|
|||
"""Contacts CRUD placeholder."""
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
"""Contact API schemas."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, EmailStr
|
||||
|
||||
|
||||
class ContactCreatePayload(BaseModel):
|
||||
name: str
|
||||
email: EmailStr | None = None
|
||||
phone: str | None = None
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
"""Contact API stubs required by the spec."""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, Query, status
|
||||
|
||||
from .models import ContactCreatePayload
|
||||
|
||||
router = APIRouter(prefix="/contacts", tags=["contacts"])
|
||||
|
||||
|
||||
def _stub(endpoint: str) -> dict[str, str]:
|
||||
return {"detail": f"{endpoint} is not implemented yet"}
|
||||
|
||||
|
||||
@router.get("/", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def list_contacts(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
search: str | None = None,
|
||||
owner_id: int | None = None,
|
||||
) -> dict[str, str]:
|
||||
"""Placeholder list endpoint supporting the required filters."""
|
||||
return _stub("GET /contacts")
|
||||
|
||||
|
||||
@router.post("/", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def create_contact(payload: ContactCreatePayload) -> dict[str, str]:
|
||||
"""Placeholder for creating a contact within the current organization."""
|
||||
_ = payload
|
||||
return _stub("POST /contacts")
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
"""Deals API package."""
|
||||
from .views import router
|
||||
|
||||
__all__ = ["router"]
|
||||
|
|
@ -0,0 +1 @@
|
|||
"""Deal CRUD placeholder."""
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
"""Deal API schemas."""
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class DealCreatePayload(BaseModel):
|
||||
contact_id: int
|
||||
title: str
|
||||
amount: Decimal | None = None
|
||||
currency: str | None = None
|
||||
|
||||
|
||||
class DealUpdatePayload(BaseModel):
|
||||
status: str | None = None
|
||||
stage: str | None = None
|
||||
amount: Decimal | None = None
|
||||
currency: str | None = None
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
"""Deal API stubs covering list/create/update operations."""
|
||||
from __future__ import annotations
|
||||
|
||||
from decimal import Decimal
|
||||
|
||||
from fastapi import APIRouter, Query, status
|
||||
|
||||
from .models import DealCreatePayload, DealUpdatePayload
|
||||
|
||||
router = APIRouter(prefix="/deals", tags=["deals"])
|
||||
|
||||
|
||||
def _stub(endpoint: str) -> dict[str, str]:
|
||||
return {"detail": f"{endpoint} is not implemented yet"}
|
||||
|
||||
|
||||
@router.get("/", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def list_deals(
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(20, ge=1, le=100),
|
||||
status_filter: list[str] | None = Query(default=None, alias="status"),
|
||||
min_amount: Decimal | None = None,
|
||||
max_amount: Decimal | None = None,
|
||||
stage: str | None = None,
|
||||
owner_id: int | None = None,
|
||||
order_by: str | None = None,
|
||||
order: str | None = Query(default=None, regex="^(asc|desc)$"),
|
||||
) -> dict[str, str]:
|
||||
"""Placeholder for deal filtering endpoint."""
|
||||
_ = (status_filter,)
|
||||
return _stub("GET /deals")
|
||||
|
||||
|
||||
@router.post("/", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def create_deal(payload: DealCreatePayload) -> dict[str, str]:
|
||||
"""Placeholder for creating a new deal."""
|
||||
_ = payload
|
||||
return _stub("POST /deals")
|
||||
|
||||
|
||||
@router.patch("/{deal_id}", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def update_deal(deal_id: int, payload: DealUpdatePayload) -> dict[str, str]:
|
||||
"""Placeholder for modifying deal status or stage."""
|
||||
_ = (deal_id, payload)
|
||||
return _stub("PATCH /deals/{deal_id}")
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
"""Organizations API package."""
|
||||
from .views import router
|
||||
|
||||
__all__ = ["router"]
|
||||
|
|
@ -0,0 +1 @@
|
|||
"""Organization CRUD placeholder."""
|
||||
|
|
@ -0,0 +1 @@
|
|||
"""Organization API schemas placeholder."""
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
"""Organization-related API stubs."""
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, status
|
||||
|
||||
router = APIRouter(prefix="/organizations", tags=["organizations"])
|
||||
|
||||
|
||||
def _stub(endpoint: str) -> dict[str, str]:
|
||||
return {"detail": f"{endpoint} is not implemented yet"}
|
||||
|
||||
|
||||
@router.get("/me", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def list_user_organizations() -> dict[str, str]:
|
||||
"""Placeholder for returning organizations linked to the current user."""
|
||||
return _stub("GET /organizations/me")
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
"""Tasks API package."""
|
||||
from .views import router
|
||||
|
||||
__all__ = ["router"]
|
||||
|
|
@ -0,0 +1 @@
|
|||
"""Task CRUD placeholder."""
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
"""Task API schemas."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class TaskCreatePayload(BaseModel):
|
||||
deal_id: int
|
||||
title: str
|
||||
description: str | None = None
|
||||
due_date: date
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
"""Task API stubs supporting list/create operations."""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
|
||||
from fastapi import APIRouter, Query, status
|
||||
|
||||
from .models import TaskCreatePayload
|
||||
|
||||
router = APIRouter(prefix="/tasks", tags=["tasks"])
|
||||
|
||||
|
||||
def _stub(endpoint: str) -> dict[str, str]:
|
||||
return {"detail": f"{endpoint} is not implemented yet"}
|
||||
|
||||
|
||||
@router.get("/", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def list_tasks(
|
||||
deal_id: int | None = None,
|
||||
only_open: bool = False,
|
||||
due_before: date | None = Query(default=None),
|
||||
due_after: date | None = Query(default=None),
|
||||
) -> dict[str, str]:
|
||||
"""Placeholder for task filtering endpoint."""
|
||||
return _stub("GET /tasks")
|
||||
|
||||
|
||||
@router.post("/", status_code=status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def create_task(payload: TaskCreatePayload) -> dict[str, str]:
|
||||
"""Placeholder for creating a task linked to a deal."""
|
||||
_ = payload
|
||||
return _stub("POST /tasks")
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
"""Users API package."""
|
||||
from .views import router
|
||||
|
||||
__all__ = ["router"]
|
||||
|
|
@ -0,0 +1 @@
|
|||
"""User CRUD placeholder."""
|
||||
|
|
@ -0,0 +1 @@
|
|||
"""User API schemas placeholder."""
|
||||
Loading…
Reference in New Issue