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, }, }) }