mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 07:54:22 +00:00
Rename filesystem interfaces and structs
This commit is contained in:
@@ -17,8 +17,8 @@ import (
|
||||
"novamd/internal/auth"
|
||||
"novamd/internal/config"
|
||||
"novamd/internal/db"
|
||||
"novamd/internal/filesystem"
|
||||
"novamd/internal/handlers"
|
||||
"novamd/internal/storage"
|
||||
)
|
||||
|
||||
func main() {
|
||||
@@ -45,7 +45,7 @@ func main() {
|
||||
}
|
||||
|
||||
// Initialize filesystem
|
||||
fs := filesystem.New(cfg.WorkDir)
|
||||
s := storage.NewService(cfg.WorkDir)
|
||||
|
||||
// Initialize JWT service
|
||||
jwtService, err := auth.NewJWTService(auth.JWTConfig{
|
||||
@@ -95,7 +95,7 @@ func main() {
|
||||
// Set up routes
|
||||
r.Route("/api/v1", func(r chi.Router) {
|
||||
r.Use(httprate.LimitByIP(cfg.RateLimitRequests, cfg.RateLimitWindow))
|
||||
api.SetupRoutes(r, database, fs, authMiddleware, sessionService)
|
||||
api.SetupRoutes(r, database, s, authMiddleware, sessionService)
|
||||
})
|
||||
|
||||
// Handle all other routes with static file server
|
||||
|
||||
@@ -4,19 +4,19 @@ package api
|
||||
import (
|
||||
"novamd/internal/auth"
|
||||
"novamd/internal/db"
|
||||
"novamd/internal/filesystem"
|
||||
"novamd/internal/handlers"
|
||||
"novamd/internal/middleware"
|
||||
"novamd/internal/storage"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// SetupRoutes configures the API routes
|
||||
func SetupRoutes(r chi.Router, db *db.DB, s *filesystem.Storage, authMiddleware *auth.Middleware, sessionService *auth.SessionService) {
|
||||
func SetupRoutes(r chi.Router, db *db.DB, s storage.Manager, authMiddleware *auth.Middleware, sessionService *auth.SessionService) {
|
||||
|
||||
handler := &handlers.Handler{
|
||||
DB: db,
|
||||
S: s,
|
||||
DB: db,
|
||||
Storage: s,
|
||||
}
|
||||
|
||||
// Public routes (no authentication required)
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package filesystem
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"novamd/internal/gitutils"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Storage represents the file system structure.
|
||||
type Storage struct {
|
||||
fs fileSystem
|
||||
RootDir string
|
||||
GitRepos map[int]map[int]*gitutils.GitRepo // map[userID]map[workspaceID]*gitutils.GitRepo
|
||||
}
|
||||
|
||||
// New creates a new Storage instance.
|
||||
// Parameters:
|
||||
// - rootDir: the root directory for the storage
|
||||
// Returns:
|
||||
// - result: the new Storage instance
|
||||
func New(rootDir string) *Storage {
|
||||
return NewWithFS(rootDir, &osFS{})
|
||||
}
|
||||
|
||||
// NewWithFS creates a new Storage instance with the given filesystem.
|
||||
// Parameters:
|
||||
// - rootDir: the root directory for the storage
|
||||
// - fs: the filesystem implementation to use
|
||||
// Returns:
|
||||
// - result: the new Storage instance
|
||||
func NewWithFS(rootDir string, fs fileSystem) *Storage {
|
||||
return &Storage{
|
||||
fs: fs,
|
||||
RootDir: rootDir,
|
||||
GitRepos: make(map[int]map[int]*gitutils.GitRepo),
|
||||
}
|
||||
}
|
||||
|
||||
// ValidatePath validates the given path and returns the cleaned path if it is valid.
|
||||
// Parameters:
|
||||
// - userID: the ID of the user who owns the workspace
|
||||
// - workspaceID: the ID of the workspace to validate the path for
|
||||
// - path: the path to validate
|
||||
// Returns:
|
||||
// - result: the cleaned path if it is valid
|
||||
// - error: any error that occurred during validation
|
||||
func (s *Storage) ValidatePath(userID, workspaceID int, path string) (string, error) {
|
||||
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||
fullPath := filepath.Join(workspacePath, path)
|
||||
cleanPath := filepath.Clean(fullPath)
|
||||
|
||||
if !strings.HasPrefix(cleanPath, workspacePath) {
|
||||
return "", fmt.Errorf("invalid path: outside of workspace")
|
||||
}
|
||||
|
||||
return cleanPath, nil
|
||||
}
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"novamd/internal/db"
|
||||
"novamd/internal/filesystem"
|
||||
"novamd/internal/httpcontext"
|
||||
"novamd/internal/models"
|
||||
"novamd/internal/storage"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@@ -91,7 +91,7 @@ func (h *Handler) AdminCreateUser() http.HandlerFunc {
|
||||
}
|
||||
|
||||
// Initialize user workspace
|
||||
if err := h.S.InitializeUserWorkspace(insertedUser.ID, insertedUser.LastWorkspaceID); err != nil {
|
||||
if err := h.Storage.InitializeUserWorkspace(insertedUser.ID, insertedUser.LastWorkspaceID); err != nil {
|
||||
http.Error(w, "Failed to initialize user workspace", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -218,7 +218,7 @@ type WorkspaceStats struct {
|
||||
WorkspaceID int `json:"workspaceID"`
|
||||
WorkspaceName string `json:"workspaceName"`
|
||||
WorkspaceCreatedAt time.Time `json:"workspaceCreatedAt"`
|
||||
*filesystem.FileCountStats
|
||||
*storage.FileCountStats
|
||||
}
|
||||
|
||||
// AdminListWorkspaces returns a list of all workspaces and their stats
|
||||
@@ -248,7 +248,7 @@ func (h *Handler) AdminListWorkspaces() http.HandlerFunc {
|
||||
workspaceData.WorkspaceName = ws.Name
|
||||
workspaceData.WorkspaceCreatedAt = ws.CreatedAt
|
||||
|
||||
fileStats, err := h.S.GetFileStats(ws.UserID, ws.ID)
|
||||
fileStats, err := h.Storage.GetFileStats(ws.UserID, ws.ID)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to get file stats", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -266,7 +266,7 @@ func (h *Handler) AdminListWorkspaces() http.HandlerFunc {
|
||||
// SystemStats holds system-wide statistics
|
||||
type SystemStats struct {
|
||||
*db.UserStats
|
||||
*filesystem.FileCountStats
|
||||
*storage.FileCountStats
|
||||
}
|
||||
|
||||
// AdminGetSystemStats returns system-wide statistics for admins
|
||||
@@ -278,7 +278,7 @@ func (h *Handler) AdminGetSystemStats() http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
fileStats, err := h.S.GetTotalFileStats()
|
||||
fileStats, err := h.Storage.GetTotalFileStats()
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to get file stats", http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
@@ -17,7 +17,7 @@ func (h *Handler) ListFiles() http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
files, err := h.S.ListFilesRecursively(ctx.UserID, ctx.Workspace.ID)
|
||||
files, err := h.Storage.ListFilesRecursively(ctx.UserID, ctx.Workspace.ID)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to list files", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -40,7 +40,7 @@ func (h *Handler) LookupFileByName() http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
filePaths, err := h.S.FindFileByName(ctx.UserID, ctx.Workspace.ID, filename)
|
||||
filePaths, err := h.Storage.FindFileByName(ctx.UserID, ctx.Workspace.ID, filename)
|
||||
if err != nil {
|
||||
http.Error(w, "File not found", http.StatusNotFound)
|
||||
return
|
||||
@@ -58,7 +58,7 @@ func (h *Handler) GetFileContent() http.HandlerFunc {
|
||||
}
|
||||
|
||||
filePath := chi.URLParam(r, "*")
|
||||
content, err := h.S.GetFileContent(ctx.UserID, ctx.Workspace.ID, filePath)
|
||||
content, err := h.Storage.GetFileContent(ctx.UserID, ctx.Workspace.ID, filePath)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to read file", http.StatusNotFound)
|
||||
return
|
||||
@@ -83,7 +83,7 @@ func (h *Handler) SaveFile() http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
err = h.S.SaveFile(ctx.UserID, ctx.Workspace.ID, filePath, content)
|
||||
err = h.Storage.SaveFile(ctx.UserID, ctx.Workspace.ID, filePath, content)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to save file", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -101,7 +101,7 @@ func (h *Handler) DeleteFile() http.HandlerFunc {
|
||||
}
|
||||
|
||||
filePath := chi.URLParam(r, "*")
|
||||
err := h.S.DeleteFile(ctx.UserID, ctx.Workspace.ID, filePath)
|
||||
err := h.Storage.DeleteFile(ctx.UserID, ctx.Workspace.ID, filePath)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to delete file", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -125,7 +125,7 @@ func (h *Handler) GetLastOpenedFile() http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := h.S.ValidatePath(ctx.UserID, ctx.Workspace.ID, filePath); err != nil {
|
||||
if _, err := h.Storage.ValidatePath(ctx.UserID, ctx.Workspace.ID, filePath); err != nil {
|
||||
http.Error(w, "Invalid file path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -152,7 +152,7 @@ func (h *Handler) UpdateLastOpenedFile() http.HandlerFunc {
|
||||
|
||||
// Validate the file path exists in the workspace
|
||||
if requestBody.FilePath != "" {
|
||||
if _, err := h.S.ValidatePath(ctx.UserID, ctx.Workspace.ID, requestBody.FilePath); err != nil {
|
||||
if _, err := h.Storage.ValidatePath(ctx.UserID, ctx.Workspace.ID, requestBody.FilePath); err != nil {
|
||||
http.Error(w, "Invalid file path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ func (h *Handler) StageCommitAndPush() http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
err := h.S.StageCommitAndPush(ctx.UserID, ctx.Workspace.ID, requestBody.Message)
|
||||
err := h.Storage.StageCommitAndPush(ctx.UserID, ctx.Workspace.ID, requestBody.Message)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to stage, commit, and push changes: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
@@ -45,7 +45,7 @@ func (h *Handler) PullChanges() http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
err := h.S.Pull(ctx.UserID, ctx.Workspace.ID)
|
||||
err := h.Storage.Pull(ctx.UserID, ctx.Workspace.ID)
|
||||
if err != nil {
|
||||
http.Error(w, "Failed to pull changes: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
@@ -4,20 +4,20 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"novamd/internal/db"
|
||||
"novamd/internal/filesystem"
|
||||
"novamd/internal/storage"
|
||||
)
|
||||
|
||||
// Handler provides common functionality for all handlers
|
||||
type Handler struct {
|
||||
DB *db.DB
|
||||
S *filesystem.Storage
|
||||
DB *db.DB
|
||||
Storage storage.Manager
|
||||
}
|
||||
|
||||
// NewHandler creates a new handler with the given dependencies
|
||||
func NewHandler(db *db.DB, fs *filesystem.Storage) *Handler {
|
||||
func NewHandler(db *db.DB, s storage.Manager) *Handler {
|
||||
return &Handler{
|
||||
DB: db,
|
||||
S: fs,
|
||||
DB: db,
|
||||
Storage: s,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ func (h *Handler) DeleteAccount() http.HandlerFunc {
|
||||
|
||||
// Delete workspace directories
|
||||
for _, workspace := range workspaces {
|
||||
if err := h.S.DeleteUserWorkspace(ctx.UserID, workspace.ID); err != nil {
|
||||
if err := h.Storage.DeleteUserWorkspace(ctx.UserID, workspace.ID); err != nil {
|
||||
http.Error(w, "Failed to delete workspace files", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ func (h *Handler) CreateWorkspace() http.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.S.InitializeUserWorkspace(workspace.UserID, workspace.ID); err != nil {
|
||||
if err := h.Storage.InitializeUserWorkspace(workspace.UserID, workspace.ID); err != nil {
|
||||
http.Error(w, "Failed to initialize workspace directory", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -107,7 +107,7 @@ func (h *Handler) UpdateWorkspace() http.HandlerFunc {
|
||||
// Handle Git repository setup/teardown if Git settings changed
|
||||
if gitSettingsChanged(&workspace, ctx.Workspace) {
|
||||
if workspace.GitEnabled {
|
||||
if err := h.S.SetupGitRepo(
|
||||
if err := h.Storage.SetupGitRepo(
|
||||
ctx.UserID,
|
||||
ctx.Workspace.ID,
|
||||
workspace.GitURL,
|
||||
@@ -119,7 +119,7 @@ func (h *Handler) UpdateWorkspace() http.HandlerFunc {
|
||||
}
|
||||
|
||||
} else {
|
||||
h.S.DisableGitRepo(ctx.UserID, ctx.Workspace.ID)
|
||||
h.Storage.DisableGitRepo(ctx.UserID, ctx.Workspace.ID)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Package filesystem provides functionalities to interact with the file system,
|
||||
// Package storage provides functionalities to interact with the file system,
|
||||
// including listing files, finding files by name, getting file content, saving files, and deleting files.
|
||||
package filesystem
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -10,12 +10,23 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// StorageNode represents a file or directory in the storage.
|
||||
type StorageNode struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Children []StorageNode `json:"children,omitempty"`
|
||||
// FileManager provides functionalities to interact with files in the storage.
|
||||
type FileManager interface {
|
||||
ListFilesRecursively(userID, workspaceID int) ([]FileNode, error)
|
||||
FindFileByName(userID, workspaceID int, filename string) ([]string, error)
|
||||
GetFileContent(userID, workspaceID int, filePath string) ([]byte, error)
|
||||
SaveFile(userID, workspaceID int, filePath string, content []byte) error
|
||||
DeleteFile(userID, workspaceID int, filePath string) error
|
||||
GetFileStats(userID, workspaceID int) (*FileCountStats, error)
|
||||
GetTotalFileStats() (*FileCountStats, error)
|
||||
}
|
||||
|
||||
// FileNode represents a file or directory in the storage.
|
||||
type FileNode struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Children []FileNode `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
// ListFilesRecursively returns a list of all files in the workspace directory and its subdirectories.
|
||||
@@ -25,13 +36,13 @@ type StorageNode struct {
|
||||
// Returns:
|
||||
// - nodes: a list of files and directories in the workspace
|
||||
// - error: any error that occurred during listing
|
||||
func (s *Storage) ListFilesRecursively(userID, workspaceID int) ([]StorageNode, error) {
|
||||
func (s *Service) ListFilesRecursively(userID, workspaceID int) ([]FileNode, error) {
|
||||
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||
return s.walkDirectory(workspacePath, "")
|
||||
}
|
||||
|
||||
// walkDirectory recursively walks the directory and returns a list of files and directories.
|
||||
func (s *Storage) walkDirectory(dir, prefix string) ([]StorageNode, error) {
|
||||
func (s *Service) walkDirectory(dir, prefix string) ([]FileNode, error) {
|
||||
entries, err := s.fs.ReadDir(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -56,7 +67,7 @@ func (s *Storage) walkDirectory(dir, prefix string) ([]StorageNode, error) {
|
||||
})
|
||||
|
||||
// Create combined slice with directories first, then files
|
||||
nodes := make([]StorageNode, 0, len(entries))
|
||||
nodes := make([]FileNode, 0, len(entries))
|
||||
|
||||
// Add directories first
|
||||
for _, entry := range dirs {
|
||||
@@ -69,7 +80,7 @@ func (s *Storage) walkDirectory(dir, prefix string) ([]StorageNode, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node := StorageNode{
|
||||
node := FileNode{
|
||||
ID: path,
|
||||
Name: name,
|
||||
Path: path,
|
||||
@@ -83,7 +94,7 @@ func (s *Storage) walkDirectory(dir, prefix string) ([]StorageNode, error) {
|
||||
name := entry.Name()
|
||||
path := filepath.Join(prefix, name)
|
||||
|
||||
node := StorageNode{
|
||||
node := FileNode{
|
||||
ID: path,
|
||||
Name: name,
|
||||
Path: path,
|
||||
@@ -102,7 +113,7 @@ func (s *Storage) walkDirectory(dir, prefix string) ([]StorageNode, error) {
|
||||
// Returns:
|
||||
// - foundPaths: a list of file paths that match the filename
|
||||
// - error: any error that occurred during the search
|
||||
func (s *Storage) FindFileByName(userID, workspaceID int, filename string) ([]string, error) {
|
||||
func (s *Service) FindFileByName(userID, workspaceID int, filename string) ([]string, error) {
|
||||
var foundPaths []string
|
||||
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||
|
||||
@@ -141,7 +152,7 @@ func (s *Storage) FindFileByName(userID, workspaceID int, filename string) ([]st
|
||||
// Returns:
|
||||
// - content: the content of the file
|
||||
// - error: any error that occurred during reading
|
||||
func (s *Storage) GetFileContent(userID, workspaceID int, filePath string) ([]byte, error) {
|
||||
func (s *Service) GetFileContent(userID, workspaceID int, filePath string) ([]byte, error) {
|
||||
fullPath, err := s.ValidatePath(userID, workspaceID, filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -157,7 +168,7 @@ func (s *Storage) GetFileContent(userID, workspaceID int, filePath string) ([]by
|
||||
// - content: the content to write to the file
|
||||
// Returns:
|
||||
// - error: any error that occurred during saving
|
||||
func (s *Storage) SaveFile(userID, workspaceID int, filePath string, content []byte) error {
|
||||
func (s *Service) SaveFile(userID, workspaceID int, filePath string, content []byte) error {
|
||||
fullPath, err := s.ValidatePath(userID, workspaceID, filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -178,7 +189,7 @@ func (s *Storage) SaveFile(userID, workspaceID int, filePath string, content []b
|
||||
// - filePath: the path of the file to delete
|
||||
// Returns:
|
||||
// - error: any error that occurred during deletion
|
||||
func (s *Storage) DeleteFile(userID, workspaceID int, filePath string) error {
|
||||
func (s *Service) DeleteFile(userID, workspaceID int, filePath string) error {
|
||||
fullPath, err := s.ValidatePath(userID, workspaceID, filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -199,7 +210,7 @@ type FileCountStats struct {
|
||||
// Returns:
|
||||
// - result: statistics about the files in the workspace
|
||||
// - error: any error that occurred during counting
|
||||
func (s *Storage) GetFileStats(userID, workspaceID int) (*FileCountStats, error) {
|
||||
func (s *Service) GetFileStats(userID, workspaceID int) (*FileCountStats, error) {
|
||||
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||
|
||||
// Check if workspace exists
|
||||
@@ -214,12 +225,12 @@ func (s *Storage) GetFileStats(userID, workspaceID int) (*FileCountStats, error)
|
||||
// GetTotalFileStats returns the total file statistics for the storage.
|
||||
// Returns:
|
||||
// - result: statistics about the files in the storage
|
||||
func (s *Storage) GetTotalFileStats() (*FileCountStats, error) {
|
||||
func (s *Service) GetTotalFileStats() (*FileCountStats, error) {
|
||||
return s.countFilesInPath(s.RootDir)
|
||||
}
|
||||
|
||||
// countFilesInPath counts the total number of files and the total size of files in the given directory.
|
||||
func (s *Storage) countFilesInPath(directoryPath string) (*FileCountStats, error) {
|
||||
func (s *Service) countFilesInPath(directoryPath string) (*FileCountStats, error) {
|
||||
result := &FileCountStats{}
|
||||
|
||||
err := filepath.WalkDir(directoryPath, func(path string, d os.DirEntry, err error) error {
|
||||
@@ -1,4 +1,4 @@
|
||||
package filesystem
|
||||
package storage
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
46
server/internal/storage/filesystem_test.go
Normal file
46
server/internal/storage/filesystem_test.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package storage_test
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"os"
|
||||
"testing/fstest"
|
||||
)
|
||||
|
||||
// mapFS adapts testing.MapFS to implement our fileSystem interface
|
||||
type mapFS struct {
|
||||
fstest.MapFS
|
||||
}
|
||||
|
||||
func NewMapFS() *mapFS {
|
||||
return &mapFS{
|
||||
MapFS: make(fstest.MapFS),
|
||||
}
|
||||
}
|
||||
|
||||
// Only implement the methods that MapFS doesn't already provide
|
||||
func (m *mapFS) WriteFile(path string, data []byte, perm fs.FileMode) error {
|
||||
m.MapFS[path] = &fstest.MapFile{
|
||||
Data: data,
|
||||
Mode: perm,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mapFS) Remove(path string) error {
|
||||
delete(m.MapFS, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mapFS) MkdirAll(_ string, _ fs.FileMode) error {
|
||||
// For MapFS, we don't actually need to create directories
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mapFS) RemoveAll(path string) error {
|
||||
delete(m.MapFS, path)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mapFS) IsNotExist(err error) bool {
|
||||
return os.IsNotExist(err)
|
||||
}
|
||||
@@ -1,10 +1,18 @@
|
||||
package filesystem
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"novamd/internal/gitutils"
|
||||
)
|
||||
|
||||
// RepositoryManager defines the interface for managing Git repositories.
|
||||
type RepositoryManager interface {
|
||||
SetupGitRepo(userID, workspaceID int, gitURL, gitUser, gitToken string) error
|
||||
DisableGitRepo(userID, workspaceID int)
|
||||
StageCommitAndPush(userID, workspaceID int, message string) error
|
||||
Pull(userID, workspaceID int) error
|
||||
}
|
||||
|
||||
// SetupGitRepo sets up a Git repository for the given user and workspace IDs.
|
||||
// Parameters:
|
||||
// - userID: the ID of the user who owns the workspace
|
||||
@@ -14,7 +22,7 @@ import (
|
||||
// - gitToken: the access token for the Git repository
|
||||
// Returns:
|
||||
// - error: any error that occurred during setup
|
||||
func (s *Storage) SetupGitRepo(userID, workspaceID int, gitURL, gitUser, gitToken string) error {
|
||||
func (s *Service) SetupGitRepo(userID, workspaceID int, gitURL, gitUser, gitToken string) error {
|
||||
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||
if _, ok := s.GitRepos[userID]; !ok {
|
||||
s.GitRepos[userID] = make(map[int]*gitutils.GitRepo)
|
||||
@@ -27,7 +35,7 @@ func (s *Storage) SetupGitRepo(userID, workspaceID int, gitURL, gitUser, gitToke
|
||||
// Parameters:
|
||||
// - userID: the ID of the user who owns the workspace
|
||||
// - workspaceID: the ID of the workspace to disable the Git repository for
|
||||
func (s *Storage) DisableGitRepo(userID, workspaceID int) {
|
||||
func (s *Service) DisableGitRepo(userID, workspaceID int) {
|
||||
if userRepos, ok := s.GitRepos[userID]; ok {
|
||||
delete(userRepos, workspaceID)
|
||||
if len(userRepos) == 0 {
|
||||
@@ -43,7 +51,7 @@ func (s *Storage) DisableGitRepo(userID, workspaceID int) {
|
||||
// - message: the commit message
|
||||
// Returns:
|
||||
// - error: any error that occurred during the operation
|
||||
func (s *Storage) StageCommitAndPush(userID, workspaceID int, message string) error {
|
||||
func (s *Service) StageCommitAndPush(userID, workspaceID int, message string) error {
|
||||
repo, ok := s.getGitRepo(userID, workspaceID)
|
||||
if !ok {
|
||||
return fmt.Errorf("git settings not configured for this workspace")
|
||||
@@ -62,7 +70,7 @@ func (s *Storage) StageCommitAndPush(userID, workspaceID int, message string) er
|
||||
// - workspaceID: the ID of the workspace to pull changes for
|
||||
// Returns:
|
||||
// - error: any error that occurred during the operation
|
||||
func (s *Storage) Pull(userID, workspaceID int) error {
|
||||
func (s *Service) Pull(userID, workspaceID int) error {
|
||||
repo, ok := s.getGitRepo(userID, workspaceID)
|
||||
if !ok {
|
||||
return fmt.Errorf("git settings not configured for this workspace")
|
||||
@@ -72,7 +80,7 @@ func (s *Storage) Pull(userID, workspaceID int) error {
|
||||
}
|
||||
|
||||
// getGitRepo returns the Git repository for the given user and workspace IDs.
|
||||
func (s *Storage) getGitRepo(userID, workspaceID int) (*gitutils.GitRepo, bool) {
|
||||
func (s *Service) getGitRepo(userID, workspaceID int) (*gitutils.GitRepo, bool) {
|
||||
userRepos, ok := s.GitRepos[userID]
|
||||
if !ok {
|
||||
return nil, false
|
||||
42
server/internal/storage/service.go
Normal file
42
server/internal/storage/service.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"novamd/internal/gitutils"
|
||||
)
|
||||
|
||||
// Manager interface combines all storage interfaces.
|
||||
type Manager interface {
|
||||
FileManager
|
||||
WorkspaceManager
|
||||
RepositoryManager
|
||||
}
|
||||
|
||||
// Service represents the file system structure.
|
||||
type Service struct {
|
||||
fs fileSystem
|
||||
RootDir string
|
||||
GitRepos map[int]map[int]*gitutils.GitRepo // map[userID]map[workspaceID]*gitutils.GitRepo
|
||||
}
|
||||
|
||||
// NewService creates a new Storage instance.
|
||||
// Parameters:
|
||||
// - rootDir: the root directory for the storage
|
||||
// Returns:
|
||||
// - result: the new Storage instance
|
||||
func NewService(rootDir string) *Service {
|
||||
return NewServiceWithFS(rootDir, &osFS{})
|
||||
}
|
||||
|
||||
// NewServiceWithFS creates a new Storage instance with the given filesystem.
|
||||
// Parameters:
|
||||
// - rootDir: the root directory for the storage
|
||||
// - fs: the filesystem implementation to use
|
||||
// Returns:
|
||||
// - result: the new Storage instance
|
||||
func NewServiceWithFS(rootDir string, fs fileSystem) *Service {
|
||||
return &Service{
|
||||
fs: fs,
|
||||
RootDir: rootDir,
|
||||
GitRepos: make(map[int]map[int]*gitutils.GitRepo),
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,46 @@
|
||||
package filesystem
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// WorkspaceManager provides functionalities to interact with workspaces in the storage.
|
||||
type WorkspaceManager interface {
|
||||
ValidatePath(userID, workspaceID int, path string) (string, error)
|
||||
GetWorkspacePath(userID, workspaceID int) string
|
||||
InitializeUserWorkspace(userID, workspaceID int) error
|
||||
DeleteUserWorkspace(userID, workspaceID int) error
|
||||
}
|
||||
|
||||
// ValidatePath validates the given path and returns the cleaned path if it is valid.
|
||||
// Parameters:
|
||||
// - userID: the ID of the user who owns the workspace
|
||||
// - workspaceID: the ID of the workspace to validate the path for
|
||||
// - path: the path to validate
|
||||
// Returns:
|
||||
// - result: the cleaned path if it is valid
|
||||
// - error: any error that occurred during validation
|
||||
func (s *Service) ValidatePath(userID, workspaceID int, path string) (string, error) {
|
||||
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||
fullPath := filepath.Join(workspacePath, path)
|
||||
cleanPath := filepath.Clean(fullPath)
|
||||
|
||||
if !strings.HasPrefix(cleanPath, workspacePath) {
|
||||
return "", fmt.Errorf("invalid path: outside of workspace")
|
||||
}
|
||||
|
||||
return cleanPath, nil
|
||||
}
|
||||
|
||||
// GetWorkspacePath returns the path to the workspace directory for the given user and workspace IDs.
|
||||
// Parameters:
|
||||
// - userID: the ID of the user who owns the workspace
|
||||
// - workspaceID: the ID of the workspace
|
||||
// Returns:
|
||||
// - result: the path to the workspace directory
|
||||
func (s *Storage) GetWorkspacePath(userID, workspaceID int) string {
|
||||
func (s *Service) GetWorkspacePath(userID, workspaceID int) string {
|
||||
return filepath.Join(s.RootDir, fmt.Sprintf("%d", userID), fmt.Sprintf("%d", workspaceID))
|
||||
}
|
||||
|
||||
@@ -21,7 +50,7 @@ func (s *Storage) GetWorkspacePath(userID, workspaceID int) string {
|
||||
// - workspaceID: the ID of the workspace to initialize
|
||||
// Returns:
|
||||
// - error: any error that occurred during the operation
|
||||
func (s *Storage) InitializeUserWorkspace(userID, workspaceID int) error {
|
||||
func (s *Service) InitializeUserWorkspace(userID, workspaceID int) error {
|
||||
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||
err := s.fs.MkdirAll(workspacePath, 0755)
|
||||
if err != nil {
|
||||
@@ -37,7 +66,7 @@ func (s *Storage) InitializeUserWorkspace(userID, workspaceID int) error {
|
||||
// - workspaceID: the ID of the workspace to delete
|
||||
// Returns:
|
||||
// - error: any error that occurred during the operation
|
||||
func (s *Storage) DeleteUserWorkspace(userID, workspaceID int) error {
|
||||
func (s *Service) DeleteUserWorkspace(userID, workspaceID int) error {
|
||||
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||
err := s.fs.RemoveAll(workspacePath)
|
||||
if err != nil {
|
||||
@@ -8,19 +8,19 @@ import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"novamd/internal/db"
|
||||
"novamd/internal/filesystem"
|
||||
"novamd/internal/models"
|
||||
"novamd/internal/storage"
|
||||
)
|
||||
|
||||
type UserService struct {
|
||||
DB *db.DB
|
||||
FS *filesystem.Storage
|
||||
DB *db.DB
|
||||
Storage storage.Manager
|
||||
}
|
||||
|
||||
func NewUserService(database *db.DB, fs *filesystem.Storage) *UserService {
|
||||
func NewUserService(database *db.DB, s storage.Manager) *UserService {
|
||||
return &UserService{
|
||||
DB: database,
|
||||
FS: fs,
|
||||
DB: database,
|
||||
Storage: s,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func (s *UserService) SetupAdminUser(adminEmail, adminPassword string) (*models.
|
||||
}
|
||||
|
||||
// Initialize workspace directory
|
||||
err = s.FS.InitializeUserWorkspace(createdUser.ID, createdUser.LastWorkspaceID)
|
||||
err = s.Storage.InitializeUserWorkspace(createdUser.ID, createdUser.LastWorkspaceID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize admin workspace: %w", err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user