Some checks failed
CI / Build & Test (push) Failing after 1m31s
- Add user management endpoints (list/create/get/update/delete) with pagination and search - Add dashboard stats endpoint with tenant/user/online counts and growth metrics - Add tenant resolver middleware for multi-tenant request scoping - Add i18n kernel with zh/en message files and AcceptLanguage middleware - Add WebSocket hub/handler for real-time communication - Add job scheduler kernel with cron support - Add plugin sandbox for isolated execution - Add storage kernel (local filesystem) - Add event bus kernel for pub/sub - Add cache kernel abstraction - Add database migration runner and version upgrade checker - Add rate limiting middleware with Redis backend - Add SQL migrations for rbac, audit_logs, settings, notifications, examples - Extend user repository with list/delete/count operations - Register all module routes with tenant resolver
86 lines
1.9 KiB
Go
86 lines
1.9 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
)
|
|
|
|
type LocalStore struct {
|
|
rootDir string
|
|
baseURL string
|
|
}
|
|
|
|
func NewLocalStore(rootDir, baseURL string) (*LocalStore, error) {
|
|
if err := os.MkdirAll(rootDir, 0755); err != nil {
|
|
return nil, fmt.Errorf("create storage dir: %w", err)
|
|
}
|
|
return &LocalStore{rootDir: rootDir, baseURL: baseURL}, nil
|
|
}
|
|
|
|
func (s *LocalStore) Upload(ctx context.Context, tenantID string, file *File) (*StoredFile, error) {
|
|
now := time.Now()
|
|
dir := filepath.Join(s.rootDir, tenantID, now.Format("2006/01/02"))
|
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
|
return nil, fmt.Errorf("create upload dir: %w", err)
|
|
}
|
|
|
|
ext := filepath.Ext(file.Name)
|
|
key := generateKey(ext)
|
|
destPath := filepath.Join(dir, key)
|
|
|
|
dest, err := os.Create(destPath)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("create file: %w", err)
|
|
}
|
|
defer dest.Close()
|
|
|
|
written, err := io.Copy(dest, file.Reader)
|
|
if err != nil {
|
|
os.Remove(destPath)
|
|
return nil, fmt.Errorf("write file: %w", err)
|
|
}
|
|
|
|
relKey := filepath.Join(tenantID, now.Format("2006/01/02"), key)
|
|
return &StoredFile{
|
|
Key: relKey,
|
|
URL: s.URL(relKey),
|
|
Size: written,
|
|
}, nil
|
|
}
|
|
|
|
func (s *LocalStore) Delete(ctx context.Context, key string) error {
|
|
path := filepath.Join(s.rootDir, key)
|
|
if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
|
|
return fmt.Errorf("delete file: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *LocalStore) Exists(ctx context.Context, key string) (bool, error) {
|
|
path := filepath.Join(s.rootDir, key)
|
|
_, err := os.Stat(path)
|
|
if err == nil {
|
|
return true, nil
|
|
}
|
|
if os.IsNotExist(err) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
func (s *LocalStore) URL(key string) string {
|
|
return fmt.Sprintf("%s/uploads/%s", s.baseURL, key)
|
|
}
|
|
|
|
func generateKey(ext string) string {
|
|
b := make([]byte, 16)
|
|
rand.Read(b)
|
|
return hex.EncodeToString(b) + ext
|
|
}
|