feat: list active public file links
This commit is contained in:
@@ -315,6 +315,22 @@ func (h *NodeHandler) ReplaceAccess(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func (h *NodeHandler) ListPublicLinks(w http.ResponseWriter, r *http.Request) {
|
||||
userID := commonmw.GetUserID(r.Context())
|
||||
id := chi.URLParam(r, "id")
|
||||
links, err := h.repo.ListPublicLinks(r.Context(), id, userID)
|
||||
if err != nil {
|
||||
writeInternalError(w, r, err, "failed to list public links")
|
||||
return
|
||||
}
|
||||
for i := range links {
|
||||
if links[i].URL != "" {
|
||||
links[i].URL = h.publicURL(links[i].URL)
|
||||
}
|
||||
}
|
||||
writeJSON(w, http.StatusOK, links)
|
||||
}
|
||||
|
||||
func (h *NodeHandler) CreatePublicLink(w http.ResponseWriter, r *http.Request) {
|
||||
var req model.PublicLinkRequest
|
||||
if err := decodeJSON(r, &req); err != nil {
|
||||
@@ -332,7 +348,7 @@ func (h *NodeHandler) CreatePublicLink(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
userID := commonmw.GetUserID(r.Context())
|
||||
id := chi.URLParam(r, "id")
|
||||
linkID, err := h.repo.CreatePublicLink(r.Context(), id, userID, token, req.ExpiresAt)
|
||||
link, err := h.repo.CreatePublicLink(r.Context(), id, userID, token, req.ExpiresAt)
|
||||
if errors.Is(err, repository.ErrNotFound) {
|
||||
writeError(w, http.StatusNotFound, "file not found")
|
||||
return
|
||||
@@ -342,11 +358,8 @@ func (h *NodeHandler) CreatePublicLink(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
h.repo.Audit(r.Context(), userID, "files.public_link_create", "files_node", id, "{}")
|
||||
writeJSON(w, http.StatusCreated, model.PublicLinkResponse{
|
||||
ID: linkID,
|
||||
URL: h.publicURL(token),
|
||||
ExpiresAt: req.ExpiresAt,
|
||||
})
|
||||
link.URL = h.publicURL(token)
|
||||
writeJSON(w, http.StatusCreated, link)
|
||||
}
|
||||
|
||||
func (h *NodeHandler) PublicMeta(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -86,6 +86,7 @@ type PublicLinkRequest struct {
|
||||
|
||||
type PublicLinkResponse struct {
|
||||
ID string `json:"id"`
|
||||
URL string `json:"url"`
|
||||
URL string `json:"url,omitempty"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
}
|
||||
|
||||
@@ -465,19 +465,49 @@ func normalizeAccess(access []model.Access) []model.Access {
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *NodeRepository) CreatePublicLink(ctx context.Context, nodeID, actorID, token string, expiresAt time.Time) (string, error) {
|
||||
func (r *NodeRepository) CreatePublicLink(ctx context.Context, nodeID, actorID, token string, expiresAt time.Time) (*model.PublicLinkResponse, error) {
|
||||
hash := TokenHash(token)
|
||||
var id string
|
||||
var link model.PublicLinkResponse
|
||||
err := r.pool.QueryRow(ctx, `
|
||||
INSERT INTO files_public_links (node_id, token_hash, expires_at, created_by)
|
||||
SELECT $1, $2, $3, $4
|
||||
WHERE effective_node_access($1, $4, '{}'::text[]) = 'edit'
|
||||
RETURNING id
|
||||
`, nodeID, hash, expiresAt, actorID).Scan(&id)
|
||||
INSERT INTO files_public_links (node_id, token_hash, public_token, expires_at, created_by)
|
||||
SELECT $1, $2, $3, $4, $5
|
||||
WHERE effective_node_access($1, $5, '{}'::text[]) = 'edit'
|
||||
RETURNING id, expires_at, created_at
|
||||
`, nodeID, hash, token, expiresAt, actorID).Scan(&link.ID, &link.ExpiresAt, &link.CreatedAt)
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return "", ErrNotFound
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
return id, err
|
||||
return &link, err
|
||||
}
|
||||
|
||||
func (r *NodeRepository) ListPublicLinks(ctx context.Context, nodeID, actorID string) ([]model.PublicLinkResponse, error) {
|
||||
rows, err := r.pool.Query(ctx, `
|
||||
SELECT id, COALESCE(public_token, ''), expires_at, created_at
|
||||
FROM files_public_links
|
||||
WHERE node_id = $1
|
||||
AND revoked_at IS NULL
|
||||
AND expires_at > now()
|
||||
AND effective_node_access($1, $2, '{}'::text[]) = 'edit'
|
||||
ORDER BY expires_at DESC, created_at DESC
|
||||
`, nodeID, actorID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
out := make([]model.PublicLinkResponse, 0)
|
||||
for rows.Next() {
|
||||
var link model.PublicLinkResponse
|
||||
var token string
|
||||
if err := rows.Scan(&link.ID, &token, &link.ExpiresAt, &link.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if token != "" {
|
||||
link.URL = token
|
||||
}
|
||||
out = append(out, link)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func (r *NodeRepository) GetByPublicToken(ctx context.Context, token string) (*model.Node, error) {
|
||||
|
||||
Reference in New Issue
Block a user