Files

158 lines
3.7 KiB
Go

package grouph
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/warmbly/warmbly/internal/api/middleware"
"github.com/warmbly/warmbly/internal/errx"
"github.com/warmbly/warmbly/internal/models"
)
func GetGroupID(c *gin.Context) string {
return c.Param("gid")
}
// scope resolves the workspace every group call is keyed on, plus the group id
// on the path for the routes that carry one. Labels belong to the organization,
// not to whoever created them, so a request without one is refused rather than
// run against a single member's rows.
func scope(c *gin.Context, withID bool) (uuid.UUID, uuid.UUID, bool) {
orgID := middleware.GetOrganizationID(c)
if orgID == nil {
errx.Handle(c, errx.ErrNoOrganization)
return uuid.Nil, uuid.Nil, false
}
if !withID {
return *orgID, uuid.Nil, true
}
gid, err := uuid.Parse(GetGroupID(c))
if err != nil {
errx.Handle(c, errx.ErrUuid)
return uuid.Nil, uuid.Nil, false
}
return *orgID, gid, true
}
// entityType maps the group's name ("folders"/"tags"/"categories") to the
// matching audit entity type.
func (h *Handler) entityType() models.AuditEntityType {
switch h.name {
case "tags":
return models.AuditEntityTag
case "categories":
return models.AuditEntityCategory
default:
return models.AuditEntityFolder
}
}
// logAudit records a group mutation in the organization-wide audit trail. It
// pulls actor/org/IP/user-agent from the gin context; no-op without org context.
func (h *Handler) logAudit(c *gin.Context, action models.AuditAction, entityID *uuid.UUID, metadata map[string]string) {
if h.audit == nil {
return
}
orgID := middleware.GetOrganizationID(c)
if orgID == nil {
return
}
actorID, err := middleware.GetUserUUID(c)
if err != nil {
return
}
h.audit.LogAction(c.Request.Context(), *orgID, actorID, action, h.entityType(), entityID, c.ClientIP(), c.Request.UserAgent(), nil, metadata)
}
func (h *Handler) Create(c *gin.Context) {
orgID, _, ok := scope(c, false)
if !ok {
return
}
// Attribution only; an API key has no human behind it, so a failed parse
// records a nil creator rather than refusing the write.
uid, _ := middleware.GetUserUUID(c)
var data models.GroupCreate
if err := c.ShouldBindJSON(&data); err != nil {
errx.Handle(c, errx.ErrInvalid)
return
}
group, xerr := h.service.Create(c.Request.Context(), orgID, uid, &data)
if xerr != nil {
errx.Handle(c, xerr)
return
}
h.logAudit(c, models.AuditActionCreate, &group.ID, map[string]string{"title": group.Title})
c.JSON(http.StatusOK, group)
}
func (h *Handler) Update(c *gin.Context) {
orgID, gid, ok := scope(c, true)
if !ok {
return
}
var data models.GroupUpdate
if err := c.ShouldBindJSON(&data); err != nil {
errx.Handle(c, errx.ErrInvalid)
return
}
group, xerr := h.service.Update(c.Request.Context(), orgID, gid, &data)
if xerr != nil {
errx.Handle(c, xerr)
return
}
h.logAudit(c, models.AuditActionUpdate, &gid, map[string]string{"title": group.Title})
c.JSON(http.StatusOK, group)
}
func (h *Handler) Move(c *gin.Context) {
orgID, gid, ok := scope(c, true)
if !ok {
return
}
var data models.Move
if err := c.ShouldBindJSON(&data); err != nil {
errx.Handle(c, errx.ErrInvalid)
return
}
orders, xerr := h.service.Move(c.Request.Context(), orgID, gid, data.Position)
if xerr != nil {
errx.Handle(c, xerr)
return
}
h.logAudit(c, models.AuditActionUpdate, &gid, map[string]string{"moved": "true"})
c.JSON(http.StatusOK, orders)
}
func (h *Handler) Delete(c *gin.Context) {
orgID, gid, ok := scope(c, true)
if !ok {
return
}
if xerr := h.service.Delete(c.Request.Context(), orgID, gid); xerr != nil {
errx.Handle(c, xerr)
return
}
h.logAudit(c, models.AuditActionDelete, &gid, nil)
c.Status(http.StatusNoContent)
}