fix: render public file previews
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
|
"html"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -343,7 +344,7 @@ func (h *NodeHandler) CreatePublicLink(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.repo.Audit(r.Context(), userID, "files.public_link_create", "files_node", id, "{}")
|
h.repo.Audit(r.Context(), userID, "files.public_link_create", "files_node", id, "{}")
|
||||||
writeJSON(w, http.StatusCreated, model.PublicLinkResponse{
|
writeJSON(w, http.StatusCreated, model.PublicLinkResponse{
|
||||||
ID: linkID,
|
ID: linkID,
|
||||||
URL: strings.TrimRight(h.cfg.PublicBaseURL, "/") + "/api/files/public/" + token,
|
URL: h.publicURL(token),
|
||||||
ExpiresAt: req.ExpiresAt,
|
ExpiresAt: req.ExpiresAt,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -353,6 +354,10 @@ func (h *NodeHandler) PublicMeta(w http.ResponseWriter, r *http.Request) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if node.NodeType == model.NodeTypeFile {
|
||||||
|
h.renderPublicPreview(w, r, node)
|
||||||
|
return
|
||||||
|
}
|
||||||
response := model.PublicNodeResponse{Node: node}
|
response := model.PublicNodeResponse{Node: node}
|
||||||
if node.NodeType == model.NodeTypeFolder {
|
if node.NodeType == model.NodeTypeFolder {
|
||||||
children, err := h.repo.ListChildrenForPublic(r.Context(), node.ID)
|
children, err := h.repo.ListChildrenForPublic(r.Context(), node.ID)
|
||||||
@@ -423,6 +428,65 @@ func (h *NodeHandler) publicNode(w http.ResponseWriter, r *http.Request) (*model
|
|||||||
return node, true
|
return node, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *NodeHandler) renderPublicPreview(w http.ResponseWriter, r *http.Request, node *model.Node) {
|
||||||
|
title := node.Title
|
||||||
|
if node.OriginalFilename != nil && *node.OriginalFilename != "" {
|
||||||
|
title = *node.OriginalFilename
|
||||||
|
}
|
||||||
|
mimeType := ""
|
||||||
|
if node.MimeType != nil {
|
||||||
|
mimeType = strings.ToLower(*node.MimeType)
|
||||||
|
}
|
||||||
|
downloadURL := h.publicURL(chi.URLParam(r, "token")) + "/download"
|
||||||
|
preview := `<div class="empty">Предпросмотр для этого типа файла недоступен.</div>`
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(mimeType, "image/"):
|
||||||
|
preview = `<img class="preview-media" src="` + html.EscapeString(downloadURL) + `" alt="">`
|
||||||
|
case mimeType == "application/pdf":
|
||||||
|
preview = `<iframe class="preview-frame" src="` + html.EscapeString(downloadURL) + `"></iframe>`
|
||||||
|
case strings.HasPrefix(mimeType, "video/"):
|
||||||
|
preview = `<video class="preview-media" src="` + html.EscapeString(downloadURL) + `" controls></video>`
|
||||||
|
case strings.HasPrefix(mimeType, "audio/"):
|
||||||
|
preview = `<audio class="preview-audio" src="` + html.EscapeString(downloadURL) + `" controls></audio>`
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
_, _ = io.WriteString(w, `<!doctype html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>`+html.EscapeString(title)+`</title>
|
||||||
|
<style>
|
||||||
|
:root { color-scheme: dark; font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #0d111b; color: #edf2ff; }
|
||||||
|
body { margin: 0; min-height: 100vh; background: #0d111b; }
|
||||||
|
.shell { min-height: 100vh; display: grid; grid-template-rows: auto 1fr; }
|
||||||
|
header { padding: 18px 24px; border-bottom: 1px solid #263044; background: #151a2a; }
|
||||||
|
h1 { margin: 0; font-size: 20px; line-height: 1.35; font-weight: 700; }
|
||||||
|
.meta { margin-top: 6px; color: #9aa7bd; font-size: 14px; }
|
||||||
|
main { padding: 24px; display: grid; place-items: center; overflow: auto; }
|
||||||
|
.preview-media { max-width: 100%; max-height: calc(100vh - 132px); border-radius: 10px; object-fit: contain; background: #080b12; }
|
||||||
|
.preview-frame { width: min(1200px, 100%); height: calc(100vh - 132px); border: 1px solid #263044; border-radius: 10px; background: #080b12; }
|
||||||
|
.preview-audio { width: min(720px, 100%); }
|
||||||
|
.empty { width: min(640px, 100%); padding: 28px; border: 1px solid #263044; border-radius: 10px; background: #151a2a; color: #c6d0e1; text-align: center; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="shell">
|
||||||
|
<header>
|
||||||
|
<h1>`+html.EscapeString(title)+`</h1>
|
||||||
|
<div class="meta">Публичный просмотр файла</div>
|
||||||
|
</header>
|
||||||
|
<main>`+preview+`</main>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>`)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *NodeHandler) publicURL(token string) string {
|
||||||
|
return strings.TrimRight(h.cfg.PublicBaseURL, "/") + "/api/files/public/" + token
|
||||||
|
}
|
||||||
|
|
||||||
func (h *NodeHandler) streamNode(w http.ResponseWriter, r *http.Request, node *model.Node) {
|
func (h *NodeHandler) streamNode(w http.ResponseWriter, r *http.Request, node *model.Node) {
|
||||||
if node.NodeType == model.NodeTypeFolder || node.StorageKey == nil {
|
if node.NodeType == model.NodeTypeFolder || node.StorageKey == nil {
|
||||||
writeError(w, http.StatusBadRequest, "node is not downloadable")
|
writeError(w, http.StatusBadRequest, "node is not downloadable")
|
||||||
|
|||||||
Reference in New Issue
Block a user