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 typing import Literal
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.models.activity import ActivityRead
@ -13,7 +16,18 @@ from app.services.activity_service import (
)
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"])

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 pydantic import BaseModel, EmailStr
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.exc import IntegrityError
@ -13,7 +14,13 @@ from app.models.user import UserCreate
from app.repositories.user_repo import UserRepository
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"])

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 fastapi import APIRouter, Depends, Query, status
from pydantic import BaseModel, EmailStr
from app.api.deps import get_organization_context
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"])

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 decimal import Decimal
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.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.services.deal_service import (
DealService,
@ -16,7 +17,31 @@ from app.services.deal_service import (
)
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"])

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 datetime import date
from datetime import date, datetime, time, timezone
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.models.task import TaskRead
from app.models.task import TaskCreate, TaskRead
from app.services.organization_service import OrganizationContext
from app.services.task_service import (
TaskDueDateError,
@ -16,7 +17,34 @@ from app.services.task_service import (
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"])

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."""