feat: implement new API endpoints for activities, analytics, auth, contacts, deals, organizations, tasks, and users; remove obsolete files
Test / test (push) Successful in 12s Details

This commit is contained in:
k1nq 2025-11-28 11:21:09 +05:00
parent 134ebbbca8
commit 0ab3bfbb34
32 changed files with 93 additions and 164 deletions

View File

@ -1,7 +1,10 @@
"""Activity timeline endpoints backed by ActivityService.""" """Activity timeline endpoints and payload schemas."""
from __future__ import annotations from __future__ import annotations
from typing import Literal
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel, Field
from app.api.deps import get_activity_service, get_organization_context from app.api.deps import get_activity_service, get_organization_context
from app.models.activity import ActivityRead from app.models.activity import ActivityRead
@ -13,7 +16,18 @@ from app.services.activity_service import (
) )
from app.services.organization_service import OrganizationContext from app.services.organization_service import OrganizationContext
from .models import ActivityCommentPayload
class ActivityCommentBody(BaseModel):
text: str = Field(..., min_length=1, max_length=2000)
class ActivityCommentPayload(BaseModel):
type: Literal["comment"] = "comment"
payload: ActivityCommentBody
def extract_text(self) -> str:
return self.payload.text.strip()
router = APIRouter(prefix="/deals/{deal_id}/activities", tags=["activities"]) router = APIRouter(prefix="/deals/{deal_id}/activities", tags=["activities"])

View File

@ -1,4 +0,0 @@
"""Activities API package."""
from .views import router
__all__ = ["router"]

View File

@ -1 +0,0 @@
"""CRUD helpers for activities (to be implemented)."""

View File

@ -1,18 +0,0 @@
"""Pydantic schemas for activity endpoints."""
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, Field
class ActivityCommentBody(BaseModel):
text: str = Field(..., min_length=1, max_length=2000)
class ActivityCommentPayload(BaseModel):
type: Literal["comment"] = "comment"
payload: ActivityCommentBody
def extract_text(self) -> str:
return self.payload.text.strip()

View File

@ -1,4 +0,0 @@
"""Analytics API package."""
from .views import router
__all__ = ["router"]

View File

@ -1 +0,0 @@
"""Analytics CRUD/query helpers placeholder."""

View File

@ -1 +0,0 @@
"""Analytics schemas placeholder."""

View File

@ -1,6 +1,7 @@
"""Authentication API endpoints.""" """Authentication API endpoints and payloads."""
from __future__ import annotations from __future__ import annotations
from pydantic import BaseModel, EmailStr
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
@ -13,7 +14,13 @@ from app.models.user import UserCreate
from app.repositories.user_repo import UserRepository from app.repositories.user_repo import UserRepository
from app.services.auth_service import AuthService, InvalidCredentialsError from app.services.auth_service import AuthService, InvalidCredentialsError
from .models import RegisterRequest
class RegisterRequest(BaseModel):
email: EmailStr
password: str
name: str
organization_name: str
router = APIRouter(prefix="/auth", tags=["auth"]) router = APIRouter(prefix="/auth", tags=["auth"])

View File

@ -1,4 +0,0 @@
"""Auth API package."""
from .views import router
__all__ = ["router"]

View File

@ -1 +0,0 @@
"""Auth CRUD/service helpers placeholder."""

View File

@ -1,11 +0,0 @@
"""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

View File

@ -1,12 +1,18 @@
"""Contact API stubs required by the spec.""" """Contact API stubs and schemas."""
from __future__ import annotations from __future__ import annotations
from fastapi import APIRouter, Depends, Query, status from fastapi import APIRouter, Depends, Query, status
from pydantic import BaseModel, EmailStr
from app.api.deps import get_organization_context from app.api.deps import get_organization_context
from app.services.organization_service import OrganizationContext from app.services.organization_service import OrganizationContext
from .models import ContactCreatePayload
class ContactCreatePayload(BaseModel):
name: str
email: EmailStr | None = None
phone: str | None = None
router = APIRouter(prefix="/contacts", tags=["contacts"]) router = APIRouter(prefix="/contacts", tags=["contacts"])

View File

@ -1,4 +0,0 @@
"""Contacts API package."""
from .views import router
__all__ = ["router"]

View File

@ -1 +0,0 @@
"""Contacts CRUD placeholder."""

View File

@ -1,10 +0,0 @@
"""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

View File

@ -1,12 +1,13 @@
"""Deal API endpoints backed by DealService.""" """Deal API endpoints backed by DealService with inline payload schemas."""
from __future__ import annotations from __future__ import annotations
from decimal import Decimal from decimal import Decimal
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel
from app.api.deps import get_deal_repository, get_deal_service, get_organization_context from app.api.deps import get_deal_repository, get_deal_service, get_organization_context
from app.models.deal import DealRead, DealStage, DealStatus from app.models.deal import DealCreate, DealRead, DealStage, DealStatus
from app.repositories.deal_repo import DealRepository, DealAccessError, DealQueryParams from app.repositories.deal_repo import DealRepository, DealAccessError, DealQueryParams
from app.services.deal_service import ( from app.services.deal_service import (
DealService, DealService,
@ -16,7 +17,31 @@ from app.services.deal_service import (
) )
from app.services.organization_service import OrganizationContext from app.services.organization_service import OrganizationContext
from .models import DealCreatePayload, DealUpdatePayload
class DealCreatePayload(BaseModel):
contact_id: int
title: str
amount: Decimal | None = None
currency: str | None = None
owner_id: int | None = None
def to_domain(self, *, organization_id: int, fallback_owner: int) -> DealCreate:
return DealCreate(
organization_id=organization_id,
contact_id=self.contact_id,
owner_id=self.owner_id or fallback_owner,
title=self.title,
amount=self.amount,
currency=self.currency,
)
class DealUpdatePayload(BaseModel):
status: DealStatus | None = None
stage: DealStage | None = None
amount: Decimal | None = None
currency: str | None = None
router = APIRouter(prefix="/deals", tags=["deals"]) router = APIRouter(prefix="/deals", tags=["deals"])

View File

@ -1,4 +0,0 @@
"""Deals API package."""
from .views import router
__all__ = ["router"]

View File

@ -1 +0,0 @@
"""Deal CRUD placeholder."""

View File

@ -1,33 +0,0 @@
"""Deal API schemas."""
from __future__ import annotations
from decimal import Decimal
from pydantic import BaseModel
from app.models.deal import DealCreate, DealStage, DealStatus
class DealCreatePayload(BaseModel):
contact_id: int
title: str
amount: Decimal | None = None
currency: str | None = None
owner_id: int | None = None
def to_domain(self, *, organization_id: int, fallback_owner: int) -> DealCreate:
return DealCreate(
organization_id=organization_id,
contact_id=self.contact_id,
owner_id=self.owner_id or fallback_owner,
title=self.title,
amount=self.amount,
currency=self.currency,
)
class DealUpdatePayload(BaseModel):
status: DealStatus | None = None
stage: DealStage | None = None
amount: Decimal | None = None
currency: str | None = None

View File

@ -1,4 +0,0 @@
"""Organizations API package."""
from .views import router
__all__ = ["router"]

View File

@ -1 +0,0 @@
"""Organization CRUD placeholder."""

View File

@ -1 +0,0 @@
"""Organization API schemas placeholder."""

View File

@ -1,12 +1,13 @@
"""Task API endpoints backed by TaskService.""" """Task API endpoints with inline schemas."""
from __future__ import annotations from __future__ import annotations
from datetime import date from datetime import date, datetime, time, timezone
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel
from app.api.deps import get_organization_context, get_task_service from app.api.deps import get_organization_context, get_task_service
from app.models.task import TaskRead from app.models.task import TaskCreate, TaskRead
from app.services.organization_service import OrganizationContext from app.services.organization_service import OrganizationContext
from app.services.task_service import ( from app.services.task_service import (
TaskDueDateError, TaskDueDateError,
@ -16,7 +17,34 @@ from app.services.task_service import (
TaskService, TaskService,
) )
from .models import TaskCreatePayload, to_range_boundary
class TaskCreatePayload(BaseModel):
deal_id: int
title: str
description: str | None = None
due_date: date | None = None
def to_domain(self) -> TaskCreate:
return TaskCreate(
deal_id=self.deal_id,
title=self.title,
description=self.description,
due_date=_date_to_datetime(self.due_date) if self.due_date else None,
)
def to_range_boundary(value: date | None, *, end_of_day: bool) -> datetime | None:
"""Convert a date query param to an inclusive datetime boundary."""
if value is None:
return None
boundary_time = time(23, 59, 59, 999999) if end_of_day else time(0, 0, 0)
return datetime.combine(value, boundary_time, tzinfo=timezone.utc)
def _date_to_datetime(value: date) -> datetime:
return datetime.combine(value, time(0, 0, 0), tzinfo=timezone.utc)
router = APIRouter(prefix="/tasks", tags=["tasks"]) router = APIRouter(prefix="/tasks", tags=["tasks"])

View File

@ -1,4 +0,0 @@
"""Tasks API package."""
from .views import router
__all__ = ["router"]

View File

@ -1 +0,0 @@
"""Task CRUD placeholder."""

View File

@ -1,36 +0,0 @@
"""Task API schemas and helpers."""
from __future__ import annotations
from datetime import date, datetime, time, timezone
from pydantic import BaseModel
from app.models.task import TaskCreate
class TaskCreatePayload(BaseModel):
deal_id: int
title: str
description: str | None = None
due_date: date | None = None
def to_domain(self) -> TaskCreate:
return TaskCreate(
deal_id=self.deal_id,
title=self.title,
description=self.description,
due_date=_date_to_datetime(self.due_date) if self.due_date else None,
)
def to_range_boundary(value: date | None, *, end_of_day: bool) -> datetime | None:
"""Convert a date query param to an inclusive datetime boundary."""
if value is None:
return None
boundary_time = time(23, 59, 59, 999999) if end_of_day else time(0, 0, 0)
return datetime.combine(value, boundary_time, tzinfo=timezone.utc)
def _date_to_datetime(value: date) -> datetime:
return datetime.combine(value, time(0, 0, 0), tzinfo=timezone.utc)

View File

@ -1,4 +0,0 @@
"""Users API package."""
from .views import router
__all__ = ["router"]

View File

@ -1 +0,0 @@
"""User CRUD placeholder."""

View File

@ -1 +0,0 @@
"""User API schemas placeholder."""