diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 9fb8634..58eb9a1 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -39,7 +39,13 @@ func main() { // Initialize user service userService := user.NewUserService(database, fs) - if _, err := userService.SetupAdminUser(); err != nil { + + adminEmail := os.Getenv("NOVAMD_ADMIN_EMAIL") + adminPassword := os.Getenv("NOVAMD_ADMIN_PASSWORD") + if adminEmail == "" || adminPassword == "" { + log.Fatal("NOVAMD_ADMIN_EMAIL and NOVAMD_ADMIN_PASSWORD environment variables must be set") + } + if _, err := userService.SetupAdminUser(adminEmail, adminPassword); err != nil { log.Fatal(err) } diff --git a/backend/internal/api/routes.go b/backend/internal/api/routes.go index af0b1f0..3682ca3 100644 --- a/backend/internal/api/routes.go +++ b/backend/internal/api/routes.go @@ -16,7 +16,7 @@ func SetupRoutes(r chi.Router, db *db.DB, fs *filesystem.FileSystem) { // Workspace routes r.Route("/workspaces", func(r chi.Router) { r.Get("/", ListWorkspaces(db)) - r.Post("/", CreateWorkspace(db)) + r.Post("/", CreateWorkspace(db, fs)) r.Get("/last", GetLastWorkspace(db)) r.Put("/last", UpdateLastWorkspace(db)) diff --git a/backend/internal/api/workspace_handlers.go b/backend/internal/api/workspace_handlers.go index c1c23c6..29415f5 100644 --- a/backend/internal/api/workspace_handlers.go +++ b/backend/internal/api/workspace_handlers.go @@ -27,7 +27,7 @@ func ListWorkspaces(db *db.DB) http.HandlerFunc { } } -func CreateWorkspace(db *db.DB) http.HandlerFunc { +func CreateWorkspace(db *db.DB, fs *filesystem.FileSystem) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { userID, err := getUserID(r) if err != nil { @@ -47,6 +47,11 @@ func CreateWorkspace(db *db.DB) http.HandlerFunc { return } + if err := fs.InitializeUserWorkspace(workspace.UserID, workspace.ID); err != nil { + http.Error(w, "Failed to initialize workspace directory", http.StatusInternalServerError) + return + } + respondJSON(w, workspace) } } diff --git a/backend/internal/filesystem/filesystem.go b/backend/internal/filesystem/filesystem.go index 6e01536..38e29c1 100644 --- a/backend/internal/filesystem/filesystem.go +++ b/backend/internal/filesystem/filesystem.go @@ -6,6 +6,7 @@ import ( "novamd/internal/gitutils" "os" "path/filepath" + "sort" "strings" ) @@ -38,13 +39,6 @@ func (fs *FileSystem) InitializeUserWorkspace(userID, workspaceID int) error { if err != nil { return fmt.Errorf("failed to create workspace directory: %w", err) } - // Optionally, create a welcome file in the new workspace - // welcomeFilePath := filepath.Join(workspacePath, "Welcome.md") - // welcomeContent := []byte("# Welcome to Your Main Workspace\n\nThis is your default workspace in NovaMD. You can start creating and editing files right away!") - // err = os.WriteFile(welcomeFilePath, welcomeContent, 0644) - // if err != nil { - // return fmt.Errorf("failed to create welcome file: %w", err) - // } return nil } @@ -82,26 +76,57 @@ func (fs *FileSystem) walkDirectory(dir, prefix string) ([]FileNode, error) { return nil, err } - nodes := make([]FileNode, 0) + // Split entries into directories and files + var dirs, files []os.DirEntry for _, entry := range entries { + if entry.IsDir() { + dirs = append(dirs, entry) + } else { + files = append(files, entry) + } + } + + // Sort directories and files separately + sort.Slice(dirs, func(i, j int) bool { + return strings.ToLower(dirs[i].Name()) < strings.ToLower(dirs[j].Name()) + }) + sort.Slice(files, func(i, j int) bool { + return strings.ToLower(files[i].Name()) < strings.ToLower(files[j].Name()) + }) + + // Create combined slice with directories first, then files + nodes := make([]FileNode, 0, len(entries)) + + // Add directories first + for _, entry := range dirs { name := entry.Name() path := filepath.Join(prefix, name) fullPath := filepath.Join(dir, name) + children, err := fs.walkDirectory(fullPath, path) + if err != nil { + return nil, err + } + + node := FileNode{ + ID: path, + Name: name, + Path: path, + Children: children, + } + nodes = append(nodes, node) + } + + // Then add files + for _, entry := range files { + name := entry.Name() + path := filepath.Join(prefix, name) + node := FileNode{ ID: path, Name: name, Path: path, } - - if entry.IsDir() { - children, err := fs.walkDirectory(fullPath, path) - if err != nil { - return nil, err - } - node.Children = children - } - nodes = append(nodes, node) } diff --git a/backend/internal/user/user.go b/backend/internal/user/user.go index b725256..6c9e2be 100644 --- a/backend/internal/user/user.go +++ b/backend/internal/user/user.go @@ -3,7 +3,6 @@ package user import ( "fmt" "log" - "os" "golang.org/x/crypto/bcrypt" @@ -24,18 +23,13 @@ func NewUserService(database *db.DB, fs *filesystem.FileSystem) *UserService { } } -func (s *UserService) SetupAdminUser() (*models.User, error) { - // Get admin email and password from environment variables - adminEmail := os.Getenv("NOVAMD_ADMIN_EMAIL") - adminPassword := os.Getenv("NOVAMD_ADMIN_PASSWORD") - if adminEmail == "" || adminPassword == "" { - return nil, fmt.Errorf("NOVAMD_ADMIN_EMAIL and NOVAMD_ADMIN_PASSWORD environment variables must be set") - } - +func (s *UserService) SetupAdminUser(adminEmail, adminPassword string) (*models.User, error) { // Check if admin user exists adminUser, err := s.DB.GetUserByEmail(adminEmail) if adminUser != nil { return adminUser, nil // Admin user already exists + } else if err != nil { + return nil, err } // Hash the password diff --git a/frontend/src/hooks/useFileList.js b/frontend/src/hooks/useFileList.js index ef72c47..ec8ea1f 100644 --- a/frontend/src/hooks/useFileList.js +++ b/frontend/src/hooks/useFileList.js @@ -1,4 +1,4 @@ -import { useState, useCallback, useEffect } from 'react'; +import { useState, useCallback } from 'react'; import { fetchFileList } from '../services/api'; import { useWorkspace } from '../contexts/WorkspaceContext'; @@ -20,7 +20,7 @@ export const useFileList = () => { console.error('Failed to load file list:', error); setFiles([]); } - }, [currentWorkspace]); + }, [currentWorkspace, workspaceLoading]); return { files, loadFileList }; }; diff --git a/frontend/src/hooks/useFileNavigation.js b/frontend/src/hooks/useFileNavigation.js index 23ce637..15928b7 100644 --- a/frontend/src/hooks/useFileNavigation.js +++ b/frontend/src/hooks/useFileNavigation.js @@ -1,4 +1,4 @@ -import { useState, useCallback } from 'react'; +import { useState, useCallback, useEffect } from 'react'; // Added useEffect import { notifications } from '@mantine/notifications'; import { lookupFileByName } from '../services/api'; import { DEFAULT_FILE } from '../utils/constants'; @@ -10,8 +10,8 @@ export const useFileNavigation = () => { const { currentWorkspace } = useWorkspace(); const handleFileSelect = useCallback((filePath) => { - setSelectedFile(filePath); - setIsNewFile(filePath === DEFAULT_FILE.path); + setSelectedFile(filePath || DEFAULT_FILE.path); + setIsNewFile(filePath ? false : true); }, []); const handleLinkClick = useCallback( @@ -38,8 +38,13 @@ export const useFileNavigation = () => { }); } }, - [currentWorkspace] + [currentWorkspace, handleFileSelect] ); + // Reset to default file when workspace changes + useEffect(() => { + handleFileSelect(null); + }, [currentWorkspace, handleFileSelect]); + return { handleLinkClick, selectedFile, isNewFile, handleFileSelect }; };