package main import ( "encoding/json" "fmt" "log" "net/http" "os" "time" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "gorm.io/driver/postgres" "gorm.io/gorm" "gorm.io/gorm/logger" ) // Модели для БД type User struct { ID uint `json:"id" gorm:"primarykey"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` DeletedAt gorm.DeletedAt `json:"deleted_at,omitempty" gorm:"index"` Name string `json:"name" gorm:"size:100;not null"` Email string `json:"email" gorm:"size:255;uniqueIndex;not null"` Password string `json:"-" gorm:"size:255;not null"` // Пароль не возвращаем в JSON } type Config struct { DBHost string DBPort string DBUser string DBPassword string DBName string AppPort string } var db *gorm.DB var config Config func main() { // Загрузка конфигурации config = Config{ DBHost: getEnv("DB_HOST", "db"), DBPort: getEnv("DB_PORT", "5432"), DBUser: getEnv("DB_USER", "postgres"), DBPassword: getEnv("DB_PASSWORD", "postgres"), DBName: getEnv("DB_NAME", "mydb"), AppPort: getEnv("APP_PORT", "8081"), } // Инициализация БД if err := initDB(); err != nil { log.Fatal("Failed to connect to database:", err) } // Автомиграция if err := db.AutoMigrate(&User{}); err != nil { log.Fatal("Failed to migrate database:", err) } r := chi.NewRouter() // Стандартные middleware r.Use(middleware.Logger) r.Use(middleware.Recoverer) r.Use(middleware.Timeout(60 * time.Second)) // Маршруты API r.Route("/api", func(r chi.Router) { r.Get("/", handleRoot) r.Get("/users", getUsers) r.Post("/users", createUser) r.Get("/users/{id}", getUser) r.Put("/users/{id}", updateUser) r.Delete("/users/{id}", deleteUser) }) // Health check r.Get("/health", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"status": "healthy"}) }) // Запуск сервера log.Printf("Server starting on port %s", config.AppPort) if err := http.ListenAndServe(":"+config.AppPort, r); err != nil { log.Fatal("Failed to start server:", err) } } func initDB() error { dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=UTC", config.DBHost, config.DBUser, config.DBPassword, config.DBName, config.DBPort) var err error db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{ Logger: logger.Default.LogMode(logger.Info), }) if err != nil { return err } sqlDB, err := db.DB() if err != nil { return err } // Настройка пула соединений sqlDB.SetMaxIdleConns(10) sqlDB.SetMaxOpenConns(100) sqlDB.SetConnMaxLifetime(time.Hour) log.Println("Successfully connected to database") return nil } func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } // Обработчики API func handleRoot(w http.ResponseWriter, r *http.Request) { response := map[string]string{ "message": "EasySite REST API Server", "version": "1.0.0", } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(response) } func getUsers(w http.ResponseWriter, r *http.Request) { var users []User if err := db.Find(&users).Error; err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(users) } func createUser(w http.ResponseWriter, r *http.Request) { var user User if err := json.NewDecoder(r.Body).Decode(&user); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if err := db.Create(&user).Error; err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(user) } func getUser(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") var user User if err := db.First(&user, id).Error; err != nil { if err == gorm.ErrRecordNotFound { http.Error(w, "User not found", http.StatusNotFound) } else { http.Error(w, err.Error(), http.StatusInternalServerError) } return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(user) } func updateUser(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") var user User if err := db.First(&user, id).Error; err != nil { if err == gorm.ErrRecordNotFound { http.Error(w, "User not found", http.StatusNotFound) } else { http.Error(w, err.Error(), http.StatusInternalServerError) } return } var updateData User if err := json.NewDecoder(r.Body).Decode(&updateData); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } if err := db.Model(&user).Updates(updateData).Error; err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(user) } func deleteUser(w http.ResponseWriter, r *http.Request) { id := chi.URLParam(r, "id") var user User if err := db.First(&user, id).Error; err != nil { if err == gorm.ErrRecordNotFound { http.Error(w, "User not found", http.StatusNotFound) } else { http.Error(w, err.Error(), http.StatusInternalServerError) } return } if err := db.Delete(&user).Error; err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{"message": "User deleted successfully"}) }