Projeto bot pontos
This commit is contained in:
289
backend/internal/httpapi/handlers_users.go
Normal file
289
backend/internal/httpapi/handlers_users.go
Normal file
@@ -0,0 +1,289 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user