package ultidraw import ( "context" "fmt" "io" "net/url" "strings" "time" "github.com/ultisuite/ulti-backend/internal/nextcloud" ) 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("ultidraw editor disabled") } resolvedPath, err := s.resolvePublicFilePath(ctx, token, filePath, password, displayName) if err != nil { return nil, err } filePath = resolvedPath if mode == "" { mode = "edit" } ownerID, ownerPath, err := s.ownerPathForPublic(ctx, token, password, filePath, 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: filePath, 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, filePath, password, s.Cfg.HocuspocusSecret) docURL := fmt.Sprintf("%s/api/v1/drive/public/shares/%s/ultidraw/document?path=%s&password=%s&sig=%s", apiBase, url.PathEscape(token), url.QueryEscape(filePath), 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: filePath, WsURL: wsURL, Token: tokenJWT, Mode: mode, Collaboration: collab, }, DocumentURL: docURL, SaveURL: saveURL, }, nil } func (s *Service) resolvePublicFilePath(ctx context.Context, token, filePath, password, displayName string) (string, error) { filePath = normalizePath(filePath) if filePath == "/" { filePath = s.publicClientSourcePath(ctx, token, password, filePath, displayName) } checkPath := filePath if !isExcalidrawPath(checkPath) && strings.TrimSpace(displayName) != "" { checkPath = normalizePath("/" + strings.TrimSpace(displayName)) } if !isExcalidrawPath(checkPath) { return "", fmt.Errorf("not an excalidraw file") } if !isExcalidrawPath(filePath) { filePath = checkPath } if _, err := s.publicFileExists(ctx, token, filePath, password); err != nil { if checkPath != filePath { if _, err2 := s.publicFileExists(ctx, token, checkPath, password); err2 != nil { return "", fmt.Errorf("file not found") } filePath = checkPath } else { return "", fmt.Errorf("file not found") } } return filePath, 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) 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) } func (s *Service) EffectivePublicSharePermissions(ctx context.Context, token, path, password string) (int, error) { return s.nc.EffectivePublicSharePermissions(ctx, token, path, password) } func (s *Service) PublicShareCanUpdate(perms int) bool { return nextcloud.PublicShareCanUpdate(perms) }