mengstack-api/internal/kernel/storage/local.go
MengStack Dev df809f1045
Some checks failed
CI / Build & Test (push) Failing after 1m31s
feat: user CRUD API + dashboard stats + kernel infrastructure
- 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
2026-10-03 03:42:58 +08:00

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
}