diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 3cdf6d3..d78ea94 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -13,6 +13,7 @@ import ( "novamd/internal/api" "novamd/internal/db" "novamd/internal/filesystem" + "novamd/internal/user" ) func main() { @@ -39,6 +40,12 @@ func main() { fs := filesystem.New(workdir) + // User service + userService := user.NewUserService(database, fs) + + // Admin user + userService.SetupAdminUser() + // Set up router r := chi.NewRouter() r.Use(middleware.Logger) diff --git a/backend/internal/db/migrations.go b/backend/internal/db/migrations.go index a99b30a..c6d234f 100644 --- a/backend/internal/db/migrations.go +++ b/backend/internal/db/migrations.go @@ -20,6 +20,7 @@ var migrations = []Migration{ email TEXT NOT NULL UNIQUE, display_name TEXT, password_hash TEXT NOT NULL, + role TEXT NOT NULL CHECK(role IN ('admin', 'editor', 'viewer')), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_workspace_id INTEGER, last_opened_file_path TEXT diff --git a/backend/internal/user/user.go b/backend/internal/user/user.go new file mode 100644 index 0000000..2f64e0d --- /dev/null +++ b/backend/internal/user/user.go @@ -0,0 +1,122 @@ +package user + +import ( + "fmt" + "log" + "os" + + "golang.org/x/crypto/bcrypt" + + "novamd/internal/db" + "novamd/internal/filesystem" + "novamd/internal/models" +) + +type UserService struct { + DB *db.DB + FS *filesystem.FileSystem +} + +func NewUserService(database *db.DB, fs *filesystem.FileSystem) *UserService { + return &UserService{ + DB: database, + FS: fs, + } +} + +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") + } + + // Check if admin user exists + adminUser, err := s.DB.GetUserByEmail(adminEmail) + if err == nil { + return adminUser, nil // Admin user already exists + } + + // Hash the password + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(adminPassword), bcrypt.DefaultCost) + if err != nil { + return nil, fmt.Errorf("failed to hash password: %w", err) + } + + // Create admin user + adminUser = &models.User{ + Email: adminEmail, + DisplayName: "Admin", + PasswordHash: string(hashedPassword), + Role: models.RoleAdmin, + } + + err = s.DB.CreateUser(adminUser) + if err != nil { + return nil, fmt.Errorf("failed to create admin user: %w", err) + } + + err = s.FS.InitializeUserWorkspace(adminUser.ID, adminUser.LastWorkspaceID) + if err != nil { + return nil, fmt.Errorf("failed to initialize admin workspace: %w", err) + } + + log.Printf("Created admin user with ID: %d and default workspace with ID: %d", adminUser.ID, adminUser.LastWorkspaceID) + + return adminUser, nil +} + +func (s *UserService) CreateUser(user *models.User) error { + err := s.DB.CreateUser(user) + if err != nil { + return fmt.Errorf("failed to create user: %w", err) + } + + err = s.FS.InitializeUserWorkspace(user.ID, user.LastWorkspaceID) + if err != nil { + return fmt.Errorf("failed to initialize user workspace: %w", err) + } + + return nil +} + +func (s *UserService) GetUserByID(id int) (*models.User, error) { + return s.DB.GetUserByID(id) +} + +func (s *UserService) GetUserByEmail(email string) (*models.User, error) { + return s.DB.GetUserByEmail(email) +} + +func (s *UserService) UpdateUser(user *models.User) error { + return s.DB.UpdateUser(user) +} + +func (s *UserService) DeleteUser(id int) error { + // First, get the user to check if they exist and to get their workspaces + user, err := s.DB.GetUserByID(id) + if err != nil { + return fmt.Errorf("failed to get user: %w", err) + } + + // Delete user's workspaces + workspaces, err := s.DB.GetWorkspacesByUserID(id) + if err != nil { + return fmt.Errorf("failed to get user's workspaces: %w", err) + } + + for _, workspace := range workspaces { + err = s.DB.DeleteWorkspace(workspace.ID) + if err != nil { + return fmt.Errorf("failed to delete workspace: %w", err) + } + err = s.FS.DeleteUserWorkspace(user.ID, workspace.ID) + if err != nil { + return fmt.Errorf("failed to delete workspace files: %w", err) + } + } + + // Finally, delete the user + return s.DB.DeleteUser(id) +}