WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Tencent/WeKnora/internal/types"
|
||||
"github.com/Tencent/WeKnora/internal/types/interfaces"
|
||||
)
|
||||
|
||||
// resourceCatalogFileService keeps provider drivers physical-path-only while
|
||||
// exposing stable resource:// references to the application layer.
|
||||
type resourceCatalogFileService struct {
|
||||
inner interfaces.FileService
|
||||
catalog interfaces.ResourceCatalog
|
||||
externalURL string
|
||||
}
|
||||
|
||||
// NewResourceCatalogFileService decorates a physical FileService with stable
|
||||
// resource registration and resolution.
|
||||
func NewResourceCatalogFileService(
|
||||
inner interfaces.FileService,
|
||||
catalog interfaces.ResourceCatalog,
|
||||
) interfaces.FileService {
|
||||
if inner == nil || catalog == nil {
|
||||
return inner
|
||||
}
|
||||
return &resourceCatalogFileService{
|
||||
inner: inner,
|
||||
catalog: catalog,
|
||||
externalURL: strings.TrimRight(strings.TrimSpace(os.Getenv("APP_EXTERNAL_URL")), "/"),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *resourceCatalogFileService) CheckConnectivity(ctx context.Context) error {
|
||||
return s.inner.CheckConnectivity(ctx)
|
||||
}
|
||||
|
||||
func resourceKind(name string) (string, string) {
|
||||
mimeType := mime.TypeByExtension(strings.ToLower(filepath.Ext(name)))
|
||||
kind := "file"
|
||||
switch {
|
||||
case strings.HasPrefix(mimeType, "image/"):
|
||||
kind = "image"
|
||||
case strings.HasPrefix(mimeType, "audio/"):
|
||||
kind = "audio"
|
||||
case strings.HasPrefix(mimeType, "video/"):
|
||||
kind = "video"
|
||||
}
|
||||
return kind, mimeType
|
||||
}
|
||||
|
||||
func (s *resourceCatalogFileService) register(
|
||||
ctx context.Context,
|
||||
physical string,
|
||||
tenantID uint64,
|
||||
name string,
|
||||
size int64,
|
||||
temporary bool,
|
||||
contentHash string,
|
||||
) (string, error) {
|
||||
kind, mimeType := resourceKind(name)
|
||||
ref, err := s.catalog.Register(ctx, tenantID, physical, interfaces.ResourceRegistration{
|
||||
Kind: kind,
|
||||
MimeType: mimeType,
|
||||
OriginalName: filepath.Base(name),
|
||||
Size: size,
|
||||
ContentHash: contentHash,
|
||||
Temporary: temporary,
|
||||
})
|
||||
if err != nil {
|
||||
_ = s.inner.DeleteFile(ctx, physical)
|
||||
return "", fmt.Errorf("register stored resource: %w", err)
|
||||
}
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
func (s *resourceCatalogFileService) SaveFile(
|
||||
ctx context.Context,
|
||||
file *multipart.FileHeader,
|
||||
tenantID uint64,
|
||||
knowledgeID string,
|
||||
) (string, error) {
|
||||
physical, err := s.inner.SaveFile(ctx, file, tenantID, knowledgeID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ref, err := s.register(ctx, physical, tenantID, file.Filename, file.Size, false, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if knowledgeID != "" {
|
||||
if err := s.catalog.Bind(ctx, ref, types.ResourceOwnerKnowledge, knowledgeID, types.ResourceRelationSourceFile); err != nil {
|
||||
_ = s.DeleteFile(ctx, ref)
|
||||
return "", fmt.Errorf("bind stored resource: %w", err)
|
||||
}
|
||||
}
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
func (s *resourceCatalogFileService) SaveBytes(
|
||||
ctx context.Context,
|
||||
data []byte,
|
||||
tenantID uint64,
|
||||
fileName string,
|
||||
temp bool,
|
||||
) (string, error) {
|
||||
physical, err := s.inner.SaveBytes(ctx, data, tenantID, fileName, temp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
sum := sha256.Sum256(data)
|
||||
return s.register(ctx, physical, tenantID, fileName, int64(len(data)), temp, hex.EncodeToString(sum[:]))
|
||||
}
|
||||
|
||||
func (s *resourceCatalogFileService) resolve(ctx context.Context, value string) (string, bool, error) {
|
||||
physical, resource, err := s.catalog.ResolvePath(ctx, value)
|
||||
return physical, resource != nil, err
|
||||
}
|
||||
|
||||
func (s *resourceCatalogFileService) GetFile(ctx context.Context, filePath string) (io.ReadCloser, error) {
|
||||
physical, _, err := s.resolve(ctx, filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.inner.GetFile(ctx, physical)
|
||||
}
|
||||
|
||||
func (s *resourceCatalogFileService) GetFileURL(ctx context.Context, filePath string) (string, error) {
|
||||
physical, isResource, err := s.resolve(ctx, filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if isResource && s.externalURL != "" {
|
||||
token, grantErr := s.catalog.CreateAccessGrant(ctx, filePath, 2*time.Hour)
|
||||
if grantErr != nil {
|
||||
return "", grantErr
|
||||
}
|
||||
return s.externalURL + "/r/" + token, nil
|
||||
}
|
||||
return s.inner.GetFileURL(ctx, physical)
|
||||
}
|
||||
|
||||
func (s *resourceCatalogFileService) DeleteFile(ctx context.Context, filePath string) error {
|
||||
physical, isResource, err := s.resolve(ctx, filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.inner.DeleteFile(ctx, physical); err != nil {
|
||||
return err
|
||||
}
|
||||
if isResource {
|
||||
return s.catalog.MarkDeleted(ctx, filePath)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *resourceCatalogFileService) CopyFile(
|
||||
ctx context.Context,
|
||||
filePath string,
|
||||
tenantID uint64,
|
||||
knowledgeID string,
|
||||
) (string, error) {
|
||||
physical, _, err := s.resolve(ctx, filePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
copied, err := s.inner.CopyFile(ctx, physical, tenantID, knowledgeID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
ref, err := s.register(ctx, copied, tenantID, filepath.Base(physical), 0, false, "")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if knowledgeID != "" {
|
||||
if err := s.catalog.Bind(ctx, ref, types.ResourceOwnerKnowledge, knowledgeID, types.ResourceRelationSourceFile); err != nil {
|
||||
_ = s.DeleteFile(ctx, ref)
|
||||
return "", fmt.Errorf("bind copied resource: %w", err)
|
||||
}
|
||||
}
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
var _ interfaces.FileService = (*resourceCatalogFileService)(nil)
|
||||
Reference in New Issue
Block a user