ultisuite-backend/internal/api/ultidraw/service.go
R3D347HR4Y 0466a1c169
Some checks are pending
CI / Go tests (push) Waiting to run
CI / Integration tests (push) Waiting to run
CI / DB migrations (push) Waiting to run
wow
2026-06-11 01:22:52 +02:00

128 lines
3.3 KiB
Go

package ultidraw
import (
"bytes"
"context"
"fmt"
"io"
"strings"
"time"
"github.com/ultisuite/ulti-backend/internal/auth"
"github.com/ultisuite/ulti-backend/internal/nextcloud"
)
type Service struct {
nc *nextcloud.Client
Cfg Config
hub fileChangePublisher
}
type fileChangePublisher interface {
PublishFileChanged(platformUserID, path string)
}
func NewService(nc *nextcloud.Client, cfg Config, hub fileChangePublisher) *Service {
return &Service{nc: nc, Cfg: cfg, hub: hub}
}
func (s *Service) EnsureNextcloudUser(ctx context.Context, claims *auth.Claims) (string, error) {
return s.nc.EnsurePrincipal(ctx, claims.Email, claims.Sub, claims.Name)
}
type SessionResult struct {
RoomID string `json:"roomId"`
CanonicalPath string `json:"canonicalPath"`
WsURL string `json:"wsUrl"`
Token string `json:"token"`
Mode string `json:"mode"`
Collaboration bool `json:"collaboration"`
}
type PublicSessionResult struct {
SessionResult
DocumentURL string `json:"documentUrl,omitempty"`
SaveURL string `json:"saveUrl,omitempty"`
}
func (s *Service) CreateSession(ctx context.Context, ncUser, filePath, mode, editorUserID, editorName string) (*SessionResult, error) {
if !s.Cfg.Enabled {
return nil, fmt.Errorf("ultidraw editor disabled")
}
filePath = normalizePath(filePath)
if !isExcalidrawPath(filePath) {
return nil, fmt.Errorf("not an excalidraw file: %s", filePath)
}
if _, err := s.nc.FileRevision(ctx, ncUser, filePath); err != nil {
return nil, fmt.Errorf("file not found: %s", filePath)
}
if mode == "" {
mode = "edit"
}
roomID, err := s.resolveCollabRoomID(ctx, ncUser, filePath)
if err != nil {
return nil, err
}
token, err := signRoomToken(roomTokenPayload{
Room: roomID,
Path: filePath,
User: ncUser,
Sub: editorUserID,
Name: editorName,
Mode: mode,
Expires: time.Now().Add(8 * time.Hour).Unix(),
}, s.Cfg.HocuspocusSecret)
if err != nil {
return nil, err
}
wsURL := strings.TrimSpace(s.Cfg.HocuspocusPublicURL)
collab := wsURL != "" && s.Cfg.HocuspocusSecret != ""
return &SessionResult{
RoomID: roomID,
CanonicalPath: filePath,
WsURL: wsURL,
Token: token,
Mode: mode,
Collaboration: collab,
}, nil
}
func (s *Service) LoadDocument(ctx context.Context, ncUser, path string) ([]byte, error) {
path = normalizePath(path)
body, _, err := s.nc.Download(ctx, ncUser, path)
if err != nil {
return nil, err
}
defer body.Close()
return io.ReadAll(body)
}
func (s *Service) LoadDocumentForUser(ctx context.Context, ncUser, path string) ([]byte, error) {
path = normalizePath(path)
if strings.HasPrefix(ncUser, "public:") {
token := strings.TrimPrefix(ncUser, "public:")
return s.LoadPublicDocumentLegacy(ctx, token, path, "")
}
return s.LoadDocument(ctx, ncUser, path)
}
func (s *Service) SaveDocument(ctx context.Context, ncUser, path string, raw []byte, platformUserID string) error {
path = normalizePath(path)
reader := bytes.NewReader(raw)
if strings.HasPrefix(ncUser, "public:") {
token := strings.TrimPrefix(ncUser, "public:")
return s.SavePublicDocumentLegacy(ctx, token, path, "", raw)
}
if err := s.nc.Upload(ctx, ncUser, path, reader, "application/json"); err != nil {
return err
}
if s.hub != nil && platformUserID != "" {
s.hub.PublishFileChanged(platformUserID, path)
}
return nil
}