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>
333 lines
13 KiB
Go
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"`
|
|
}
|