feat: add knowledge management functionality with CRUD operations and database schema
- Implemented KnowledgeHandler for managing knowledge groups and items, including listing, creating, updating, and deleting operations. - Added database migration scripts to create necessary tables for knowledge management, including knowledge_groups, knowledge_items, knowledge_parse_tasks, and knowledge_chunks_meta. - Introduced prompt_rule_knowledge_groups table to associate prompt rules with knowledge groups.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
package objectstorage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/geo-platform/tenant-api/internal/shared/config"
|
||||
)
|
||||
|
||||
var ErrNotConfigured = errors.New("object storage is not configured")
|
||||
|
||||
type Client interface {
|
||||
Validate() error
|
||||
PutBytes(ctx context.Context, objectKey string, content []byte, contentType string) error
|
||||
GetBytes(ctx context.Context, objectKey string) ([]byte, error)
|
||||
Delete(ctx context.Context, objectKey string) error
|
||||
}
|
||||
|
||||
func New(cfg config.ObjectStorageConfig, logger *zap.Logger) Client {
|
||||
provider := strings.ToLower(strings.TrimSpace(cfg.Provider))
|
||||
switch provider {
|
||||
case "", "disabled":
|
||||
return disabledClient{reason: "object storage is disabled"}
|
||||
case "minio":
|
||||
return NewMinIOClient(cfg, logger)
|
||||
default:
|
||||
return disabledClient{reason: fmt.Sprintf("unsupported object storage provider %q", cfg.Provider)}
|
||||
}
|
||||
}
|
||||
|
||||
type disabledClient struct {
|
||||
reason string
|
||||
}
|
||||
|
||||
func (c disabledClient) Validate() error {
|
||||
if c.reason == "" {
|
||||
c.reason = "missing object storage configuration"
|
||||
}
|
||||
return fmt.Errorf("%w: %s", ErrNotConfigured, c.reason)
|
||||
}
|
||||
|
||||
func (c disabledClient) PutBytes(context.Context, string, []byte, string) error {
|
||||
return c.Validate()
|
||||
}
|
||||
|
||||
func (c disabledClient) GetBytes(context.Context, string) ([]byte, error) {
|
||||
return nil, c.Validate()
|
||||
}
|
||||
|
||||
func (c disabledClient) Delete(context.Context, string) error {
|
||||
return c.Validate()
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package objectstorage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/minio/minio-go/v7"
|
||||
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/geo-platform/tenant-api/internal/shared/config"
|
||||
"github.com/geo-platform/tenant-api/internal/shared/middleware"
|
||||
)
|
||||
|
||||
type minioClient struct {
|
||||
client *minio.Client
|
||||
bucket string
|
||||
region string
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewMinIOClient(cfg config.ObjectStorageConfig, logger *zap.Logger) Client {
|
||||
endpoint := strings.TrimSpace(cfg.Endpoint)
|
||||
accessKey := strings.TrimSpace(cfg.AccessKey)
|
||||
secretKey := strings.TrimSpace(cfg.SecretKey)
|
||||
bucket := strings.TrimSpace(cfg.Bucket)
|
||||
if endpoint == "" || accessKey == "" || secretKey == "" || bucket == "" {
|
||||
return disabledClient{reason: "missing object_storage minio endpoint/access_key/secret_key/bucket"}
|
||||
}
|
||||
|
||||
client, err := minio.New(endpoint, &minio.Options{
|
||||
Creds: credentials.NewStaticV4(accessKey, secretKey, ""),
|
||||
Secure: cfg.UseSSL,
|
||||
Region: strings.TrimSpace(cfg.Region),
|
||||
})
|
||||
if err != nil {
|
||||
return disabledClient{reason: fmt.Sprintf("init minio client failed: %v", err)}
|
||||
}
|
||||
|
||||
return &minioClient{
|
||||
client: client,
|
||||
bucket: bucket,
|
||||
region: strings.TrimSpace(cfg.Region),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *minioClient) Validate() error {
|
||||
if c == nil || c.client == nil {
|
||||
return fmt.Errorf("%w: minio client is not initialized", ErrNotConfigured)
|
||||
}
|
||||
if strings.TrimSpace(c.bucket) == "" {
|
||||
return fmt.Errorf("%w: minio bucket is empty", ErrNotConfigured)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *minioClient) PutBytes(
|
||||
ctx context.Context,
|
||||
objectKey string,
|
||||
content []byte,
|
||||
contentType string,
|
||||
) error {
|
||||
if err := c.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.TrimSpace(objectKey) == "" {
|
||||
return fmt.Errorf("object key is required")
|
||||
}
|
||||
|
||||
exists, err := c.client.BucketExists(ctx, c.bucket)
|
||||
if err != nil {
|
||||
c.logError(ctx, "minio bucket exists check failed",
|
||||
zap.String("bucket", c.bucket),
|
||||
zap.String("object_key", objectKey),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("check minio bucket: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
if err := c.client.MakeBucket(ctx, c.bucket, minio.MakeBucketOptions{Region: c.region}); err != nil {
|
||||
c.logError(ctx, "minio create bucket failed",
|
||||
zap.String("bucket", c.bucket),
|
||||
zap.String("object_key", objectKey),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("create minio bucket: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
_, err = c.client.PutObject(ctx, c.bucket, objectKey, bytes.NewReader(content), int64(len(content)), minio.PutObjectOptions{
|
||||
ContentType: contentType,
|
||||
})
|
||||
if err != nil {
|
||||
c.logError(ctx, "minio put object failed",
|
||||
zap.String("bucket", c.bucket),
|
||||
zap.String("object_key", objectKey),
|
||||
zap.Int("content_size", len(content)),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("put minio object: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *minioClient) GetBytes(ctx context.Context, objectKey string) ([]byte, error) {
|
||||
if err := c.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reader, err := c.client.GetObject(ctx, c.bucket, objectKey, minio.GetObjectOptions{})
|
||||
if err != nil {
|
||||
c.logError(ctx, "minio get object failed",
|
||||
zap.String("bucket", c.bucket),
|
||||
zap.String("object_key", objectKey),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("get minio object: %w", err)
|
||||
}
|
||||
defer reader.Close()
|
||||
|
||||
data, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
c.logError(ctx, "minio read object failed",
|
||||
zap.String("bucket", c.bucket),
|
||||
zap.String("object_key", objectKey),
|
||||
zap.Error(err),
|
||||
)
|
||||
return nil, fmt.Errorf("read minio object: %w", err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (c *minioClient) Delete(ctx context.Context, objectKey string) error {
|
||||
if err := c.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if strings.TrimSpace(objectKey) == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := c.client.RemoveObject(ctx, c.bucket, objectKey, minio.RemoveObjectOptions{})
|
||||
if err != nil {
|
||||
c.logError(ctx, "minio delete object failed",
|
||||
zap.String("bucket", c.bucket),
|
||||
zap.String("object_key", objectKey),
|
||||
zap.Error(err),
|
||||
)
|
||||
return fmt.Errorf("delete minio object: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *minioClient) logError(ctx context.Context, msg string, fields ...zap.Field) {
|
||||
if c == nil || c.logger == nil {
|
||||
return
|
||||
}
|
||||
if requestID := middleware.RequestIDFromContext(ctx); requestID != "" {
|
||||
fields = append(fields, zap.String("request_id", requestID))
|
||||
}
|
||||
c.logger.Error(msg, fields...)
|
||||
}
|
||||
Reference in New Issue
Block a user