package httpapi import ( "net/http" "botpontos/internal/adapters/postgres" "botpontos/internal/app" "botpontos/internal/domain" "botpontos/internal/httpx" ) func actorOf(r *http.Request) app.Actor { return app.WebActor(sessionFrom(r).AccountID) } // --- autenticação --- func (a *API) login(w http.ResponseWriter, r *http.Request) { if !a.sameOrigin(r) { httpx.WriteProblem(w, http.StatusForbidden, "Origem da requisição não permitida", "BAD_ORIGIN") return } var body struct { Username string `json:"username"` Password string `json:"password"` } if !decode(w, r, &body) { return } result, err := a.svc.Login(r.Context(), body.Username, body.Password, a.clientIP(r), r.UserAgent()) if err != nil { a.writeError(w, err) return } setSessionCookies(w, result.Token, result.CSRFToken, result.ExpiresAt, a.cfg.Web.CookieSecure) httpx.WriteJSON(w, http.StatusOK, map[string]any{"account": toAccount(result.Account), "expires_at": result.ExpiresAt}) } func (a *API) logout(w http.ResponseWriter, r *http.Request) { if err := a.svc.Logout(r.Context(), sessionFrom(r)); err != nil { a.writeError(w, err) return } clearCookies(w, a.cfg.Web.CookieSecure) w.WriteHeader(http.StatusNoContent) } func (a *API) me(w http.ResponseWriter, r *http.Request) { s := sessionFrom(r) httpx.WriteJSON(w, http.StatusOK, map[string]any{"account": toAccount(s.Account), "expires_at": s.ExpiresAt}) } func (a *API) dashboard(w http.ResponseWriter, r *http.Request) { o, err := a.svc.Overview(r.Context()) if err != nil { a.writeError(w, err) return } if account := sessionFrom(r).Account; isRestricted(account) { // Início da conta restrita: sem contadores globais, só os avisos do // sistema e os lembretes de hoje do próprio usuário. today := a.svc.TodayDate() items, err := a.svc.Store().ListNotifications(r.Context(), postgres.NotificationFilter{Day: &today, UserID: account.UserID, Limit: 200}) if err != nil { a.writeError(w, err) return } byStatus := map[string]int{} for _, v := range items { byStatus[string(v.Notification.Status)]++ } httpx.WriteJSON(w, http.StatusOK, dashboardJSON{ UsersByStatus: map[string]int{}, TodayByStatus: byStatus, OutageSince: o.OutageSince, HolidayFallbackDay: o.HolidayFallback, Today: today, }) return } httpx.WriteJSON(w, http.StatusOK, dashboardJSON{ UsersByStatus: o.Counts.UsersByStatus, NotificationsEnabled: o.Counts.NotificationsEnabled, PausedByAPIFailures: o.Counts.PausedByAPIFailures, TodayByStatus: o.Counts.TodayByStatus, FailedLast7Days: o.Counts.FailedLast7Days, ExpiredLast7Days: o.Counts.ExpiredLast7Days, CustomWindows: o.Counts.CustomWindows, OutageSince: o.OutageSince, HolidayFallbackDay: o.HolidayFallback, Today: a.svc.TodayDate(), }) } // --- usuários --- func (a *API) listUsers(w http.ResponseWriter, r *http.Request) { custom, err := queryBool(r, "custom_window") if err != nil { a.writeError(w, err) return } users, err := a.svc.Store().ListUsers(r.Context(), postgres.UserFilter{ Status: queryString(r, "status"), Query: queryString(r, "q"), CustomWindow: custom, }) if err != nil { a.writeError(w, err) return } account := sessionFrom(r).Account out := make([]userSummaryJSON, 0, len(users)) for _, u := range users { if isRestricted(account) && u.User.ID != *account.UserID { continue } out = append(out, toUserSummary(u)) } httpx.WriteJSON(w, http.StatusOK, map[string]any{"users": out}) } func (a *API) getUser(w http.ResponseWriter, r *http.Request) { id, ok := pathID(w, r) if !ok { return } detail, err := a.svc.UserDetail(r.Context(), id) if err != nil { a.writeError(w, err) return } httpx.WriteJSON(w, http.StatusOK, toUserDetail(detail)) } // respondUser devolve o detalhe atualizado após uma ação. func (a *API) respondUser(w http.ResponseWriter, r *http.Request, id int64) { detail, err := a.svc.UserDetail(r.Context(), id) if err != nil { a.writeError(w, err) return } httpx.WriteJSON(w, http.StatusOK, toUserDetail(detail)) } func (a *API) activateUser(w http.ResponseWriter, r *http.Request) { id, ok := pathID(w, r) if !ok { return } if _, err := a.svc.ActivateUser(r.Context(), actorOf(r), id); err != nil { a.writeError(w, err) return } a.respondUser(w, r, id) } func (a *API) disableUser(w http.ResponseWriter, r *http.Request) { id, ok := pathID(w, r) if !ok { return } if err := a.svc.DisableUser(r.Context(), actorOf(r), id); err != nil { a.writeError(w, err) return } a.respondUser(w, r, id) } func (a *API) updateSchedule(w http.ResponseWriter, r *http.Request) { id, ok := pathID(w, r) if !ok { return } var body struct { scheduleJSON Timezone string `json:"timezone"` } if !decode(w, r, &body) { return } in := app.ScheduleInput{ LunchMinutes: body.LunchMinutes, DailyWorkMinutes: body.DailyWorkMinutes, Entrada2LeadMinutes: body.Entrada2LeadMinutes, Saida2LeadMinutes: body.Saida2LeadMinutes, Timezone: body.Timezone, } if body.Window != nil { window, err := body.Window.parse("window.") if err != nil { a.writeError(w, err) return } in.Window = &window } if _, err := a.svc.UpdateSchedule(r.Context(), actorOf(r), id, in); err != nil { a.writeError(w, err) return } a.respondUser(w, r, id) } func (a *API) updateCredentials(w http.ResponseWriter, r *http.Request) { id, ok := pathID(w, r) if !ok { return } var body struct { Email string `json:"email"` Password string `json:"password"` } if !decode(w, r, &body) { return } if err := a.svc.SetCredentials(r.Context(), actorOf(r), id, body.Email, body.Password); err != nil { a.writeError(w, err) return } a.respondUser(w, r, id) } func (a *API) testCredentials(w http.ResponseWriter, r *http.Request) { id, ok := pathID(w, r) if !ok { return } view, err := a.svc.TestCredentials(r.Context(), actorOf(r), id) if err != nil { a.writeError(w, err) return } httpx.WriteJSON(w, http.StatusOK, toToday(view)) } func (a *API) enableNotifications(w http.ResponseWriter, r *http.Request) { id, ok := pathID(w, r) if !ok { return } if _, err := a.svc.EnableNotifications(r.Context(), actorOf(r), id); err != nil { a.writeError(w, err) return } a.respondUser(w, r, id) } func (a *API) disableNotifications(w http.ResponseWriter, r *http.Request) { id, ok := pathID(w, r) if !ok { return } if err := a.svc.DisableNotifications(r.Context(), actorOf(r), id, domain.PauseAdmin); err != nil { a.writeError(w, err) return } a.respondUser(w, r, id) } func (a *API) setBotAdmin(w http.ResponseWriter, r *http.Request) { id, ok := pathID(w, r) if !ok { return } var body struct { Enabled bool `json:"enabled"` } if !decode(w, r, &body) { return } if err := a.svc.SetBotAdmin(r.Context(), actorOf(r), id, body.Enabled); err != nil { a.writeError(w, err) return } a.respondUser(w, r, id) } func (a *API) today(w http.ResponseWriter, r *http.Request) { id, ok := pathID(w, r) if !ok { return } view, err := a.svc.Today(r.Context(), actorOf(r), id, nil) if err != nil { a.writeError(w, err) return } httpx.WriteJSON(w, http.StatusOK, toToday(view)) } func (a *API) resumeDay(w http.ResponseWriter, r *http.Request) { id, ok := pathID(w, r) if !ok { return } if _, err := a.svc.ResumeDay(r.Context(), actorOf(r), id); err != nil { a.writeError(w, err) return } a.respondUser(w, r, id) }