Files
honeyDueAPI/internal/admin/dto/requests.go
Trey t 6dac34e373 Migrate from Gin to Echo framework and add comprehensive integration tests
Major changes:
- Migrate all handlers from Gin to Echo framework
- Add new apperrors, echohelpers, and validator packages
- Update middleware for Echo compatibility
- Add ArchivedHandler to task categorization chain (archived tasks go to cancelled_tasks column)
- Add 6 new integration tests:
  - RecurringTaskLifecycle: NextDueDate advancement for weekly/monthly tasks
  - MultiUserSharing: Complex sharing with user removal
  - TaskStateTransitions: All state transitions and kanban column changes
  - DateBoundaryEdgeCases: Threshold boundary testing
  - CascadeOperations: Residence deletion cascade effects
  - MultiUserOperations: Shared residence collaboration
- Add single-purpose repository functions for kanban columns (GetOverdueTasks, GetDueSoonTasks, etc.)
- Fix RemoveUser route param mismatch (userId -> user_id)
- Fix determineExpectedColumn helper to correctly prioritize in_progress over overdue

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 13:52:08 -06:00

333 lines
13 KiB
Go

package dto
// PaginationParams holds pagination query parameters
type PaginationParams struct {
Page int `form:"page" validate:"omitempty,min=1"`
PerPage int `form:"per_page" validate:"omitempty,min=1,max=10000"`
Search string `form:"search"`
SortBy string `form:"sort_by"`
SortDir string `form:"sort_dir" validate:"omitempty,oneof=asc desc"`
}
// GetPage returns the page number with default
func (p *PaginationParams) GetPage() int {
if p.Page < 1 {
return 1
}
return p.Page
}
// GetPerPage returns items per page with default
func (p *PaginationParams) GetPerPage() int {
if p.PerPage < 1 {
return 20
}
if p.PerPage > 10000 {
return 10000
}
return p.PerPage
}
// GetOffset calculates the database offset
func (p *PaginationParams) GetOffset() int {
return (p.GetPage() - 1) * p.GetPerPage()
}
// GetSortDir returns sort direction with default
func (p *PaginationParams) GetSortDir() string {
if p.SortDir == "asc" {
return "ASC"
}
return "DESC"
}
// UserFilters holds user-specific filter parameters
type UserFilters struct {
PaginationParams
IsActive *bool `form:"is_active"`
IsStaff *bool `form:"is_staff"`
IsSuperuser *bool `form:"is_superuser"`
Verified *bool `form:"verified"`
}
// CreateUserRequest for creating a new user
type CreateUserRequest struct {
Username string `json:"username" validate:"required,min=3,max=150"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8"`
FirstName string `json:"first_name" validate:"max=150"`
LastName string `json:"last_name" validate:"max=150"`
PhoneNumber string `json:"phone_number" validate:"max=20"`
IsActive *bool `json:"is_active"`
IsStaff *bool `json:"is_staff"`
IsSuperuser *bool `json:"is_superuser"`
}
// UpdateUserRequest for updating a user
type UpdateUserRequest struct {
Username *string `json:"username" validate:"omitempty,min=3,max=150"`
Email *string `json:"email" validate:"omitempty,email"`
Password *string `json:"password" validate:"omitempty,min=8"`
FirstName *string `json:"first_name" validate:"omitempty,max=150"`
LastName *string `json:"last_name" validate:"omitempty,max=150"`
PhoneNumber *string `json:"phone_number" validate:"omitempty,max=20"`
IsActive *bool `json:"is_active"`
IsStaff *bool `json:"is_staff"`
IsSuperuser *bool `json:"is_superuser"`
Verified *bool `json:"verified"`
}
// BulkDeleteRequest for bulk delete operations
type BulkDeleteRequest struct {
IDs []uint `json:"ids" validate:"required,min=1"`
}
// ResidenceFilters holds residence-specific filter parameters
type ResidenceFilters struct {
PaginationParams
IsActive *bool `form:"is_active"`
OwnerID *uint `form:"owner_id"`
}
// UpdateResidenceRequest for updating a residence
type UpdateResidenceRequest struct {
OwnerID *uint `json:"owner_id"`
Name *string `json:"name" validate:"omitempty,max=200"`
PropertyTypeID *uint `json:"property_type_id"`
StreetAddress *string `json:"street_address" validate:"omitempty,max=255"`
ApartmentUnit *string `json:"apartment_unit" validate:"omitempty,max=50"`
City *string `json:"city" validate:"omitempty,max=100"`
StateProvince *string `json:"state_province" validate:"omitempty,max=100"`
PostalCode *string `json:"postal_code" validate:"omitempty,max=20"`
Country *string `json:"country" validate:"omitempty,max=100"`
Bedrooms *int `json:"bedrooms"`
Bathrooms *float64 `json:"bathrooms"`
SquareFootage *int `json:"square_footage"`
LotSize *float64 `json:"lot_size"`
YearBuilt *int `json:"year_built"`
Description *string `json:"description"`
PurchaseDate *string `json:"purchase_date"`
PurchasePrice *float64 `json:"purchase_price"`
IsActive *bool `json:"is_active"`
IsPrimary *bool `json:"is_primary"`
}
// TaskFilters holds task-specific filter parameters
type TaskFilters struct {
PaginationParams
ResidenceID *uint `form:"residence_id"`
CategoryID *uint `form:"category_id"`
PriorityID *uint `form:"priority_id"`
InProgress *bool `form:"in_progress"`
IsCancelled *bool `form:"is_cancelled"`
IsArchived *bool `form:"is_archived"`
}
// UpdateTaskRequest for updating a task
type UpdateTaskRequest struct {
ResidenceID *uint `json:"residence_id"`
CreatedByID *uint `json:"created_by_id"`
AssignedToID *uint `json:"assigned_to_id"`
Title *string `json:"title" validate:"omitempty,max=200"`
Description *string `json:"description"`
CategoryID *uint `json:"category_id"`
PriorityID *uint `json:"priority_id"`
FrequencyID *uint `json:"frequency_id"`
InProgress *bool `json:"in_progress"`
DueDate *string `json:"due_date"`
NextDueDate *string `json:"next_due_date"`
EstimatedCost *float64 `json:"estimated_cost"`
ActualCost *float64 `json:"actual_cost"`
ContractorID *uint `json:"contractor_id"`
ParentTaskID *uint `json:"parent_task_id"`
IsCancelled *bool `json:"is_cancelled"`
IsArchived *bool `json:"is_archived"`
}
// ContractorFilters holds contractor-specific filter parameters
type ContractorFilters struct {
PaginationParams
IsActive *bool `form:"is_active"`
IsFavorite *bool `form:"is_favorite"`
ResidenceID *uint `form:"residence_id"`
}
// UpdateContractorRequest for updating a contractor
type UpdateContractorRequest struct {
ResidenceID *uint `json:"residence_id"`
CreatedByID *uint `json:"created_by_id"`
Name *string `json:"name" validate:"omitempty,max=200"`
Company *string `json:"company" validate:"omitempty,max=200"`
Phone *string `json:"phone" validate:"omitempty,max=20"`
Email *string `json:"email" validate:"omitempty,email"`
Website *string `json:"website" validate:"omitempty,max=200"`
Notes *string `json:"notes"`
StreetAddress *string `json:"street_address" validate:"omitempty,max=255"`
City *string `json:"city" validate:"omitempty,max=100"`
StateProvince *string `json:"state_province" validate:"omitempty,max=100"`
PostalCode *string `json:"postal_code" validate:"omitempty,max=20"`
Rating *float64 `json:"rating"`
IsFavorite *bool `json:"is_favorite"`
IsActive *bool `json:"is_active"`
SpecialtyIDs []uint `json:"specialty_ids"`
}
// DocumentFilters holds document-specific filter parameters
type DocumentFilters struct {
PaginationParams
IsActive *bool `form:"is_active"`
ResidenceID *uint `form:"residence_id"`
DocumentType *string `form:"document_type"`
}
// UpdateDocumentRequest for updating a document
type UpdateDocumentRequest struct {
ResidenceID *uint `json:"residence_id"`
CreatedByID *uint `json:"created_by_id"`
Title *string `json:"title" validate:"omitempty,max=200"`
Description *string `json:"description"`
DocumentType *string `json:"document_type"`
FileURL *string `json:"file_url" validate:"omitempty,max=500"`
FileName *string `json:"file_name" validate:"omitempty,max=255"`
FileSize *int64 `json:"file_size"`
MimeType *string `json:"mime_type" validate:"omitempty,max=100"`
PurchaseDate *string `json:"purchase_date"`
ExpiryDate *string `json:"expiry_date"`
PurchasePrice *float64 `json:"purchase_price"`
Vendor *string `json:"vendor" validate:"omitempty,max=200"`
SerialNumber *string `json:"serial_number" validate:"omitempty,max=100"`
ModelNumber *string `json:"model_number" validate:"omitempty,max=100"`
Provider *string `json:"provider" validate:"omitempty,max=200"`
ProviderContact *string `json:"provider_contact" validate:"omitempty,max=200"`
ClaimPhone *string `json:"claim_phone" validate:"omitempty,max=50"`
ClaimEmail *string `json:"claim_email" validate:"omitempty,email"`
ClaimWebsite *string `json:"claim_website" validate:"omitempty,max=500"`
Notes *string `json:"notes"`
TaskID *uint `json:"task_id"`
IsActive *bool `json:"is_active"`
}
// NotificationFilters holds notification-specific filter parameters
type NotificationFilters struct {
PaginationParams
UserID *uint `form:"user_id"`
NotificationType *string `form:"notification_type"`
Sent *bool `form:"sent"`
Read *bool `form:"read"`
}
// UpdateNotificationRequest for updating a notification
type UpdateNotificationRequest struct {
Title *string `json:"title" validate:"omitempty,max=200"`
Body *string `json:"body" validate:"omitempty,max=1000"`
Read *bool `json:"read"`
}
// SubscriptionFilters holds subscription-specific filter parameters
type SubscriptionFilters struct {
PaginationParams
UserID *uint `form:"user_id"`
Tier *string `form:"tier"`
Platform *string `form:"platform"`
AutoRenew *bool `form:"auto_renew"`
Active *bool `form:"active"`
}
// UpdateSubscriptionRequest for updating a subscription
type UpdateSubscriptionRequest struct {
Tier *string `json:"tier" validate:"omitempty,oneof=free premium pro"`
AutoRenew *bool `json:"auto_renew"`
IsFree *bool `json:"is_free"`
Platform *string `json:"platform" validate:"omitempty,max=20"`
SubscribedAt *string `json:"subscribed_at"`
ExpiresAt *string `json:"expires_at"`
CancelledAt *string `json:"cancelled_at"`
}
// CreateResidenceRequest for creating a new residence
type CreateResidenceRequest struct {
OwnerID uint `json:"owner_id" validate:"required"`
Name string `json:"name" validate:"required,max=200"`
PropertyTypeID *uint `json:"property_type_id"`
StreetAddress string `json:"street_address" validate:"max=255"`
ApartmentUnit string `json:"apartment_unit" validate:"max=50"`
City string `json:"city" validate:"max=100"`
StateProvince string `json:"state_province" validate:"max=100"`
PostalCode string `json:"postal_code" validate:"max=20"`
Country string `json:"country" validate:"max=100"`
Bedrooms *int `json:"bedrooms"`
Bathrooms *float64 `json:"bathrooms"`
SquareFootage *int `json:"square_footage"`
YearBuilt *int `json:"year_built"`
Description string `json:"description"`
IsPrimary bool `json:"is_primary"`
}
// CreateTaskRequest for creating a new task
type CreateTaskRequest struct {
ResidenceID uint `json:"residence_id" validate:"required"`
CreatedByID uint `json:"created_by_id" validate:"required"`
Title string `json:"title" validate:"required,max=200"`
Description string `json:"description"`
CategoryID *uint `json:"category_id"`
PriorityID *uint `json:"priority_id"`
FrequencyID *uint `json:"frequency_id"`
InProgress bool `json:"in_progress"`
AssignedToID *uint `json:"assigned_to_id"`
DueDate *string `json:"due_date"`
EstimatedCost *float64 `json:"estimated_cost"`
ContractorID *uint `json:"contractor_id"`
}
// CreateContractorRequest for creating a new contractor
type CreateContractorRequest struct {
ResidenceID *uint `json:"residence_id"`
CreatedByID uint `json:"created_by_id" validate:"required"`
Name string `json:"name" validate:"required,max=200"`
Company string `json:"company" validate:"max=200"`
Phone string `json:"phone" validate:"max=20"`
Email string `json:"email" validate:"omitempty,email"`
Website string `json:"website" validate:"max=200"`
Notes string `json:"notes"`
StreetAddress string `json:"street_address" validate:"max=255"`
City string `json:"city" validate:"max=100"`
StateProvince string `json:"state_province" validate:"max=100"`
PostalCode string `json:"postal_code" validate:"max=20"`
IsFavorite bool `json:"is_favorite"`
SpecialtyIDs []uint `json:"specialty_ids"`
}
// CreateDocumentRequest for creating a new document
type CreateDocumentRequest struct {
ResidenceID uint `json:"residence_id" validate:"required"`
CreatedByID uint `json:"created_by_id" validate:"required"`
Title string `json:"title" validate:"required,max=200"`
Description string `json:"description"`
DocumentType string `json:"document_type" validate:"omitempty,oneof=general warranty receipt contract insurance manual"`
FileURL string `json:"file_url" validate:"max=500"`
FileName string `json:"file_name" validate:"max=255"`
FileSize *int64 `json:"file_size"`
MimeType string `json:"mime_type" validate:"max=100"`
PurchaseDate *string `json:"purchase_date"`
ExpiryDate *string `json:"expiry_date"`
PurchasePrice *float64 `json:"purchase_price"`
Vendor string `json:"vendor" validate:"max=200"`
SerialNumber string `json:"serial_number" validate:"max=100"`
ModelNumber string `json:"model_number" validate:"max=100"`
TaskID *uint `json:"task_id"`
}
// SendTestNotificationRequest for sending a test push notification
type SendTestNotificationRequest struct {
UserID uint `json:"user_id" validate:"required"`
Title string `json:"title" validate:"required,max=200"`
Body string `json:"body" validate:"required,max=500"`
}
// SendTestEmailRequest for sending a test email
type SendTestEmailRequest struct {
UserID uint `json:"user_id" validate:"required"`
Subject string `json:"subject" validate:"required,max=200"`
Body string `json:"body" validate:"required"`
}