210 lines
6.4 KiB
Go
210 lines
6.4 KiB
Go
package richtext
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type PublicSessionResult struct {
|
|
SessionResult
|
|
DocumentURL string `json:"documentUrl,omitempty"`
|
|
SaveURL string `json:"saveUrl,omitempty"`
|
|
}
|
|
|
|
func (s *Service) CreatePublicSession(ctx context.Context, token, filePath, mode, password, guestID, guestName, displayName string) (*PublicSessionResult, error) {
|
|
if !s.Cfg.Enabled {
|
|
return nil, fmt.Errorf("rich text editor disabled")
|
|
}
|
|
filePath = normalizePath(filePath)
|
|
canonical, source, importRequired, err := s.resolvePublicCanonicalPath(ctx, token, filePath, password, displayName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ownerID, ownerPath, err := s.ownerSidecarPathForPublic(ctx, token, password, canonical, displayName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
roomID, err := s.resolveCollabRoomID(ctx, ownerID, ownerPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
tokenJWT, err := signRoomToken(roomTokenPayload{
|
|
Room: roomID,
|
|
Path: canonical,
|
|
User: "public:" + token,
|
|
Sub: guestID,
|
|
Name: guestName,
|
|
Mode: mode,
|
|
Expires: time.Now().Add(8 * time.Hour).Unix(),
|
|
}, s.Cfg.HocuspocusSecret)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
apiBase := strings.TrimRight(s.Cfg.APIInternalURL, "/")
|
|
sig, _ := signPublicDocAccess(token, canonical, password, s.Cfg.HocuspocusSecret)
|
|
docURL := fmt.Sprintf("%s/api/v1/drive/public/shares/%s/richtext/document?path=%s&password=%s&sig=%s",
|
|
apiBase, url.PathEscape(token), url.QueryEscape(canonical), url.QueryEscape(password), url.QueryEscape(sig))
|
|
saveURL := docURL
|
|
|
|
wsURL := strings.TrimSpace(s.Cfg.HocuspocusPublicURL)
|
|
collab := wsURL != "" && s.Cfg.HocuspocusSecret != ""
|
|
|
|
return &PublicSessionResult{
|
|
SessionResult: SessionResult{
|
|
RoomID: roomID,
|
|
CanonicalPath: canonical,
|
|
SourcePath: source,
|
|
WsURL: wsURL,
|
|
Token: tokenJWT,
|
|
Mode: mode,
|
|
ImportRequired: importRequired,
|
|
Collaboration: collab,
|
|
},
|
|
DocumentURL: docURL,
|
|
SaveURL: saveURL,
|
|
}, nil
|
|
}
|
|
|
|
func (s *Service) resolvePublicCanonicalPath(ctx context.Context, token, filePath, password, displayName string) (canonical, source string, importRequired bool, err error) {
|
|
filePath = normalizePath(filePath)
|
|
source = filePath
|
|
if source == "/" {
|
|
source = s.publicClientSourcePath(ctx, token, password, filePath, displayName)
|
|
}
|
|
|
|
if isUltidocPath(filePath) {
|
|
return filePath, "", false, nil
|
|
}
|
|
if isUltidocPath(source) {
|
|
return source, "", false, nil
|
|
}
|
|
|
|
sidecar := sidecarPathForSource(source)
|
|
|
|
if s.publicSidecarExists(ctx, token, password, sidecar, displayName) {
|
|
return sidecar, source, false, nil
|
|
}
|
|
|
|
if _, err := s.publicFileExists(ctx, token, source, password); err != nil {
|
|
return "", "", false, fmt.Errorf("file not found")
|
|
}
|
|
|
|
return sidecar, source, true, nil
|
|
}
|
|
|
|
func (s *Service) publicClientSourcePath(ctx context.Context, token, password, clientPath, displayName string) string {
|
|
binding, err := s.nc.ResolvePublicShareBinding(ctx, token, password)
|
|
if err == nil {
|
|
return binding.ClientSourcePath(clientPath, displayName)
|
|
}
|
|
if name := strings.TrimSpace(displayName); name != "" {
|
|
return normalizePath("/" + name)
|
|
}
|
|
return clientPath
|
|
}
|
|
|
|
func (s *Service) publicSidecarExists(ctx context.Context, token, password, clientSidecar, displayName string) bool {
|
|
if _, err := s.publicFileExists(ctx, token, clientSidecar, password); err == nil {
|
|
return true
|
|
}
|
|
binding, err := s.nc.ResolvePublicShareBinding(ctx, token, password)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
ownerPath := binding.OwnerPathForClient(clientSidecar, displayName)
|
|
if _, err := s.nc.FileRevision(ctx, binding.OwnerID, ownerPath); err == nil {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (s *Service) ImportPublicDocument(ctx context.Context, token, password, displayName string, req ImportRequest) (string, error) {
|
|
source := normalizePath(req.SourcePath)
|
|
canonical, _, importRequired, err := s.resolvePublicCanonicalPath(ctx, token, source, password, displayName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if !importRequired {
|
|
return canonical, nil
|
|
}
|
|
|
|
var content json.RawMessage
|
|
if len(req.Content) > 0 {
|
|
content = req.Content
|
|
} else {
|
|
body, err := s.LoadPublicDocument(ctx, token, source, password, displayName)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
name := fileNameFromPath(source)
|
|
content, err = ImportBytes(name, "", body)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
doc := NewUltiDoc(content, &UltiDocSource{
|
|
Path: source,
|
|
ImportedAt: time.Now().UTC().Format(time.RFC3339),
|
|
})
|
|
payload, err := doc.Marshal()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if err := s.SavePublicDocument(ctx, token, canonical, password, displayName, payload); err != nil {
|
|
return "", err
|
|
}
|
|
return canonical, nil
|
|
}
|
|
|
|
func (s *Service) publicFileExists(ctx context.Context, token, path, password string) (bool, error) {
|
|
_, err := s.nc.PublicShareFileRevision(ctx, token, path, password)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func (s *Service) LoadPublicDocument(ctx context.Context, token, clientPath, password, displayName string) ([]byte, error) {
|
|
if binding, err := s.nc.ResolvePublicShareBinding(ctx, token, password); err == nil {
|
|
ownerPath := binding.OwnerPathForClient(clientPath, displayName)
|
|
body, _, err := s.nc.Download(ctx, binding.OwnerID, ownerPath)
|
|
if err == nil {
|
|
defer body.Close()
|
|
return io.ReadAll(body)
|
|
}
|
|
}
|
|
body, _, err := s.nc.DownloadPublicShare(ctx, token, clientPath, password)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer body.Close()
|
|
return io.ReadAll(body)
|
|
}
|
|
|
|
func (s *Service) SavePublicDocument(ctx context.Context, token, clientPath, password, displayName string, raw []byte) error {
|
|
if binding, err := s.nc.ResolvePublicShareBinding(ctx, token, password); err == nil {
|
|
ownerPath := binding.OwnerPathForClient(clientPath, displayName)
|
|
reader := strings.NewReader(string(raw))
|
|
if err := s.nc.Upload(ctx, binding.OwnerID, ownerPath, reader, "application/json"); err == nil {
|
|
return nil
|
|
}
|
|
}
|
|
reader := strings.NewReader(string(raw))
|
|
return s.nc.UploadPublicShare(ctx, token, clientPath, password, reader, "application/json")
|
|
}
|
|
|
|
func (s *Service) LoadPublicDocumentLegacy(ctx context.Context, token, path, password string) ([]byte, error) {
|
|
return s.LoadPublicDocument(ctx, token, path, password, "")
|
|
}
|
|
|
|
func (s *Service) SavePublicDocumentLegacy(ctx context.Context, token, path, password string, raw []byte) error {
|
|
return s.SavePublicDocument(ctx, token, path, password, "", raw)
|
|
}
|