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
Test / test (push) Successful in 12s
Details
This commit is contained in:
parent
134ebbbca8
commit
0ab3bfbb34
|
|
@ -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"])
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
"""Activities API package."""
|
|
||||||
from .views import router
|
|
||||||
|
|
||||||
__all__ = ["router"]
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""CRUD helpers for activities (to be implemented)."""
|
|
||||||
|
|
@ -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()
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
"""Analytics API package."""
|
|
||||||
from .views import router
|
|
||||||
|
|
||||||
__all__ = ["router"]
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Analytics CRUD/query helpers placeholder."""
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Analytics schemas placeholder."""
|
|
||||||
|
|
@ -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"])
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
"""Auth API package."""
|
|
||||||
from .views import router
|
|
||||||
|
|
||||||
__all__ = ["router"]
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Auth CRUD/service helpers placeholder."""
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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"])
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
"""Contacts API package."""
|
|
||||||
from .views import router
|
|
||||||
|
|
||||||
__all__ = ["router"]
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Contacts CRUD placeholder."""
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -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"])
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
"""Deals API package."""
|
|
||||||
from .views import router
|
|
||||||
|
|
||||||
__all__ = ["router"]
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Deal CRUD placeholder."""
|
|
||||||
|
|
@ -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
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
"""Organizations API package."""
|
|
||||||
from .views import router
|
|
||||||
|
|
||||||
__all__ = ["router"]
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Organization CRUD placeholder."""
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Organization API schemas placeholder."""
|
|
||||||
|
|
@ -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"])
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
"""Tasks API package."""
|
|
||||||
from .views import router
|
|
||||||
|
|
||||||
__all__ = ["router"]
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""Task CRUD placeholder."""
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
"""Users API package."""
|
|
||||||
from .views import router
|
|
||||||
|
|
||||||
__all__ = ["router"]
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""User CRUD placeholder."""
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
"""User API schemas placeholder."""
|
|
||||||
Loading…
Reference in New Issue