145 lines
6.0 KiB
Go
145 lines
6.0 KiB
Go
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"`
|
||
}
|