Yimaru-BackEnd/internal/web_server/handlers/activity_logs.go

209 lines
5.9 KiB
Go

package handlers
import (
"Yimaru-Backend/internal/domain"
"encoding/json"
"strconv"
"time"
"github.com/gofiber/fiber/v2"
)
type activityLogRes struct {
ID int64 `json:"id"`
ActorID *int64 `json:"actor_id,omitempty"`
ActorRole *string `json:"actor_role,omitempty"`
Action string `json:"action"`
ResourceType string `json:"resource_type"`
ResourceID *int64 `json:"resource_id,omitempty"`
Message *string `json:"message,omitempty"`
Metadata json.RawMessage `json:"metadata"`
IPAddress *string `json:"ip_address,omitempty"`
UserAgent *string `json:"user_agent,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
type activityLogListRes struct {
Logs []activityLogRes `json:"logs"`
TotalCount int64 `json:"total_count"`
Limit int32 `json:"limit"`
Offset int32 `json:"offset"`
}
// GetActivityLogs godoc
// @Summary Get activity logs
// @Description Returns a filtered, paginated list of activity logs
// @Tags activity-logs
// @Produce json
// @Param actor_id query int false "Filter by actor ID"
// @Param action query string false "Filter by action"
// @Param resource_type query string false "Filter by resource type"
// @Param resource_id query int false "Filter by resource ID"
// @Param after query string false "Filter logs after this RFC3339 timestamp"
// @Param before query string false "Filter logs before this RFC3339 timestamp"
// @Param limit query int false "Limit" default(20)
// @Param offset query int false "Offset" default(0)
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 500 {object} domain.ErrorResponse
// @Router /api/v1/activity-logs [get]
func (h *Handler) GetActivityLogs(c *fiber.Ctx) error {
var filter domain.ActivityLogFilter
if v := c.Query("actor_id"); v != "" {
id, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid actor_id parameter",
Error: err.Error(),
})
}
filter.ActorID = &id
}
if v := c.Query("action"); v != "" {
filter.Action = &v
}
if v := c.Query("resource_type"); v != "" {
filter.ResourceType = &v
}
if v := c.Query("resource_id"); v != "" {
id, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid resource_id parameter",
Error: err.Error(),
})
}
filter.ResourceID = &id
}
if v := c.Query("after"); v != "" {
t, err := time.Parse(time.RFC3339, v)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid after parameter, expected RFC3339 format",
Error: err.Error(),
})
}
filter.After = &t
}
if v := c.Query("before"); v != "" {
t, err := time.Parse(time.RFC3339, v)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid before parameter, expected RFC3339 format",
Error: err.Error(),
})
}
filter.Before = &t
}
limitStr := c.Query("limit", "20")
limit, err := strconv.Atoi(limitStr)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid limit parameter",
Error: err.Error(),
})
}
if limit > 100 {
limit = 100
}
filter.Limit = int32(limit)
offsetStr := c.Query("offset", "0")
offset, err := strconv.Atoi(offsetStr)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid offset parameter",
Error: err.Error(),
})
}
filter.Offset = int32(offset)
logs, totalCount, err := h.activityLogSvc.List(c.Context(), filter)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(domain.ErrorResponse{
Message: "Failed to retrieve activity logs",
Error: err.Error(),
})
}
var logResponses []activityLogRes
for _, l := range logs {
logResponses = append(logResponses, activityLogRes{
ID: l.ID,
ActorID: l.ActorID,
ActorRole: l.ActorRole,
Action: l.Action,
ResourceType: l.ResourceType,
ResourceID: l.ResourceID,
Message: l.Message,
Metadata: l.Metadata,
IPAddress: l.IPAddress,
UserAgent: l.UserAgent,
CreatedAt: l.CreatedAt,
})
}
return c.JSON(domain.Response{
Message: "Activity logs retrieved successfully",
Data: activityLogListRes{
Logs: logResponses,
TotalCount: totalCount,
Limit: int32(limit),
Offset: int32(offset),
},
})
}
// GetActivityLogByID godoc
// @Summary Get activity log by ID
// @Description Returns a single activity log entry by its ID
// @Tags activity-logs
// @Produce json
// @Param id path int true "Activity Log ID"
// @Success 200 {object} domain.Response
// @Failure 400 {object} domain.ErrorResponse
// @Failure 404 {object} domain.ErrorResponse
// @Router /api/v1/activity-logs/{id} [get]
func (h *Handler) GetActivityLogByID(c *fiber.Ctx) error {
idStr := c.Params("id")
id, err := strconv.ParseInt(idStr, 10, 64)
if err != nil {
return c.Status(fiber.StatusBadRequest).JSON(domain.ErrorResponse{
Message: "Invalid activity log ID",
Error: err.Error(),
})
}
l, err := h.activityLogSvc.GetByID(c.Context(), id)
if err != nil {
return c.Status(fiber.StatusNotFound).JSON(domain.ErrorResponse{
Message: "Activity log not found",
Error: err.Error(),
})
}
return c.JSON(domain.Response{
Message: "Activity log retrieved successfully",
Data: activityLogRes{
ID: l.ID,
ActorID: l.ActorID,
ActorRole: l.ActorRole,
Action: l.Action,
ResourceType: l.ResourceType,
ResourceID: l.ResourceID,
Message: l.Message,
Metadata: l.Metadata,
IPAddress: l.IPAddress,
UserAgent: l.UserAgent,
CreatedAt: l.CreatedAt,
},
})
}