mirror of
https://github.com/get-drexa/drive.git
synced 2026-02-02 15:01:17 +00:00
feat(backend): impl share expiry update
This commit is contained in:
28
apps/backend/internal/nullable/time.go
Normal file
28
apps/backend/internal/nullable/time.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package nullable
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Time tracks whether a JSON field was set and stores an optional time value.
|
||||
type Time struct {
|
||||
Time *time.Time
|
||||
Set bool
|
||||
}
|
||||
|
||||
func (nt *Time) UnmarshalJSON(b []byte) error {
|
||||
nt.Set = true
|
||||
if bytes.Equal(bytes.TrimSpace(b), []byte("null")) {
|
||||
nt.Time = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
var t time.Time
|
||||
if err := json.Unmarshal(b, &t); err != nil {
|
||||
return err
|
||||
}
|
||||
nt.Time = &t
|
||||
return nil
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/get-drexa/drexa/internal/account"
|
||||
"github.com/get-drexa/drexa/internal/httperr"
|
||||
"github.com/get-drexa/drexa/internal/nullable"
|
||||
"github.com/get-drexa/drexa/internal/reqctx"
|
||||
"github.com/get-drexa/drexa/internal/user"
|
||||
"github.com/get-drexa/drexa/internal/virtualfs"
|
||||
@@ -31,6 +32,13 @@ type createShareRequest struct {
|
||||
ExpiresAt *time.Time `json:"expiresAt" example:"2025-01-15T00:00:00Z"`
|
||||
}
|
||||
|
||||
// patchShareRequest represents a request to update a share link
|
||||
// @Description Request to update a share link. Omit expiresAt to keep the current value. Use null to remove the expiry.
|
||||
type patchShareRequest struct {
|
||||
// Optional expiration time for the share (ISO 8601), null clears it.
|
||||
ExpiresAt nullable.Time `json:"expiresAt" example:"2025-01-15T00:00:00Z"`
|
||||
}
|
||||
|
||||
func NewHTTPHandler(sharingService *Service, accountService *account.Service, vfs *virtualfs.VirtualFS, db *bun.DB, authMiddleware fiber.Handler) *HTTPHandler {
|
||||
return &HTTPHandler{
|
||||
sharingService: sharingService,
|
||||
@@ -48,8 +56,9 @@ func (h *HTTPHandler) RegisterShareConsumeRoutes(r fiber.Router) *virtualfs.Scop
|
||||
|
||||
func (h *HTTPHandler) RegisterShareManagementRoutes(api *account.ScopedRouter) {
|
||||
g := api.Group("/shares")
|
||||
g.Get("/:shareID", h.getShare)
|
||||
g.Post("/", h.createShare)
|
||||
g.Get("/:shareID", h.getShare)
|
||||
g.Patch("/:shareID", h.updateShare)
|
||||
g.Delete("/:shareID", h.deleteShare)
|
||||
}
|
||||
|
||||
@@ -216,6 +225,52 @@ func (h *HTTPHandler) createShare(c *fiber.Ctx) error {
|
||||
return c.JSON(share)
|
||||
}
|
||||
|
||||
func (h *HTTPHandler) updateShare(c *fiber.Ctx) error {
|
||||
shareID := c.Params("shareID")
|
||||
|
||||
share, err := h.sharingService.FindShareByPublicID(c.Context(), h.db, shareID)
|
||||
if err != nil {
|
||||
if errors.Is(err, ErrShareNotFound) {
|
||||
return c.SendStatus(fiber.StatusNotFound)
|
||||
}
|
||||
return httperr.Internal(err)
|
||||
}
|
||||
|
||||
var req patchShareRequest
|
||||
if err := c.BodyParser(&req); err != nil {
|
||||
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{
|
||||
"error": "invalid request",
|
||||
})
|
||||
}
|
||||
|
||||
tx, err := h.db.BeginTx(c.Context(), nil)
|
||||
if err != nil {
|
||||
return httperr.Internal(err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
opts := UpdateShareOptions{}
|
||||
if req.ExpiresAt.Set {
|
||||
if req.ExpiresAt.Time == nil {
|
||||
opts.ExpiresAt = &time.Time{}
|
||||
} else {
|
||||
opts.ExpiresAt = req.ExpiresAt.Time
|
||||
}
|
||||
}
|
||||
|
||||
err = h.sharingService.UpdateShare(c.Context(), tx, share, opts)
|
||||
if err != nil {
|
||||
return httperr.Internal(err)
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return httperr.Internal(err)
|
||||
}
|
||||
|
||||
return c.JSON(share)
|
||||
}
|
||||
|
||||
// deleteShare deletes a share link
|
||||
// @Summary Delete share
|
||||
// @Description Delete a share link, revoking access for all users
|
||||
|
||||
@@ -34,6 +34,11 @@ type ListSharesOptions struct {
|
||||
IncludesExpired bool
|
||||
}
|
||||
|
||||
type UpdateShareOptions struct {
|
||||
// ExpiresAt sets the expiration time for the share. If nil, the expiration time is not changed. If it is a zero time, the expiration time is cleared.
|
||||
ExpiresAt *time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
ErrShareNotFound = errors.New("share not found")
|
||||
ErrShareExpired = errors.New("share expired")
|
||||
@@ -317,3 +322,37 @@ func (s *Service) generatePublicID() (string, error) {
|
||||
n := binary.BigEndian.Uint64(b[:])
|
||||
return s.sqid.Encode([]uint64{n})
|
||||
}
|
||||
|
||||
func (s *Service) UpdateShare(ctx context.Context, db bun.IDB, share *Share, opts UpdateShareOptions) error {
|
||||
now := time.Now()
|
||||
|
||||
cols := make([]string, 0, 2)
|
||||
|
||||
if opts.ExpiresAt != nil {
|
||||
newExpiresAt := opts.ExpiresAt
|
||||
if opts.ExpiresAt.IsZero() {
|
||||
newExpiresAt = nil
|
||||
}
|
||||
if !timePtrEqual(share.ExpiresAt, newExpiresAt) {
|
||||
share.ExpiresAt = newExpiresAt
|
||||
share.UpdatedAt = now
|
||||
cols = append(cols, "expires_at", "updated_at")
|
||||
}
|
||||
}
|
||||
|
||||
if len(cols) > 0 {
|
||||
_, err := db.NewUpdate().Model(share).Column(cols...).WherePK().Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func timePtrEqual(a, b *time.Time) bool {
|
||||
if a == nil || b == nil {
|
||||
return a == b
|
||||
}
|
||||
return a.Equal(*b)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user