Initial commit
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
"""Pydantic response schemas for analytics endpoints."""
|
||||
|
||||
from datetime import date
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.services.analytics_service import Age4Projection, WorkspaceOverview
|
||||
|
||||
|
||||
class Age4ProjectionResponse(BaseModel):
|
||||
personal_data_uuid: UUID
|
||||
child_id: UUID
|
||||
birth_date: date
|
||||
fourth_birthday: date
|
||||
total_days: int
|
||||
days_used: int
|
||||
planned_days_before_b4: int
|
||||
projected_left_at_b4: int
|
||||
savable_after_b4: int
|
||||
expiring_at_b4: int
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, p: Age4Projection) -> "Age4ProjectionResponse":
|
||||
return cls(
|
||||
personal_data_uuid=p.personal_data_uuid,
|
||||
child_id=p.child_id,
|
||||
birth_date=p.birth_date,
|
||||
fourth_birthday=p.fourth_birthday,
|
||||
total_days=p.total_days,
|
||||
days_used=p.days_used,
|
||||
planned_days_before_b4=p.planned_days_before_b4,
|
||||
projected_left_at_b4=p.projected_left_at_b4,
|
||||
savable_after_b4=p.savable_after_b4,
|
||||
expiring_at_b4=p.expiring_at_b4,
|
||||
)
|
||||
|
||||
|
||||
class WorkspaceOverviewResponse(BaseModel):
|
||||
workspace_id: UUID
|
||||
child_id: UUID
|
||||
parent_projections: list[Age4ProjectionResponse]
|
||||
combined_days_used: int
|
||||
combined_days_left: int
|
||||
combined_expiring_at_b4: int
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, overview: WorkspaceOverview) -> "WorkspaceOverviewResponse":
|
||||
return cls(
|
||||
workspace_id=overview.workspace_id,
|
||||
child_id=overview.child_id,
|
||||
parent_projections=[
|
||||
Age4ProjectionResponse.from_entity(p) for p in overview.parent_projections
|
||||
],
|
||||
combined_days_used=overview.combined_days_used,
|
||||
combined_days_left=overview.combined_days_left,
|
||||
combined_expiring_at_b4=overview.combined_expiring_at_b4,
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
"""Pydantic schemas for auth endpoints."""
|
||||
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
|
||||
|
||||
class RegisterRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str = Field(min_length=8)
|
||||
display_name: str = Field(min_length=1, max_length=100)
|
||||
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
email: EmailStr
|
||||
password: str
|
||||
|
||||
|
||||
class TokenResponse(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
expires_in: int # seconds until expiry
|
||||
|
||||
|
||||
class UserMeResponse(BaseModel):
|
||||
id: str
|
||||
email: str
|
||||
display_name: str
|
||||
workspace_id: str | None
|
||||
@@ -0,0 +1,53 @@
|
||||
"""API schemas (Pydantic) for BreakPeriod.
|
||||
|
||||
Schemas validate request bodies and shape responses. Separate from domain entities
|
||||
so the wire format can evolve independently.
|
||||
"""
|
||||
|
||||
from datetime import date, datetime
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.entities.break_period import BreakPeriod
|
||||
|
||||
|
||||
class BreakPeriodCreate(BaseModel):
|
||||
workspace_id: UUID
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
start_date: date
|
||||
end_date: date
|
||||
target_weeks: int = Field(ge=1, le=52)
|
||||
|
||||
|
||||
class BreakPeriodUpdate(BaseModel):
|
||||
"""Full replacement — all fields required (PUT semantics). Partial updates are not supported."""
|
||||
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
start_date: date
|
||||
end_date: date
|
||||
target_weeks: int = Field(ge=1, le=52)
|
||||
|
||||
|
||||
class BreakPeriodResponse(BaseModel):
|
||||
id: UUID
|
||||
workspace_id: UUID
|
||||
name: str
|
||||
start_date: date
|
||||
end_date: date
|
||||
target_weeks: int
|
||||
created_at: datetime | None
|
||||
updated_at: datetime | None
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity: BreakPeriod) -> "BreakPeriodResponse":
|
||||
return cls(
|
||||
id=entity.id,
|
||||
workspace_id=entity.workspace_id,
|
||||
name=entity.name,
|
||||
start_date=entity.start_date,
|
||||
end_date=entity.end_date,
|
||||
target_weeks=entity.target_weeks,
|
||||
created_at=entity.created_at,
|
||||
updated_at=entity.updated_at,
|
||||
)
|
||||
@@ -0,0 +1,69 @@
|
||||
"""API schemas (Pydantic) for Child.
|
||||
|
||||
Schemas validate request bodies and shape responses. Separate from domain entities
|
||||
so the wire format can evolve independently.
|
||||
"""
|
||||
|
||||
from datetime import date, datetime
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from app.entities.child import Child
|
||||
|
||||
|
||||
class ChildCreate(BaseModel):
|
||||
workspace_id: UUID
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
birth_date: date
|
||||
preschool_start_date: date | None = None
|
||||
|
||||
@field_validator("preschool_start_date")
|
||||
@classmethod
|
||||
def validate_preschool_date(cls, v: date | None, info) -> date | None:
|
||||
"""Preschool start date must be after birth date if both provided."""
|
||||
if v is None:
|
||||
return v
|
||||
birth_date = info.data.get("birth_date")
|
||||
if birth_date and v <= birth_date:
|
||||
raise ValueError("preschool_start_date must be after birth_date")
|
||||
return v
|
||||
|
||||
|
||||
class ChildUpdate(BaseModel):
|
||||
"""Partial update — all fields optional."""
|
||||
|
||||
name: str | None = Field(None, min_length=1, max_length=255)
|
||||
birth_date: date | None = None
|
||||
preschool_start_date: date | None = None
|
||||
|
||||
@field_validator("preschool_start_date")
|
||||
@classmethod
|
||||
def validate_preschool_date(cls, v: date | None, info) -> date | None:
|
||||
"""Preschool start date must be after birth date if both provided."""
|
||||
if v is None:
|
||||
return v
|
||||
birth_date = info.data.get("birth_date")
|
||||
if birth_date and v <= birth_date:
|
||||
raise ValueError("preschool_start_date must be after birth_date")
|
||||
return v
|
||||
|
||||
|
||||
class ChildResponse(BaseModel):
|
||||
id: UUID
|
||||
workspace_id: UUID
|
||||
name: str
|
||||
birth_date: date
|
||||
preschool_start_date: date | None
|
||||
created_at: datetime | None
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity: Child) -> "ChildResponse":
|
||||
return cls(
|
||||
id=entity.id,
|
||||
workspace_id=entity.workspace_id,
|
||||
name=entity.name,
|
||||
birth_date=entity.birth_date,
|
||||
preschool_start_date=entity.preschool_start_date,
|
||||
created_at=entity.created_at,
|
||||
)
|
||||
@@ -0,0 +1,84 @@
|
||||
"""Pydantic schemas for parental leave fetch endpoints."""
|
||||
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.entities.parental_leave import ParentalLeaveFetch, TierData
|
||||
|
||||
|
||||
class TierDataResponse(BaseModel):
|
||||
sjukpenning_total: int
|
||||
sjukpenning_used: int
|
||||
sjukpenning_left: int
|
||||
lagsta_niva_total: int
|
||||
lagsta_niva_used: int
|
||||
lagsta_niva_left: int
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, td: TierData) -> "TierDataResponse":
|
||||
return cls(
|
||||
sjukpenning_total=td.sjukpenning_total,
|
||||
sjukpenning_used=td.sjukpenning_used,
|
||||
sjukpenning_left=td.sjukpenning_left,
|
||||
lagsta_niva_total=td.lagsta_niva_total,
|
||||
lagsta_niva_used=td.lagsta_niva_used,
|
||||
lagsta_niva_left=td.lagsta_niva_left,
|
||||
)
|
||||
|
||||
|
||||
class ManualFetchRequest(BaseModel):
|
||||
personal_data_uuid: UUID
|
||||
workspace_id: UUID
|
||||
child_id: UUID
|
||||
total_days: int = Field(ge=0, le=480)
|
||||
days_used: int = Field(ge=0, le=480)
|
||||
reserved_days_total: int = Field(default=90, ge=0, le=240)
|
||||
reserved_days_left: int = Field(ge=0, le=240)
|
||||
transferable_days_left: int = Field(ge=0, le=150)
|
||||
dubbeldagar_left: int = Field(default=60, ge=0, le=60)
|
||||
sjukpenning_total: int = Field(default=390, ge=0, le=390)
|
||||
sjukpenning_used: int = Field(ge=0, le=390)
|
||||
lagsta_niva_total: int = Field(default=90, ge=0, le=90)
|
||||
lagsta_niva_used: int = Field(ge=0, le=90)
|
||||
|
||||
|
||||
class ParentalLeaveFetchResponse(BaseModel):
|
||||
id: UUID
|
||||
personal_data_uuid: UUID
|
||||
workspace_id: UUID
|
||||
child_id: UUID
|
||||
source: str
|
||||
fetched_at: datetime
|
||||
total_days: int
|
||||
days_used: int
|
||||
days_left: int
|
||||
reserved_days_total: int
|
||||
reserved_days_left: int
|
||||
transferable_days_left: int
|
||||
dubbeldagar_left: int
|
||||
tier_data: TierDataResponse
|
||||
created_at: datetime | None
|
||||
updated_at: datetime | None
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, fetch: ParentalLeaveFetch) -> "ParentalLeaveFetchResponse":
|
||||
return cls(
|
||||
id=fetch.id,
|
||||
personal_data_uuid=fetch.personal_data_uuid,
|
||||
workspace_id=fetch.workspace_id,
|
||||
child_id=fetch.child_id,
|
||||
source=fetch.source,
|
||||
fetched_at=fetch.fetched_at,
|
||||
total_days=fetch.total_days,
|
||||
days_used=fetch.days_used,
|
||||
days_left=fetch.days_left,
|
||||
reserved_days_total=fetch.reserved_days_total,
|
||||
reserved_days_left=fetch.reserved_days_left,
|
||||
transferable_days_left=fetch.transferable_days_left,
|
||||
dubbeldagar_left=fetch.dubbeldagar_left,
|
||||
tier_data=TierDataResponse.from_entity(fetch.tier_data),
|
||||
created_at=fetch.created_at,
|
||||
updated_at=fetch.updated_at,
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
"""API schemas (Pydantic) for PersonalData.
|
||||
|
||||
Schemas validate request bodies and shape responses. Separate from domain entities
|
||||
so the wire format can evolve independently.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.entities.personal_data import PersonalData
|
||||
|
||||
|
||||
class PersonalDataUpdate(BaseModel):
|
||||
"""Partial update — all fields optional."""
|
||||
|
||||
salary_type: str | None = Field(None, pattern="^(monthly|hourly)$")
|
||||
employment_status: str | None = Field(None, pattern="^(employed|jobseeking|none)$")
|
||||
|
||||
|
||||
class PersonalDataResponse(BaseModel):
|
||||
uuid: UUID
|
||||
workspace_id: UUID
|
||||
name: str
|
||||
salary_type: str
|
||||
employment_status: str
|
||||
created_at: datetime | None
|
||||
deleted_at: datetime | None
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity: PersonalData) -> "PersonalDataResponse":
|
||||
return cls(
|
||||
uuid=entity.uuid,
|
||||
workspace_id=entity.workspace_id,
|
||||
name=entity.name,
|
||||
salary_type=entity.salary_type,
|
||||
employment_status=entity.employment_status,
|
||||
created_at=entity.created_at,
|
||||
deleted_at=entity.deleted_at,
|
||||
)
|
||||
@@ -0,0 +1,54 @@
|
||||
"""API schemas (Pydantic) for PlannedLeaveDay.
|
||||
|
||||
Schemas validate request bodies and shape responses. Separate from domain entities
|
||||
so the wire format can evolve independently.
|
||||
"""
|
||||
|
||||
from datetime import date, datetime
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.entities.planned_leave import PlannedLeaveDay
|
||||
|
||||
|
||||
class PlannedLeaveDayCreate(BaseModel):
|
||||
workspace_id: UUID
|
||||
child_id: UUID
|
||||
parent_personal_data_uuid: UUID
|
||||
date: date
|
||||
day_type: str = Field(pattern="^(sjukpenning|lagsta|vacation)$")
|
||||
is_dubbeldag: bool = False
|
||||
|
||||
|
||||
class PlannedLeaveDayUpdate(BaseModel):
|
||||
"""Partial update — all fields optional."""
|
||||
|
||||
day_type: str | None = Field(None, pattern="^(sjukpenning|lagsta|vacation)$")
|
||||
is_dubbeldag: bool | None = None
|
||||
|
||||
|
||||
class PlannedLeaveDayResponse(BaseModel):
|
||||
id: UUID
|
||||
workspace_id: UUID
|
||||
child_id: UUID
|
||||
parent_personal_data_uuid: UUID
|
||||
date: date
|
||||
day_type: str
|
||||
is_dubbeldag: bool
|
||||
created_at: datetime | None
|
||||
updated_at: datetime | None
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity: PlannedLeaveDay) -> "PlannedLeaveDayResponse":
|
||||
return cls(
|
||||
id=entity.id,
|
||||
workspace_id=entity.workspace_id,
|
||||
child_id=entity.child_id,
|
||||
parent_personal_data_uuid=entity.parent_personal_data_uuid,
|
||||
date=entity.date,
|
||||
day_type=entity.day_type,
|
||||
is_dubbeldag=entity.is_dubbeldag,
|
||||
created_at=entity.created_at,
|
||||
updated_at=entity.updated_at,
|
||||
)
|
||||
@@ -0,0 +1,50 @@
|
||||
"""API schemas (Pydantic) for VacationDays.
|
||||
|
||||
Schemas are the API boundary — they validate request bodies and shape responses.
|
||||
Keep them separate from domain entities so the wire format can evolve
|
||||
independently. This is the REFERENCE for other entities' schemas.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.entities.vacation_days import VacationDays
|
||||
|
||||
|
||||
class VacationDaysCreate(BaseModel):
|
||||
personal_data_uuid: UUID
|
||||
workspace_id: UUID
|
||||
year: int = Field(ge=1900, le=2100)
|
||||
total_days: int = Field(ge=0, le=366)
|
||||
saved_days_in: int = Field(default=0, ge=0, le=366)
|
||||
|
||||
|
||||
class VacationDaysUpdate(BaseModel):
|
||||
total_days: int = Field(ge=0, le=366)
|
||||
saved_days_in: int = Field(default=0, ge=0, le=366)
|
||||
|
||||
|
||||
class VacationDaysResponse(BaseModel):
|
||||
id: UUID
|
||||
personal_data_uuid: UUID
|
||||
workspace_id: UUID
|
||||
year: int
|
||||
total_days: int
|
||||
saved_days_in: int
|
||||
created_at: datetime | None
|
||||
updated_at: datetime | None
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, entity: VacationDays) -> "VacationDaysResponse":
|
||||
return cls(
|
||||
id=entity.id,
|
||||
personal_data_uuid=entity.personal_data_uuid,
|
||||
workspace_id=entity.workspace_id,
|
||||
year=entity.year,
|
||||
total_days=entity.total_days,
|
||||
saved_days_in=entity.saved_days_in,
|
||||
created_at=entity.created_at,
|
||||
updated_at=entity.updated_at,
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
"""Pydantic schemas for workspace and invitation endpoints."""
|
||||
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.entities.workspace import Workspace
|
||||
from app.entities.workspace_invitation import WorkspaceInvitation
|
||||
|
||||
|
||||
class WorkspaceCreate(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=100)
|
||||
|
||||
|
||||
class WorkspaceUpdate(BaseModel):
|
||||
name: str | None = Field(default=None, min_length=1, max_length=100)
|
||||
planning_goal: str | None = None
|
||||
|
||||
|
||||
class WorkspaceResponse(BaseModel):
|
||||
id: UUID
|
||||
name: str
|
||||
planning_goal: str
|
||||
created_at: datetime | None
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, ws: Workspace) -> "WorkspaceResponse":
|
||||
return cls(id=ws.id, name=ws.name, planning_goal=ws.planning_goal, created_at=ws.created_at)
|
||||
|
||||
|
||||
class PersonalDataResponse(BaseModel):
|
||||
uuid: UUID
|
||||
workspace_id: UUID
|
||||
name: str
|
||||
salary_type: str
|
||||
employment_status: str
|
||||
created_at: datetime | None
|
||||
|
||||
|
||||
class PersonalDataCreate(BaseModel):
|
||||
display_name: str = Field(min_length=1, max_length=100)
|
||||
|
||||
|
||||
class InviteResponse(BaseModel):
|
||||
token: str # raw one-time token — share out-of-band; not stored on backend
|
||||
|
||||
|
||||
class RedeemInviteRequest(BaseModel):
|
||||
token: str
|
||||
|
||||
|
||||
class InvitationResponse(BaseModel):
|
||||
id: UUID
|
||||
workspace_id: UUID
|
||||
status: str
|
||||
expires_at: datetime
|
||||
created_at: datetime
|
||||
|
||||
@classmethod
|
||||
def from_entity(cls, inv: WorkspaceInvitation) -> "InvitationResponse":
|
||||
return cls(
|
||||
id=inv.id,
|
||||
workspace_id=inv.workspace_id,
|
||||
status=inv.status,
|
||||
expires_at=inv.expires_at,
|
||||
created_at=inv.created_at,
|
||||
)
|
||||
Reference in New Issue
Block a user