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"`
|
|||
|
|
}
|