mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-06 16:04:23 +00:00
Rename filesystem interfaces and structs
This commit is contained in:
@@ -17,8 +17,8 @@ import (
|
|||||||
"novamd/internal/auth"
|
"novamd/internal/auth"
|
||||||
"novamd/internal/config"
|
"novamd/internal/config"
|
||||||
"novamd/internal/db"
|
"novamd/internal/db"
|
||||||
"novamd/internal/filesystem"
|
|
||||||
"novamd/internal/handlers"
|
"novamd/internal/handlers"
|
||||||
|
"novamd/internal/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -45,7 +45,7 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize filesystem
|
// Initialize filesystem
|
||||||
fs := filesystem.New(cfg.WorkDir)
|
s := storage.NewService(cfg.WorkDir)
|
||||||
|
|
||||||
// Initialize JWT service
|
// Initialize JWT service
|
||||||
jwtService, err := auth.NewJWTService(auth.JWTConfig{
|
jwtService, err := auth.NewJWTService(auth.JWTConfig{
|
||||||
@@ -95,7 +95,7 @@ func main() {
|
|||||||
// Set up routes
|
// Set up routes
|
||||||
r.Route("/api/v1", func(r chi.Router) {
|
r.Route("/api/v1", func(r chi.Router) {
|
||||||
r.Use(httprate.LimitByIP(cfg.RateLimitRequests, cfg.RateLimitWindow))
|
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
|
// Handle all other routes with static file server
|
||||||
|
|||||||
@@ -4,19 +4,19 @@ package api
|
|||||||
import (
|
import (
|
||||||
"novamd/internal/auth"
|
"novamd/internal/auth"
|
||||||
"novamd/internal/db"
|
"novamd/internal/db"
|
||||||
"novamd/internal/filesystem"
|
|
||||||
"novamd/internal/handlers"
|
"novamd/internal/handlers"
|
||||||
"novamd/internal/middleware"
|
"novamd/internal/middleware"
|
||||||
|
"novamd/internal/storage"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetupRoutes configures the API routes
|
// 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{
|
handler := &handlers.Handler{
|
||||||
DB: db,
|
DB: db,
|
||||||
S: s,
|
Storage: s,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Public routes (no authentication required)
|
// 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"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"novamd/internal/db"
|
"novamd/internal/db"
|
||||||
"novamd/internal/filesystem"
|
|
||||||
"novamd/internal/httpcontext"
|
"novamd/internal/httpcontext"
|
||||||
"novamd/internal/models"
|
"novamd/internal/models"
|
||||||
|
"novamd/internal/storage"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ func (h *Handler) AdminCreateUser() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize user workspace
|
// 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)
|
http.Error(w, "Failed to initialize user workspace", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -218,7 +218,7 @@ type WorkspaceStats struct {
|
|||||||
WorkspaceID int `json:"workspaceID"`
|
WorkspaceID int `json:"workspaceID"`
|
||||||
WorkspaceName string `json:"workspaceName"`
|
WorkspaceName string `json:"workspaceName"`
|
||||||
WorkspaceCreatedAt time.Time `json:"workspaceCreatedAt"`
|
WorkspaceCreatedAt time.Time `json:"workspaceCreatedAt"`
|
||||||
*filesystem.FileCountStats
|
*storage.FileCountStats
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminListWorkspaces returns a list of all workspaces and their stats
|
// 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.WorkspaceName = ws.Name
|
||||||
workspaceData.WorkspaceCreatedAt = ws.CreatedAt
|
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 {
|
if err != nil {
|
||||||
http.Error(w, "Failed to get file stats", http.StatusInternalServerError)
|
http.Error(w, "Failed to get file stats", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -266,7 +266,7 @@ func (h *Handler) AdminListWorkspaces() http.HandlerFunc {
|
|||||||
// SystemStats holds system-wide statistics
|
// SystemStats holds system-wide statistics
|
||||||
type SystemStats struct {
|
type SystemStats struct {
|
||||||
*db.UserStats
|
*db.UserStats
|
||||||
*filesystem.FileCountStats
|
*storage.FileCountStats
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminGetSystemStats returns system-wide statistics for admins
|
// AdminGetSystemStats returns system-wide statistics for admins
|
||||||
@@ -278,7 +278,7 @@ func (h *Handler) AdminGetSystemStats() http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fileStats, err := h.S.GetTotalFileStats()
|
fileStats, err := h.Storage.GetTotalFileStats()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Failed to get file stats", http.StatusInternalServerError)
|
http.Error(w, "Failed to get file stats", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func (h *Handler) ListFiles() http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
files, err := h.S.ListFilesRecursively(ctx.UserID, ctx.Workspace.ID)
|
files, err := h.Storage.ListFilesRecursively(ctx.UserID, ctx.Workspace.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Failed to list files", http.StatusInternalServerError)
|
http.Error(w, "Failed to list files", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -40,7 +40,7 @@ func (h *Handler) LookupFileByName() http.HandlerFunc {
|
|||||||
return
|
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 {
|
if err != nil {
|
||||||
http.Error(w, "File not found", http.StatusNotFound)
|
http.Error(w, "File not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
@@ -58,7 +58,7 @@ func (h *Handler) GetFileContent() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
filePath := chi.URLParam(r, "*")
|
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 {
|
if err != nil {
|
||||||
http.Error(w, "Failed to read file", http.StatusNotFound)
|
http.Error(w, "Failed to read file", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
@@ -83,7 +83,7 @@ func (h *Handler) SaveFile() http.HandlerFunc {
|
|||||||
return
|
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 {
|
if err != nil {
|
||||||
http.Error(w, "Failed to save file", http.StatusInternalServerError)
|
http.Error(w, "Failed to save file", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -101,7 +101,7 @@ func (h *Handler) DeleteFile() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
filePath := chi.URLParam(r, "*")
|
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 {
|
if err != nil {
|
||||||
http.Error(w, "Failed to delete file", http.StatusInternalServerError)
|
http.Error(w, "Failed to delete file", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -125,7 +125,7 @@ func (h *Handler) GetLastOpenedFile() http.HandlerFunc {
|
|||||||
return
|
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)
|
http.Error(w, "Invalid file path", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -152,7 +152,7 @@ func (h *Handler) UpdateLastOpenedFile() http.HandlerFunc {
|
|||||||
|
|
||||||
// Validate the file path exists in the workspace
|
// Validate the file path exists in the workspace
|
||||||
if requestBody.FilePath != "" {
|
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)
|
http.Error(w, "Invalid file path", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ func (h *Handler) StageCommitAndPush() http.HandlerFunc {
|
|||||||
return
|
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 {
|
if err != nil {
|
||||||
http.Error(w, "Failed to stage, commit, and push changes: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Failed to stage, commit, and push changes: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
@@ -45,7 +45,7 @@ func (h *Handler) PullChanges() http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.S.Pull(ctx.UserID, ctx.Workspace.ID)
|
err := h.Storage.Pull(ctx.UserID, ctx.Workspace.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Failed to pull changes: "+err.Error(), http.StatusInternalServerError)
|
http.Error(w, "Failed to pull changes: "+err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -4,20 +4,20 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"novamd/internal/db"
|
"novamd/internal/db"
|
||||||
"novamd/internal/filesystem"
|
"novamd/internal/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Handler provides common functionality for all handlers
|
// Handler provides common functionality for all handlers
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
DB *db.DB
|
DB *db.DB
|
||||||
S *filesystem.Storage
|
Storage storage.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewHandler creates a new handler with the given dependencies
|
// 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{
|
return &Handler{
|
||||||
DB: db,
|
DB: db,
|
||||||
S: fs,
|
Storage: s,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ func (h *Handler) DeleteAccount() http.HandlerFunc {
|
|||||||
|
|
||||||
// Delete workspace directories
|
// Delete workspace directories
|
||||||
for _, workspace := range workspaces {
|
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)
|
http.Error(w, "Failed to delete workspace files", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ func (h *Handler) CreateWorkspace() http.HandlerFunc {
|
|||||||
return
|
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)
|
http.Error(w, "Failed to initialize workspace directory", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -107,7 +107,7 @@ func (h *Handler) UpdateWorkspace() http.HandlerFunc {
|
|||||||
// Handle Git repository setup/teardown if Git settings changed
|
// Handle Git repository setup/teardown if Git settings changed
|
||||||
if gitSettingsChanged(&workspace, ctx.Workspace) {
|
if gitSettingsChanged(&workspace, ctx.Workspace) {
|
||||||
if workspace.GitEnabled {
|
if workspace.GitEnabled {
|
||||||
if err := h.S.SetupGitRepo(
|
if err := h.Storage.SetupGitRepo(
|
||||||
ctx.UserID,
|
ctx.UserID,
|
||||||
ctx.Workspace.ID,
|
ctx.Workspace.ID,
|
||||||
workspace.GitURL,
|
workspace.GitURL,
|
||||||
@@ -119,7 +119,7 @@ func (h *Handler) UpdateWorkspace() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} 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.
|
// including listing files, finding files by name, getting file content, saving files, and deleting files.
|
||||||
package filesystem
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -10,12 +10,23 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// StorageNode represents a file or directory in the storage.
|
// FileManager provides functionalities to interact with files in the storage.
|
||||||
type StorageNode struct {
|
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"`
|
ID string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
Children []StorageNode `json:"children,omitempty"`
|
Children []FileNode `json:"children,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListFilesRecursively returns a list of all files in the workspace directory and its subdirectories.
|
// ListFilesRecursively returns a list of all files in the workspace directory and its subdirectories.
|
||||||
@@ -25,13 +36,13 @@ type StorageNode struct {
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - nodes: a list of files and directories in the workspace
|
// - nodes: a list of files and directories in the workspace
|
||||||
// - error: any error that occurred during listing
|
// - 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)
|
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||||
return s.walkDirectory(workspacePath, "")
|
return s.walkDirectory(workspacePath, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
// walkDirectory recursively walks the directory and returns a list of files and directories.
|
// 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)
|
entries, err := s.fs.ReadDir(dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -56,7 +67,7 @@ func (s *Storage) walkDirectory(dir, prefix string) ([]StorageNode, error) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Create combined slice with directories first, then files
|
// Create combined slice with directories first, then files
|
||||||
nodes := make([]StorageNode, 0, len(entries))
|
nodes := make([]FileNode, 0, len(entries))
|
||||||
|
|
||||||
// Add directories first
|
// Add directories first
|
||||||
for _, entry := range dirs {
|
for _, entry := range dirs {
|
||||||
@@ -69,7 +80,7 @@ func (s *Storage) walkDirectory(dir, prefix string) ([]StorageNode, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
node := StorageNode{
|
node := FileNode{
|
||||||
ID: path,
|
ID: path,
|
||||||
Name: name,
|
Name: name,
|
||||||
Path: path,
|
Path: path,
|
||||||
@@ -83,7 +94,7 @@ func (s *Storage) walkDirectory(dir, prefix string) ([]StorageNode, error) {
|
|||||||
name := entry.Name()
|
name := entry.Name()
|
||||||
path := filepath.Join(prefix, name)
|
path := filepath.Join(prefix, name)
|
||||||
|
|
||||||
node := StorageNode{
|
node := FileNode{
|
||||||
ID: path,
|
ID: path,
|
||||||
Name: name,
|
Name: name,
|
||||||
Path: path,
|
Path: path,
|
||||||
@@ -102,7 +113,7 @@ func (s *Storage) walkDirectory(dir, prefix string) ([]StorageNode, error) {
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - foundPaths: a list of file paths that match the filename
|
// - foundPaths: a list of file paths that match the filename
|
||||||
// - error: any error that occurred during the search
|
// - 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
|
var foundPaths []string
|
||||||
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||||
|
|
||||||
@@ -141,7 +152,7 @@ func (s *Storage) FindFileByName(userID, workspaceID int, filename string) ([]st
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - content: the content of the file
|
// - content: the content of the file
|
||||||
// - error: any error that occurred during reading
|
// - 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)
|
fullPath, err := s.ValidatePath(userID, workspaceID, filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// - content: the content to write to the file
|
||||||
// Returns:
|
// Returns:
|
||||||
// - error: any error that occurred during saving
|
// - 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)
|
fullPath, err := s.ValidatePath(userID, workspaceID, filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// - filePath: the path of the file to delete
|
||||||
// Returns:
|
// Returns:
|
||||||
// - error: any error that occurred during deletion
|
// - 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)
|
fullPath, err := s.ValidatePath(userID, workspaceID, filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -199,7 +210,7 @@ type FileCountStats struct {
|
|||||||
// Returns:
|
// Returns:
|
||||||
// - result: statistics about the files in the workspace
|
// - result: statistics about the files in the workspace
|
||||||
// - error: any error that occurred during counting
|
// - 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)
|
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||||
|
|
||||||
// Check if workspace exists
|
// 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.
|
// GetTotalFileStats returns the total file statistics for the storage.
|
||||||
// Returns:
|
// Returns:
|
||||||
// - result: statistics about the files in the storage
|
// - 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)
|
return s.countFilesInPath(s.RootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// countFilesInPath counts the total number of files and the total size of files in the given directory.
|
// 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{}
|
result := &FileCountStats{}
|
||||||
|
|
||||||
err := filepath.WalkDir(directoryPath, func(path string, d os.DirEntry, err error) error {
|
err := filepath.WalkDir(directoryPath, func(path string, d os.DirEntry, err error) error {
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package filesystem
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/fs"
|
"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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"novamd/internal/gitutils"
|
"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.
|
// SetupGitRepo sets up a Git repository for the given user and workspace IDs.
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// - userID: the ID of the user who owns the workspace
|
// - userID: the ID of the user who owns the workspace
|
||||||
@@ -14,7 +22,7 @@ import (
|
|||||||
// - gitToken: the access token for the Git repository
|
// - gitToken: the access token for the Git repository
|
||||||
// Returns:
|
// Returns:
|
||||||
// - error: any error that occurred during setup
|
// - 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)
|
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||||
if _, ok := s.GitRepos[userID]; !ok {
|
if _, ok := s.GitRepos[userID]; !ok {
|
||||||
s.GitRepos[userID] = make(map[int]*gitutils.GitRepo)
|
s.GitRepos[userID] = make(map[int]*gitutils.GitRepo)
|
||||||
@@ -27,7 +35,7 @@ func (s *Storage) SetupGitRepo(userID, workspaceID int, gitURL, gitUser, gitToke
|
|||||||
// Parameters:
|
// Parameters:
|
||||||
// - userID: the ID of the user who owns the workspace
|
// - userID: the ID of the user who owns the workspace
|
||||||
// - workspaceID: the ID of the workspace to disable the Git repository for
|
// - 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 {
|
if userRepos, ok := s.GitRepos[userID]; ok {
|
||||||
delete(userRepos, workspaceID)
|
delete(userRepos, workspaceID)
|
||||||
if len(userRepos) == 0 {
|
if len(userRepos) == 0 {
|
||||||
@@ -43,7 +51,7 @@ func (s *Storage) DisableGitRepo(userID, workspaceID int) {
|
|||||||
// - message: the commit message
|
// - message: the commit message
|
||||||
// Returns:
|
// Returns:
|
||||||
// - error: any error that occurred during the operation
|
// - 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)
|
repo, ok := s.getGitRepo(userID, workspaceID)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("git settings not configured for this workspace")
|
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
|
// - workspaceID: the ID of the workspace to pull changes for
|
||||||
// Returns:
|
// Returns:
|
||||||
// - error: any error that occurred during the operation
|
// - 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)
|
repo, ok := s.getGitRepo(userID, workspaceID)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("git settings not configured for this workspace")
|
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.
|
// 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]
|
userRepos, ok := s.GitRepos[userID]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"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.
|
// GetWorkspacePath returns the path to the workspace directory for the given user and workspace IDs.
|
||||||
// Parameters:
|
// Parameters:
|
||||||
// - userID: the ID of the user who owns the workspace
|
// - userID: the ID of the user who owns the workspace
|
||||||
// - workspaceID: the ID of the workspace
|
// - workspaceID: the ID of the workspace
|
||||||
// Returns:
|
// Returns:
|
||||||
// - result: the path to the workspace directory
|
// - 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))
|
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
|
// - workspaceID: the ID of the workspace to initialize
|
||||||
// Returns:
|
// Returns:
|
||||||
// - error: any error that occurred during the operation
|
// - 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)
|
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||||
err := s.fs.MkdirAll(workspacePath, 0755)
|
err := s.fs.MkdirAll(workspacePath, 0755)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -37,7 +66,7 @@ func (s *Storage) InitializeUserWorkspace(userID, workspaceID int) error {
|
|||||||
// - workspaceID: the ID of the workspace to delete
|
// - workspaceID: the ID of the workspace to delete
|
||||||
// Returns:
|
// Returns:
|
||||||
// - error: any error that occurred during the operation
|
// - 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)
|
workspacePath := s.GetWorkspacePath(userID, workspaceID)
|
||||||
err := s.fs.RemoveAll(workspacePath)
|
err := s.fs.RemoveAll(workspacePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -8,19 +8,19 @@ import (
|
|||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
"novamd/internal/db"
|
"novamd/internal/db"
|
||||||
"novamd/internal/filesystem"
|
|
||||||
"novamd/internal/models"
|
"novamd/internal/models"
|
||||||
|
"novamd/internal/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserService struct {
|
type UserService struct {
|
||||||
DB *db.DB
|
DB *db.DB
|
||||||
FS *filesystem.Storage
|
Storage storage.Manager
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewUserService(database *db.DB, fs *filesystem.Storage) *UserService {
|
func NewUserService(database *db.DB, s storage.Manager) *UserService {
|
||||||
return &UserService{
|
return &UserService{
|
||||||
DB: database,
|
DB: database,
|
||||||
FS: fs,
|
Storage: s,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ func (s *UserService) SetupAdminUser(adminEmail, adminPassword string) (*models.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize workspace directory
|
// Initialize workspace directory
|
||||||
err = s.FS.InitializeUserWorkspace(createdUser.ID, createdUser.LastWorkspaceID)
|
err = s.Storage.InitializeUserWorkspace(createdUser.ID, createdUser.LastWorkspaceID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to initialize admin workspace: %w", err)
|
return nil, fmt.Errorf("failed to initialize admin workspace: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user