package handlers import ( "time" "building-mgmt/internal/models" ) // Формы ответов API. Нужны дважды: как фактический JSON в хендлерах // и как схема в Swagger — чтобы документация не разъезжалась с кодом. // ErrorResponse — единая форма ошибки. Текст фиксированный и короткий, // детали БД в него не попадают (они только в логе). type ErrorResponse struct { Error string `json:"error"` } // TokenResponse — ответ login, register и refresh. type TokenResponse struct { // Access-токен: JWT на auth.AccessTTL. Token string `json:"token"` // Срок жизни access-токена в секундах. ExpiresIn int `json:"expires_in"` User *models.Profile `json:"user"` // Refresh-токен в JSON не отдаётся: он живёт только в httpOnly-куке // (см. AuthHandler.setRefreshCookie) — из JavaScript ему не достаться. } // OKResponse — ответ для действий без сущности на выходе (logout). type OKResponse struct { OK bool `json:"ok"` // Revoked — сколько сессий закрыто; заполняется только в logout-all. Revoked int64 `json:"revoked,omitempty"` } // Формы списков. Список — это всегда страница (items + метаданные), // а не голый массив: total считается COUNT(*) OVER() до обрезки, // limit/offset отражают то, что реально применено. // TicketListResponse — страница GET /tickets. type TicketListResponse struct { Items []models.Ticket `json:"items"` Total int `json:"total"` Limit int `json:"limit"` Offset int `json:"offset"` } // ProjectListResponse — страница GET /projects. type ProjectListResponse struct { Items []models.Project `json:"items"` Total int `json:"total"` Limit int `json:"limit"` Offset int `json:"offset"` } // ObjectListResponse — страница GET /projects/{id}/objects. type ObjectListResponse struct { Items []models.Object `json:"items"` Total int `json:"total"` Limit int `json:"limit"` Offset int `json:"offset"` } // ProfileListResponse — страница пользователей: GET /users. type ProfileListResponse struct { Items []models.Profile `json:"items"` Total int `json:"total"` Limit int `json:"limit"` Offset int `json:"offset"` } // ProjectWorkerListResponse — страница состава проекта // (GET /projects/{id}/workers): профили плюс статус членства. type ProjectWorkerListResponse struct { Items []models.ProjectWorker `json:"items"` Total int `json:"total"` Limit int `json:"limit"` Offset int `json:"offset"` } // InvitationListResponse — страница приглашений мастера (GET /invitations). type InvitationListResponse struct { Items []models.ProjectInvitation `json:"items"` Total int `json:"total"` Limit int `json:"limit"` Offset int `json:"offset"` } // TicketEventListResponse — страница журнала заявки (GET /tickets/{id}/events). type TicketEventListResponse struct { Items []models.TicketEvent `json:"items"` Total int `json:"total"` Limit int `json:"limit"` Offset int `json:"offset"` } // PhotoListResponse — страница списка фото (GET /tickets/{id}/photos). type PhotoListResponse struct { Items []models.TicketPhoto `json:"items"` Total int `json:"total"` Limit int `json:"limit"` Offset int `json:"offset"` // MaxPerTicket — лимит файлов на заявку (PHOTO_MAX_PER_TICKET): // UI рисует «n / N», не зная настройки окружения. MaxPerTicket int `json:"max_per_ticket"` } // RecurringRuleListResponse — страница правил повторения (GET /recurring). type RecurringRuleListResponse struct { Items []models.RecurringRule `json:"items"` Total int `json:"total"` Limit int `json:"limit"` Offset int `json:"offset"` } // RecurringRunListResponse — страница истории срабатываний правила // (GET /recurring/{id}/runs). type RecurringRunListResponse struct { Items []models.RecurringRun `json:"items"` Total int `json:"total"` Limit int `json:"limit"` Offset int `json:"offset"` } // InvitationResponse — ответ на POST /projects/{id}/workers: с 0005 это // приглашение, а не готовое членство, поэтому в ответе его статус и срок. type InvitationResponse struct { OK bool `json:"ok"` // Всегда "invited": стать участником мастер может только сам, через // POST /projects/{id}/invitation/accept. Status string `json:"status"` // До какого срока мастер может ответить (см. repository.InvitationTTL). ExpiresAt time.Time `json:"expires_at"` } // PasswordResetResponse — ответ на POST /users/{id}/password-reset. // Сам токен живёт только в этом ответе: в БД — лишь sha256, поэтому // выпущенный токен нельзя подсмотреть даже с доступом к базе. type PasswordResetResponse struct { OK bool `json:"ok"` Token string `json:"token"` // См. repository.ResetTTL (24 часа). ExpiresAt time.Time `json:"expires_at"` }