Files
bot-pontos/backend/internal/httpapi/dto.go
2026-10-23 22:31:02 -03:00

275 lines
9.5 KiB
Go

package httpapi
import (
"time"
"botpontos/internal/adapters/postgres"
"botpontos/internal/app"
"botpontos/internal/domain"
)
// Formatos JSON da API. Os tipos TypeScript equivalentes ficam em web/lib/types.ts.
type userJSON struct {
ID int64 `json:"id"`
TelegramChatID int64 `json:"telegram_chat_id"`
DisplayName string `json:"display_name"`
Status string `json:"status"`
IsBotAdmin bool `json:"is_bot_admin"`
NotificationsEnabled bool `json:"notifications_enabled"`
PausedReason *string `json:"paused_reason"`
Timezone string `json:"timezone"`
CreatedAt time.Time `json:"created_at"`
}
func toUser(u domain.User) userJSON {
var reason *string
if u.PausedReason != nil {
v := string(*u.PausedReason)
reason = &v
}
return userJSON{
ID: u.ID, TelegramChatID: u.TelegramChatID, DisplayName: u.DisplayName, Status: string(u.Status),
IsBotAdmin: u.IsBotAdmin, NotificationsEnabled: u.NotificationsEnabled, PausedReason: reason,
Timezone: u.Timezone, CreatedAt: u.CreatedAt,
}
}
type userSummaryJSON struct {
userJSON
HasCredential bool `json:"has_credential"`
CredentialValidated bool `json:"credential_validated"`
ConsecutiveFailures int `json:"consecutive_failures"`
CustomWindow bool `json:"custom_window"`
}
func toUserSummary(s postgres.UserSummary) userSummaryJSON {
return userSummaryJSON{
userJSON: toUser(s.User), HasCredential: s.HasCredential, CredentialValidated: s.CredentialValidated,
ConsecutiveFailures: s.ConsecutiveFailures, CustomWindow: s.CustomWindow,
}
}
type windowJSON struct {
QueryWindowStart string `json:"query_window_start"`
QueryWindowEnd string `json:"query_window_end"`
DayCheckDeadline string `json:"day_check_deadline"`
NoPointsRecheckUntil string `json:"no_points_recheck_until"`
}
func toWindow(w domain.QueryWindow) windowJSON {
return windowJSON{
QueryWindowStart: w.Start.String(), QueryWindowEnd: w.End.String(),
DayCheckDeadline: w.DayCheckDeadline.String(), NoPointsRecheckUntil: w.NoPointsRecheckUntil.String(),
}
}
// parse converte para o domínio, com erros por campo.
func (w windowJSON) parse(prefix string) (domain.QueryWindow, error) {
var out domain.QueryWindow
var errs domain.ValidationErrors
fields := []struct {
name string
value string
target *domain.ClockTime
}{
{"query_window_start", w.QueryWindowStart, &out.Start},
{"query_window_end", w.QueryWindowEnd, &out.End},
{"day_check_deadline", w.DayCheckDeadline, &out.DayCheckDeadline},
{"no_points_recheck_until", w.NoPointsRecheckUntil, &out.NoPointsRecheckUntil},
}
for _, f := range fields {
c, err := domain.ParseClock(f.value)
if err != nil {
errs.Add(prefix+f.name, "horário inválido (use HH:MM)")
continue
}
*f.target = c
}
return out, errs.OrNil()
}
type scheduleJSON struct {
LunchMinutes int `json:"lunch_minutes"`
DailyWorkMinutes int `json:"daily_work_minutes"`
Entrada2LeadMinutes int `json:"entrada2_lead_minutes"`
Saida2LeadMinutes int `json:"saida2_lead_minutes"`
Window *windowJSON `json:"window"`
}
func toSchedule(s domain.WorkSchedule) scheduleJSON {
out := scheduleJSON{
LunchMinutes: s.LunchMinutes, DailyWorkMinutes: s.DailyWorkMinutes,
Entrada2LeadMinutes: s.Entrada2LeadMinutes, Saida2LeadMinutes: s.Saida2LeadMinutes,
}
if s.Window != nil {
w := toWindow(*s.Window)
out.Window = &w
}
return out
}
type credentialJSON struct {
APIEmail string `json:"api_email"`
Validated bool `json:"validated"`
LastValidatedAt *time.Time `json:"last_validated_at"`
LastError *string `json:"last_error"`
ConsecutiveFailures int `json:"consecutive_failures"`
LastFailureAt *time.Time `json:"last_failure_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type dayJSON struct {
Day string `json:"day"`
Status string `json:"status"`
ZeroPointChecks int `json:"zero_point_checks"`
LastPointCount int `json:"last_point_count"`
NextCheckAt *time.Time `json:"next_check_at"`
ResumedBy *string `json:"resumed_by"`
}
func toDay(d domain.UserDay) dayJSON {
var resumed *string
if d.ResumedBy != nil {
v := string(*d.ResumedBy)
resumed = &v
}
return dayJSON{
Day: d.Day, Status: string(d.Status), ZeroPointChecks: d.ZeroPointChecks,
LastPointCount: d.LastPointCount, NextCheckAt: d.NextCheckAt, ResumedBy: resumed,
}
}
type userDetailJSON struct {
User userJSON `json:"user"`
Schedule scheduleJSON `json:"schedule"`
Credential *credentialJSON `json:"credential"`
Today dayJSON `json:"today"`
EffectiveWindow windowJSON `json:"effective_window"`
CustomWindow bool `json:"custom_window"`
}
func toUserDetail(d app.UserDetail) userDetailJSON {
out := userDetailJSON{
User: toUser(d.User), Schedule: toSchedule(d.Schedule), Today: toDay(d.Today),
EffectiveWindow: toWindow(d.Window), CustomWindow: d.CustomWindow,
}
if c := d.Credential; c != nil {
out.Credential = &credentialJSON{
APIEmail: c.APIEmail, Validated: c.IsValidated(), LastValidatedAt: c.LastValidatedAt,
LastError: c.LastError, ConsecutiveFailures: c.ConsecutiveFailures, LastFailureAt: c.LastFailureAt,
UpdatedAt: c.UpdatedAt,
}
}
return out
}
type todayJSON struct {
Day string `json:"day"`
Timezone string `json:"timezone"`
Points []time.Time `json:"points"`
Invalid bool `json:"invalid"`
Overtime bool `json:"overtime"`
Entrada2At *time.Time `json:"entrada2_at"`
Saida2At *time.Time `json:"saida2_at"`
Saida2Error string `json:"saida2_error,omitempty"`
DayState dayJSON `json:"day_state"`
Window windowJSON `json:"window"`
CustomWindow bool `json:"custom_window"`
}
func toToday(v app.TodayView) todayJSON {
points := v.Points
if points == nil {
points = []time.Time{}
}
return todayJSON{
Day: v.Day, Timezone: v.User.Timezone, Points: points, Invalid: v.Invalid, Overtime: v.Overtime,
Entrada2At: v.Entrada2At, Saida2At: v.Saida2At, Saida2Error: v.Saida2Error,
DayState: toDay(v.DayState), Window: toWindow(v.Window), CustomWindow: v.CustomWindow,
}
}
type notificationJSON struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
UserName string `json:"user_name,omitempty"`
ReferenceDate string `json:"reference_date"`
Type string `json:"type"`
ScheduledAt time.Time `json:"scheduled_at"`
PredictedAt time.Time `json:"predicted_at"`
Status string `json:"status"`
Attempts int `json:"attempts"`
SentAt *time.Time `json:"sent_at"`
ErrorMessage *string `json:"error_message"`
}
func toNotification(n domain.Notification, userName string) notificationJSON {
return notificationJSON{
ID: n.ID, UserID: n.UserID, UserName: userName, ReferenceDate: n.ReferenceDate, Type: string(n.Type),
ScheduledAt: n.ScheduledAt, PredictedAt: n.PredictedAt, Status: string(n.Status), Attempts: n.Attempts,
SentAt: n.SentAt, ErrorMessage: n.ErrorMessage,
}
}
type auditJSON struct {
ID int64 `json:"id"`
OccurredAt time.Time `json:"occurred_at"`
Source string `json:"source"`
ActorUserID *int64 `json:"actor_user_id"`
ActorUserName *string `json:"actor_user_name"`
ActorAccount *int64 `json:"actor_account"`
ActorUsername *string `json:"actor_username"`
TargetUserID *int64 `json:"target_user_id"`
TargetUserName *string `json:"target_user_name"`
EventType string `json:"event_type"`
Success bool `json:"success"`
Details map[string]any `json:"details"`
}
func toAudit(l domain.AuditLog) auditJSON {
return auditJSON{
ID: l.ID, OccurredAt: l.OccurredAt, Source: string(l.Source), ActorUserID: l.ActorUserID,
ActorUserName: l.ActorUserName, ActorAccount: l.ActorAccount, ActorUsername: l.ActorUsername,
TargetUserID: l.TargetUserID, TargetUserName: l.TargetUserName, EventType: l.EventType,
Success: l.Success, Details: l.Details,
}
}
type holidayJSON struct {
Day string `json:"day"`
Name string `json:"name"`
Source string `json:"source"`
}
type accountJSON struct {
ID int64 `json:"id"`
Username string `json:"username"`
Role string `json:"role"`
UserID *int64 `json:"user_id"`
Disabled bool `json:"disabled"`
LockedUntil *time.Time `json:"locked_until"`
LastLoginAt *time.Time `json:"last_login_at"`
CreatedAt time.Time `json:"created_at"`
}
func toAccount(a domain.WebAccount) accountJSON {
return accountJSON{
ID: a.ID, Username: a.Username, Role: string(a.Role), UserID: a.UserID, Disabled: a.IsDisabled(),
LockedUntil: a.LockedUntil, LastLoginAt: a.LastLoginAt, CreatedAt: a.CreatedAt,
}
}
type dashboardJSON struct {
UsersByStatus map[string]int `json:"users_by_status"`
NotificationsEnabled int `json:"notifications_enabled"`
PausedByAPIFailures int `json:"paused_by_api_failures"`
TodayByStatus map[string]int `json:"today_by_status"`
FailedLast7Days int `json:"failed_last_7_days"`
ExpiredLast7Days int `json:"expired_last_7_days"`
CustomWindows int `json:"custom_windows"`
OutageSince *string `json:"outage_since"`
HolidayFallbackDay *string `json:"holiday_fallback_day"`
Today string `json:"today"`
}