diff --git a/README_GOLANG.md b/README_GOLANG.md index b61153ee..558bf025 100644 --- a/README_GOLANG.md +++ b/README_GOLANG.md @@ -105,6 +105,43 @@ go run cmd/tracks/main.go The application will be available at `http://localhost:3000` +### Default Admin User + +On first startup, the application automatically creates a default admin user: + +- **Username**: `admin` +- **Password**: `admin` + +**Important**: Change the default password immediately after first login! + +To login, make a POST request to `/api/auth/login`: +```bash +curl -X POST http://localhost:3000/api/auth/login \ + -H "Content-Type: application/json" \ + -d '{"login":"admin","password":"admin"}' +``` + +The response will include a JWT token that you can use for authenticated requests. + +### Creating Additional Users + +As an admin, you can create new users via the admin API: + +```bash +curl -X POST http://localhost:3000/api/admin/users \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \ + -d '{ + "login": "newuser", + "password": "password123", + "first_name": "John", + "last_name": "Doe", + "is_admin": false + }' +``` + +Set `"is_admin": true` to grant admin privileges to the new user. + ### Configuration The application can be configured using environment variables. See `.env.example` for all available options. @@ -175,6 +212,35 @@ GET /api/me Authorization: Bearer ``` +### Admin Endpoints (Requires Admin Role) + +#### Create User +```bash +POST /api/admin/users +Authorization: Bearer +Content-Type: application/json + +{ + "login": "newuser", + "password": "password123", + "first_name": "John", + "last_name": "Doe", + "is_admin": false +} +``` + +Response: +```json +{ + "id": 2, + "login": "newuser", + "first_name": "John", + "last_name": "Doe", + "is_admin": false, + "created_at": "2024-01-01T00:00:00Z" +} +``` + ### Todos #### List Todos diff --git a/cmd/tracks/main.go b/cmd/tracks/main.go index 409b602d..34815036 100644 --- a/cmd/tracks/main.go +++ b/cmd/tracks/main.go @@ -48,6 +48,11 @@ func main() { log.Fatal("Failed to run migrations:", err) } + // Create default admin user if no users exist + if err := database.CreateDefaultAdmin(); err != nil { + log.Fatal("Failed to create default admin:", err) + } + // Set Gin mode gin.SetMode(cfg.Server.Mode) @@ -148,6 +153,14 @@ func setupRoutes(router *gin.Engine, cfg *config.Config) { } } + // Admin routes (requires authentication + admin role) + admin := api.Group("/admin") + admin.Use(middleware.AuthMiddleware(cfg.Auth.JWTSecret)) + admin.Use(middleware.AdminMiddleware()) + { + admin.POST("/users", authHandler.CreateUser) + } + // CORS middleware for development router.Use(func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") diff --git a/internal/database/database.go b/internal/database/database.go index 35a4bcfd..709a5073 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -91,3 +91,55 @@ func Close() error { func GetDB() *gorm.DB { return DB } + +// CreateDefaultAdmin creates a default admin user if no users exist +func CreateDefaultAdmin() error { + if DB == nil { + return fmt.Errorf("database not initialized") + } + + // Check if any users exist + var count int64 + if err := DB.Model(&models.User{}).Count(&count).Error; err != nil { + return fmt.Errorf("failed to count users: %w", err) + } + + // If users exist, don't create default admin + if count > 0 { + log.Println("Users already exist, skipping default admin creation") + return nil + } + + log.Println("Creating default admin user (login: admin, password: admin)") + + // Create default admin user + admin := models.User{ + Login: "admin", + FirstName: "Admin", + LastName: "User", + IsAdmin: true, + AuthType: models.AuthTypeDatabase, + Token: "default-admin-token", + } + + // Set password + if err := admin.SetPassword("admin"); err != nil { + return fmt.Errorf("failed to set admin password: %w", err) + } + + // Save admin user + if err := DB.Create(&admin).Error; err != nil { + return fmt.Errorf("failed to create admin user: %w", err) + } + + // Create default preference for admin + preference := models.Preference{ + UserID: admin.ID, + } + if err := DB.Create(&preference).Error; err != nil { + return fmt.Errorf("failed to create admin preference: %w", err) + } + + log.Printf("Default admin user created successfully (ID: %d)", admin.ID) + return nil +} diff --git a/internal/handlers/auth_handler.go b/internal/handlers/auth_handler.go index 34e4b8b3..6962327d 100644 --- a/internal/handlers/auth_handler.go +++ b/internal/handlers/auth_handler.go @@ -94,3 +94,20 @@ func (h *AuthHandler) RefreshToken(c *gin.Context) { c.JSON(http.StatusOK, gin.H{"token": token}) } + +// CreateUser handles POST /api/admin/users (admin only) +func (h *AuthHandler) CreateUser(c *gin.Context) { + var req services.CreateUserRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + user, err := h.authService.CreateUser(req) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, user) +} diff --git a/internal/services/auth_service.go b/internal/services/auth_service.go index f9d1f68d..f4a060d8 100644 --- a/internal/services/auth_service.go +++ b/internal/services/auth_service.go @@ -37,6 +37,15 @@ type RegisterRequest struct { LastName string `json:"last_name"` } +// CreateUserRequest represents an admin user creation request +type CreateUserRequest struct { + Login string `json:"login" binding:"required"` + Password string `json:"password" binding:"required"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + IsAdmin bool `json:"is_admin"` +} + // AuthResponse represents an authentication response type AuthResponse struct { Token string `json:"token"` @@ -146,3 +155,42 @@ func (s *AuthService) RefreshToken(userID uint) (string, error) { return user.Token, nil } + +// CreateUser creates a new user (admin only) +func (s *AuthService) CreateUser(req CreateUserRequest) (*models.User, error) { + // Check if user already exists + var existingUser models.User + if err := database.DB.Where("login = ?", req.Login).First(&existingUser).Error; err == nil { + return nil, errors.New("user already exists") + } + + // Create new user + user := models.User{ + Login: req.Login, + FirstName: req.FirstName, + LastName: req.LastName, + IsAdmin: req.IsAdmin, + AuthType: models.AuthTypeDatabase, + Token: uuid.New().String(), + } + + // Set password + if err := user.SetPassword(req.Password); err != nil { + return nil, err + } + + // Save user + if err := database.DB.Create(&user).Error; err != nil { + return nil, err + } + + // Create default preference + preference := models.Preference{ + UserID: user.ID, + } + if err := database.DB.Create(&preference).Error; err != nil { + return nil, err + } + + return &user, nil +}