Backend: - Password reset flow (forgot/reset endpoints, tokens table) - Profile management (PUT /users/me, skills, experience, education) - Tickets system (CRUD, messages, stats) - Activity logs (list, stats) - Document validator (CNPJ, CPF, EIN support) - Input sanitizer (XSS prevention) - Full-text search em vagas (plainto_tsquery) - Filtros avançados (location, salary, workMode) - Ordenação (date, salary, relevance) Frontend: - Forgot/Reset password pages - Candidate profile edit page - Sanitize utilities (sanitize.ts) Backoffice: - TicketsModule proxy - ActivityLogsModule proxy - Dockerfile otimizado (multi-stage, non-root, healthcheck) Migrations: - 013: Profile fields to users - 014: Password reset tokens - 015: Tickets table - 016: Activity logs table
102 lines
3 KiB
Go
102 lines
3 KiB
Go
package handlers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/rede5/gohorsejobs/backend/internal/models"
|
|
"github.com/rede5/gohorsejobs/backend/internal/services"
|
|
)
|
|
|
|
type ActivityLogHandler struct {
|
|
service *services.ActivityLogService
|
|
}
|
|
|
|
func NewActivityLogHandler(service *services.ActivityLogService) *ActivityLogHandler {
|
|
return &ActivityLogHandler{service: service}
|
|
}
|
|
|
|
// GetActivityLogs lists activity logs
|
|
// @Summary List Activity Logs
|
|
// @Description Get activity logs with optional filters
|
|
// @Tags Activity Logs
|
|
// @Produce json
|
|
// @Param user_id query int false "Filter by user ID"
|
|
// @Param action query string false "Filter by action"
|
|
// @Param resource_type query string false "Filter by resource type"
|
|
// @Param start_date query string false "Start date (RFC3339)"
|
|
// @Param end_date query string false "End date (RFC3339)"
|
|
// @Param limit query int false "Limit results"
|
|
// @Param offset query int false "Offset for pagination"
|
|
// @Success 200 {array} models.ActivityLog
|
|
// @Router /api/v1/activity-logs [get]
|
|
func (h *ActivityLogHandler) GetActivityLogs(w http.ResponseWriter, r *http.Request) {
|
|
filter := models.ActivityLogFilter{}
|
|
|
|
if userID := r.URL.Query().Get("user_id"); userID != "" {
|
|
if id, err := strconv.Atoi(userID); err == nil {
|
|
filter.UserID = &id
|
|
}
|
|
}
|
|
|
|
if action := r.URL.Query().Get("action"); action != "" {
|
|
filter.Action = &action
|
|
}
|
|
|
|
if resourceType := r.URL.Query().Get("resource_type"); resourceType != "" {
|
|
filter.ResourceType = &resourceType
|
|
}
|
|
|
|
if startDate := r.URL.Query().Get("start_date"); startDate != "" {
|
|
if t, err := time.Parse(time.RFC3339, startDate); err == nil {
|
|
filter.StartDate = &t
|
|
}
|
|
}
|
|
|
|
if endDate := r.URL.Query().Get("end_date"); endDate != "" {
|
|
if t, err := time.Parse(time.RFC3339, endDate); err == nil {
|
|
filter.EndDate = &t
|
|
}
|
|
}
|
|
|
|
if limit := r.URL.Query().Get("limit"); limit != "" {
|
|
if l, err := strconv.Atoi(limit); err == nil {
|
|
filter.Limit = l
|
|
}
|
|
}
|
|
|
|
if offset := r.URL.Query().Get("offset"); offset != "" {
|
|
if o, err := strconv.Atoi(offset); err == nil {
|
|
filter.Offset = o
|
|
}
|
|
}
|
|
|
|
logs, err := h.service.List(filter)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(logs)
|
|
}
|
|
|
|
// GetActivityLogStats gets statistics
|
|
// @Summary Get Activity Stats
|
|
// @Description Get activity log statistics for dashboard
|
|
// @Tags Activity Logs
|
|
// @Produce json
|
|
// @Success 200 {object} models.ActivityLogStats
|
|
// @Router /api/v1/activity-logs/stats [get]
|
|
func (h *ActivityLogHandler) GetActivityLogStats(w http.ResponseWriter, r *http.Request) {
|
|
stats, err := h.service.GetStats()
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(stats)
|
|
}
|