290 lines
7.3 KiB
Go
290 lines
7.3 KiB
Go
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)
|
|
}
|