Backend changes: - Add WithSummaryResponse wrappers for Task, TaskCompletion, and Residence CRUD - Update services to return summary with all mutations (create, update, delete) - Update handlers to pass through new response types - Add getSummaryForUser helper for fetching summary in CRUD operations - Wire ResidenceService into TaskService for summary access - Add summary field to JoinResidenceResponse This optimization eliminates the need for a separate getSummary() call after every task/residence mutation, reducing network calls from 2 to 1. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
215 lines
7.3 KiB
Go
215 lines
7.3 KiB
Go
package responses
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/shopspring/decimal"
|
|
|
|
"github.com/treytartt/casera-api/internal/models"
|
|
)
|
|
|
|
// ResidenceTypeResponse represents a residence type in the API response
|
|
type ResidenceTypeResponse struct {
|
|
ID uint `json:"id"`
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// ResidenceUserResponse represents a user with access to a residence
|
|
type ResidenceUserResponse struct {
|
|
ID uint `json:"id"`
|
|
Username string `json:"username"`
|
|
Email string `json:"email"`
|
|
FirstName string `json:"first_name"`
|
|
LastName string `json:"last_name"`
|
|
}
|
|
|
|
// ResidenceResponse represents a residence in the API response
|
|
type ResidenceResponse struct {
|
|
ID uint `json:"id"`
|
|
OwnerID uint `json:"owner_id"`
|
|
Owner *ResidenceUserResponse `json:"owner,omitempty"`
|
|
Users []ResidenceUserResponse `json:"users,omitempty"`
|
|
Name string `json:"name"`
|
|
PropertyTypeID *uint `json:"property_type_id"`
|
|
PropertyType *ResidenceTypeResponse `json:"property_type,omitempty"`
|
|
StreetAddress string `json:"street_address"`
|
|
ApartmentUnit string `json:"apartment_unit"`
|
|
City string `json:"city"`
|
|
StateProvince string `json:"state_province"`
|
|
PostalCode string `json:"postal_code"`
|
|
Country string `json:"country"`
|
|
Bedrooms *int `json:"bedrooms"`
|
|
Bathrooms *decimal.Decimal `json:"bathrooms"`
|
|
SquareFootage *int `json:"square_footage"`
|
|
LotSize *decimal.Decimal `json:"lot_size"`
|
|
YearBuilt *int `json:"year_built"`
|
|
Description string `json:"description"`
|
|
PurchaseDate *time.Time `json:"purchase_date"`
|
|
PurchasePrice *decimal.Decimal `json:"purchase_price"`
|
|
IsPrimary bool `json:"is_primary"`
|
|
IsActive bool `json:"is_active"`
|
|
OverdueCount int `json:"overdue_count"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// TotalSummary represents summary statistics for all residences
|
|
type TotalSummary struct {
|
|
TotalResidences int `json:"total_residences"`
|
|
TotalTasks int `json:"total_tasks"`
|
|
TotalPending int `json:"total_pending"`
|
|
TotalOverdue int `json:"total_overdue"`
|
|
TasksDueNextWeek int `json:"tasks_due_next_week"`
|
|
TasksDueNextMonth int `json:"tasks_due_next_month"`
|
|
}
|
|
|
|
// MyResidencesResponse represents the response for my-residences endpoint
|
|
type MyResidencesResponse struct {
|
|
Residences []ResidenceResponse `json:"residences"`
|
|
Summary TotalSummary `json:"summary"`
|
|
}
|
|
|
|
// ResidenceWithSummaryResponse wraps ResidenceResponse with TotalSummary for CRUD operations
|
|
type ResidenceWithSummaryResponse struct {
|
|
Data ResidenceResponse `json:"data"`
|
|
Summary TotalSummary `json:"summary"`
|
|
}
|
|
|
|
// ResidenceDeleteWithSummaryResponse for delete operations
|
|
type ResidenceDeleteWithSummaryResponse struct {
|
|
Data string `json:"data"`
|
|
Summary TotalSummary `json:"summary"`
|
|
}
|
|
|
|
// ShareCodeResponse represents a share code in the API response
|
|
type ShareCodeResponse struct {
|
|
ID uint `json:"id"`
|
|
Code string `json:"code"`
|
|
ResidenceID uint `json:"residence_id"`
|
|
CreatedByID uint `json:"created_by_id"`
|
|
IsActive bool `json:"is_active"`
|
|
ExpiresAt *time.Time `json:"expires_at"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// JoinResidenceResponse represents the response after joining a residence
|
|
type JoinResidenceResponse struct {
|
|
Message string `json:"message"`
|
|
Residence ResidenceResponse `json:"residence"`
|
|
Summary TotalSummary `json:"summary"`
|
|
}
|
|
|
|
// GenerateShareCodeResponse represents the response after generating a share code
|
|
type GenerateShareCodeResponse struct {
|
|
Message string `json:"message"`
|
|
ShareCode ShareCodeResponse `json:"share_code"`
|
|
}
|
|
|
|
// SharePackageResponse represents the response for generating a share package
|
|
// This contains the share code plus metadata for the .casera file
|
|
type SharePackageResponse struct {
|
|
ShareCode string `json:"share_code"`
|
|
ResidenceName string `json:"residence_name"`
|
|
SharedBy string `json:"shared_by"`
|
|
ExpiresAt *time.Time `json:"expires_at,omitempty"`
|
|
}
|
|
|
|
// === Factory Functions ===
|
|
|
|
// NewResidenceUserResponse creates a ResidenceUserResponse from a User model
|
|
func NewResidenceUserResponse(user *models.User) *ResidenceUserResponse {
|
|
if user == nil {
|
|
return nil
|
|
}
|
|
return &ResidenceUserResponse{
|
|
ID: user.ID,
|
|
Username: user.Username,
|
|
Email: user.Email,
|
|
FirstName: user.FirstName,
|
|
LastName: user.LastName,
|
|
}
|
|
}
|
|
|
|
// NewResidenceTypeResponse creates a ResidenceTypeResponse from a ResidenceType model
|
|
func NewResidenceTypeResponse(rt *models.ResidenceType) *ResidenceTypeResponse {
|
|
if rt == nil {
|
|
return nil
|
|
}
|
|
return &ResidenceTypeResponse{
|
|
ID: rt.ID,
|
|
Name: rt.Name,
|
|
}
|
|
}
|
|
|
|
// NewResidenceResponse creates a ResidenceResponse from a Residence model
|
|
func NewResidenceResponse(residence *models.Residence) ResidenceResponse {
|
|
resp := ResidenceResponse{
|
|
ID: residence.ID,
|
|
OwnerID: residence.OwnerID,
|
|
Name: residence.Name,
|
|
PropertyTypeID: residence.PropertyTypeID,
|
|
StreetAddress: residence.StreetAddress,
|
|
ApartmentUnit: residence.ApartmentUnit,
|
|
City: residence.City,
|
|
StateProvince: residence.StateProvince,
|
|
PostalCode: residence.PostalCode,
|
|
Country: residence.Country,
|
|
Bedrooms: residence.Bedrooms,
|
|
Bathrooms: residence.Bathrooms,
|
|
SquareFootage: residence.SquareFootage,
|
|
LotSize: residence.LotSize,
|
|
YearBuilt: residence.YearBuilt,
|
|
Description: residence.Description,
|
|
PurchaseDate: residence.PurchaseDate,
|
|
PurchasePrice: residence.PurchasePrice,
|
|
IsPrimary: residence.IsPrimary,
|
|
IsActive: residence.IsActive,
|
|
CreatedAt: residence.CreatedAt,
|
|
UpdatedAt: residence.UpdatedAt,
|
|
}
|
|
|
|
// Include owner if loaded
|
|
if residence.Owner.ID != 0 {
|
|
resp.Owner = NewResidenceUserResponse(&residence.Owner)
|
|
}
|
|
|
|
// Include property type if loaded
|
|
if residence.PropertyType != nil {
|
|
resp.PropertyType = NewResidenceTypeResponse(residence.PropertyType)
|
|
}
|
|
|
|
// Include shared users if loaded
|
|
if len(residence.Users) > 0 {
|
|
resp.Users = make([]ResidenceUserResponse, len(residence.Users))
|
|
for i, user := range residence.Users {
|
|
resp.Users[i] = *NewResidenceUserResponse(&user)
|
|
}
|
|
} else {
|
|
resp.Users = []ResidenceUserResponse{}
|
|
}
|
|
|
|
return resp
|
|
}
|
|
|
|
// NewResidenceListResponse creates a list of residence responses
|
|
func NewResidenceListResponse(residences []models.Residence) []ResidenceResponse {
|
|
results := make([]ResidenceResponse, len(residences))
|
|
for i, r := range residences {
|
|
results[i] = NewResidenceResponse(&r)
|
|
}
|
|
return results
|
|
}
|
|
|
|
// NewShareCodeResponse creates a ShareCodeResponse from a ResidenceShareCode model
|
|
func NewShareCodeResponse(sc *models.ResidenceShareCode) ShareCodeResponse {
|
|
return ShareCodeResponse{
|
|
ID: sc.ID,
|
|
Code: sc.Code,
|
|
ResidenceID: sc.ResidenceID,
|
|
CreatedByID: sc.CreatedByID,
|
|
IsActive: sc.IsActive,
|
|
ExpiresAt: sc.ExpiresAt,
|
|
CreatedAt: sc.CreatedAt,
|
|
}
|
|
}
|