package handlers import ( "context" "net/http" "strconv" "time" "git.haelnorr.com/h/golib/hws" "git.haelnorr.com/h/oslstats/internal/db" "git.haelnorr.com/h/oslstats/internal/throw" "git.haelnorr.com/h/oslstats/internal/validation" adminview "git.haelnorr.com/h/oslstats/internal/view/adminview" "git.haelnorr.com/h/timefmt" "github.com/pkg/errors" "github.com/uptrace/bun" ) // AdminAuditLogsPage renders the full admin dashboard page with audit logs section (GET request) func AdminAuditLogsPage(s *hws.Server, conn *db.DB) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { pageOpts, ok := db.GetPageOpts(s, w, r) if !ok { return } var logs *db.List[db.AuditLog] var users []*db.User var actions []string var resourceTypes []string if ok := conn.WithReadTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) { var err error // Get filters from query filters, ok := getAuditFiltersFromQuery(s, w, r) if !ok { return false, nil } // Get audit logs logs, err = db.GetAuditLogs(ctx, tx, pageOpts, filters) if err != nil { return false, errors.Wrap(err, "db.GetAuditLogs") } // Get all users for filter dropdown usersList, err := db.GetUsers(ctx, tx, nil) if err != nil { return false, errors.Wrap(err, "db.GetUsers") } users = usersList.Items // Get unique actions actions, err = db.GetUniqueActions(ctx, tx) if err != nil { return false, errors.Wrap(err, "db.GetUniqueActions") } // Get unique resource types resourceTypes, err = db.GetUniqueResourceTypes(ctx, tx) if err != nil { return false, errors.Wrap(err, "db.GetUniqueResourceTypes") } return true, nil }); !ok { return } renderSafely(adminview.AuditLogsPage(logs, users, actions, resourceTypes), s, r, w) }) } // AdminAuditLogsList shows the full audit logs list with filters (POST request for HTMX) func AdminAuditLogsList(s *hws.Server, conn *db.DB) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { pageOpts, ok := db.GetPageOpts(s, w, r) if !ok { return } var logs *db.List[db.AuditLog] var users []*db.User var actions []string var resourceTypes []string if ok := conn.WithReadTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) { var err error // Get filters from form filters, ok := getAuditFiltersFromForm(s, w, r) if !ok { return false, nil } // Get audit logs logs, err = db.GetAuditLogs(ctx, tx, pageOpts, filters) if err != nil { return false, errors.Wrap(err, "db.GetAuditLogs") } // Get all users for filter dropdown usersList, err := db.GetUsers(ctx, tx, nil) if err != nil { return false, errors.Wrap(err, "db.GetUsers") } users = usersList.Items // Get unique actions actions, err = db.GetUniqueActions(ctx, tx) if err != nil { return false, errors.Wrap(err, "db.GetUniqueActions") } // Get unique resource types resourceTypes, err = db.GetUniqueResourceTypes(ctx, tx) if err != nil { return false, errors.Wrap(err, "db.GetUniqueResourceTypes") } return true, nil }); !ok { return } renderSafely(adminview.AuditLogsList(logs, users, actions, resourceTypes), s, r, w) }) } // AdminAuditLogsFilter returns only the results container (table + pagination) for HTMX updates func AdminAuditLogsFilter(s *hws.Server, conn *db.DB) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { pageOpts, ok := db.GetPageOpts(s, w, r) if !ok { return } var logs *db.List[db.AuditLog] if ok := conn.WithReadTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) { var err error // Get filters from form filters, ok := getAuditFiltersFromForm(s, w, r) if !ok { return false, nil } // Get audit logs logs, err = db.GetAuditLogs(ctx, tx, pageOpts, filters) if err != nil { return false, errors.Wrap(err, "db.GetAuditLogs") } return true, nil }); !ok { return } // Return only the results container, not the full page with filters renderSafely(adminview.AuditLogsResults(logs), s, r, w) }) } // AdminAuditLogDetail shows details for a single audit log entry func AdminAuditLogDetail(s *hws.Server, conn *db.DB) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Get ID from path idStr := r.PathValue("id") if idStr == "" { throw.BadRequest(s, w, r, "Missing audit log ID", nil) return } id, err := strconv.Atoi(idStr) if err != nil { throw.BadRequest(s, w, r, "Invalid audit log ID", err) return } var log *db.AuditLog if ok := conn.WithReadTx(s, w, r, func(ctx context.Context, tx bun.Tx) (bool, error) { var err error log, err = db.GetAuditLogByID(ctx, tx, id) if err != nil { if db.IsBadRequest(err) { throw.NotFound(s, w, r, r.URL.Path) return false, nil } return false, errors.Wrap(err, "db.GetAuditLogByID") } return true, nil }); !ok { return } renderSafely(adminview.AuditLogDetail(log), s, r, w) }) } // getAuditFiltersFromQuery extracts audit log filters from query string func getAuditFiltersFromQuery(s *hws.Server, w http.ResponseWriter, r *http.Request) (*db.AuditLogFilter, bool) { g := validation.NewQueryGetter(r) filters, ok := buildAuditFilters(g, s, w, r) return filters, ok } // getAuditFiltersFromForm extracts audit log filters from form data func getAuditFiltersFromForm(s *hws.Server, w http.ResponseWriter, r *http.Request) (*db.AuditLogFilter, bool) { g, ok := validation.ParseFormOrError(s, w, r) if !ok { return nil, false } return buildAuditFilters(g, s, w, r) } // buildAuditFilters builds audit log filters from a validation.Getter func buildAuditFilters(g validation.Getter, s *hws.Server, w http.ResponseWriter, r *http.Request) (*db.AuditLogFilter, bool) { filters := db.NewAuditLogFilter() userIDs := g.IntList("user_id").Values() actions := g.StringList("action").Values() resourceTypes := g.StringList("resource_type").Values() results := g.StringList("result").Values() format := timefmt.NewBuilder().DayNumeric2().Slash(). MonthNumeric2().Slash().Year4().Build() startDate := g.Time("start_date", format).Optional().Value endDate := g.Time("end_date", format).Optional().Value if !g.ValidateAndError(s, w, r) { return nil, false } if len(userIDs) > 0 { filters.UserIDs(userIDs) } if len(actions) > 0 { filters.Actions(actions) } if len(resourceTypes) > 0 { filters.ResourceTypes(resourceTypes) } if len(results) > 0 { filters.Results(results) } if !startDate.IsZero() { filters.DateRange(startDate.Unix(), 0) } if !endDate.IsZero() { endOfDay := endDate.Add(23*time.Hour + 59*time.Minute + 59*time.Second) filters.DateRange(0, endOfDay.Unix()) } return filters, true }