diff --git a/serv_nginx/api_bb/cmd/main.go b/serv_nginx/api_bb/cmd/main.go new file mode 100644 index 0000000..d868ff6 --- /dev/null +++ b/serv_nginx/api_bb/cmd/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "log" + "net/http" + + "go-rest-api/internal/config" + "go-rest-api/internal/routes" + "go-rest-api/pkg/database" +) + +func main() { + // Load configuration + cfg := config.Load() + + // Initialize database + db, err := database.InitDB(cfg.DatabaseURL) + if err != nil { + log.Fatal("Failed to connect to database:", err) + } + + // Initialize router + router := routes.SetupRouter(db) + + log.Printf("Server starting on port %s", cfg.Port) + log.Fatal(http.ListenAndServe(":"+cfg.Port, router)) +} \ No newline at end of file diff --git a/serv_nginx/api_bb/go.mod b/serv_nginx/api_bb/go.mod new file mode 100644 index 0000000..1dc1d70 --- /dev/null +++ b/serv_nginx/api_bb/go.mod @@ -0,0 +1,16 @@ +module go-rest-api + +go 1.21 + +require ( + github.com/go-chi/chi/v5 v5.0.10 + github.com/go-chi/cors v1.2.1 + gorm.io/driver/sqlite v1.5.4 + gorm.io/gorm v1.25.5 +) + +require ( + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/mattn/go-sqlite3 v1.14.17 // indirect +) diff --git a/serv_nginx/api_bb/go.sum b/serv_nginx/api_bb/go.sum new file mode 100644 index 0000000..db35de8 --- /dev/null +++ b/serv_nginx/api_bb/go.sum @@ -0,0 +1,14 @@ +github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= +github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4= +github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= +github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= +gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= diff --git a/serv_nginx/api_bb/internal/config/config.go b/serv_nginx/api_bb/internal/config/config.go new file mode 100644 index 0000000..8f49bc9 --- /dev/null +++ b/serv_nginx/api_bb/internal/config/config.go @@ -0,0 +1,26 @@ +package config + +import "os" + +type Config struct { + Port string + DatabaseURL string +} + +func Load() *Config { + port := getEnv("PORT", "8080") + databaseURL := getEnv("DATABASE_URL", "sqlite:test.db") + + return &Config{ + Port: port, + DatabaseURL: databaseURL, + } +} + +func getEnv(key, defaultValue string) string { + value := os.Getenv(key) + if value == "" { + return defaultValue + } + return value +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/handlers/auth.go b/serv_nginx/api_bb/internal/handlers/auth.go new file mode 100644 index 0000000..2b44dca --- /dev/null +++ b/serv_nginx/api_bb/internal/handlers/auth.go @@ -0,0 +1,97 @@ +package handlers + +import ( + "encoding/json" + "net/http" + + "github.com/go-chi/chi/v5" + "go-rest-api/internal/models" + "go-rest-api/internal/service" +) + +type AuthHandler struct { + authService service.AuthService +} + +func NewAuthHandler(authService service.AuthService) *AuthHandler { + return &AuthHandler{authService: authService} +} + +func (h *AuthHandler) Routes() chi.Router { + r := chi.NewRouter() + + r.Post("/register", h.Register) + r.Post("/login", h.Login) + r.Get("/check", h.AuthCheck) + + return r +} + +type RegisterRequest struct { + Email string `json:"email"` + Password string `json:"password"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` +} + +type LoginRequest struct { + Email string `json:"email"` + Password string `json:"password"` +} + +func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) { + var req RegisterRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid request payload") + return + } + + user := &models.User{ + Email: req.Email, + Password: req.Password, + FirstName: req.FirstName, + LastName: req.LastName, + Role: "user", + } + + if err := h.authService.Register(user); err != nil { + respondWithError(w, http.StatusBadRequest, err.Error()) + return + } + + respondWithJSON(w, http.StatusCreated, map[string]string{ + "message": "User registered successfully", + }) +} + +func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { + var req LoginRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + respondWithError(w, http.StatusBadRequest, "Invalid request payload") + return + } + + user, err := h.authService.Login(req.Email, req.Password) + if err != nil { + respondWithError(w, http.StatusUnauthorized, err.Error()) + return + } + + respondWithJSON(w, http.StatusOK, map[string]interface{}{ + "message": "Login successful", + "user": map[string]interface{}{ + "id": user.ID, + "email": user.Email, + "first_name": user.FirstName, + "last_name": user.LastName, + "role": user.Role, + }, + }) +} + +func (h *AuthHandler) AuthCheck(w http.ResponseWriter, r *http.Request) { + respondWithJSON(w, http.StatusOK, map[string]string{ + "status": "ok", + "message": "Auth endpoint is working", + }) +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/handlers/common.go b/serv_nginx/api_bb/internal/handlers/common.go new file mode 100644 index 0000000..a25fded --- /dev/null +++ b/serv_nginx/api_bb/internal/handlers/common.go @@ -0,0 +1,16 @@ +package handlers + +import ( + "encoding/json" + "net/http" +) + +func respondWithJSON(w http.ResponseWriter, status int, payload interface{}) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(status) + json.NewEncoder(w).Encode(payload) +} + +func respondWithError(w http.ResponseWriter, status int, message string) { + respondWithJSON(w, status, map[string]string{"error": message}) +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/handlers/handlers.go b/serv_nginx/api_bb/internal/handlers/handlers.go new file mode 100644 index 0000000..50cc1ef --- /dev/null +++ b/serv_nginx/api_bb/internal/handlers/handlers.go @@ -0,0 +1,22 @@ +package handlers + +import ( + "go-rest-api/internal/repository" + "go-rest-api/internal/service" + "gorm.io/gorm" +) + +type Handler struct { + healthHandler *HealthHandler + authHandler *AuthHandler +} + +func NewHandler(db *gorm.DB) *Handler { + userRepo := repository.NewUserRepository(db) + authService := service.NewAuthService(userRepo) + + return &Handler{ + healthHandler: NewHealthHandler(), + authHandler: NewAuthHandler(authService), + } +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/handlers/health.go b/serv_nginx/api_bb/internal/handlers/health.go new file mode 100644 index 0000000..1126256 --- /dev/null +++ b/serv_nginx/api_bb/internal/handlers/health.go @@ -0,0 +1,40 @@ +package handlers + +import ( + "net/http" + + "github.com/go-chi/chi/v5" +) + +type HealthHandler struct{} + +func NewHealthHandler() *HealthHandler { + return &HealthHandler{} +} + +func (h *HealthHandler) Routes() chi.Router { + r := chi.NewRouter() + + r.Get("/health", h.HealthCheck) + r.Get("/check", h.Check) + + return r +} + +func (h *HealthHandler) HealthCheck(w http.ResponseWriter, r *http.Request) { + response := map[string]string{ + "status": "ok", + "message": "Service is healthy", + } + + respondWithJSON(w, http.StatusOK, response) +} + +func (h *HealthHandler) Check(w http.ResponseWriter, r *http.Request) { + response := map[string]string{ + "status": "ok", + "message": "API is working", + } + + respondWithJSON(w, http.StatusOK, response) +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/models/user.go b/serv_nginx/api_bb/internal/models/user.go new file mode 100644 index 0000000..ae5dc4f --- /dev/null +++ b/serv_nginx/api_bb/internal/models/user.go @@ -0,0 +1,23 @@ +package models + +import ( + "time" + "gorm.io/gorm" +) + +type User struct { + gorm.Model + ID uint `gorm:"primaryKey" json:"id"` + Email string `gorm:"uniqueIndex;not null" json:"email"` + Password string `gorm:"not null" json:"-"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Role string `gorm:"default:user" json:"role"` // user, admin + IsActive bool `gorm:"default:true" json:"is_active"` + LastLogin time.Time `json:"last_login"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// Другие модели будут добавлены позже +// type Profile {}, type Event {}, type Review {}, etc. \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/repository/user_repository.go b/serv_nginx/api_bb/internal/repository/user_repository.go new file mode 100644 index 0000000..e285f72 --- /dev/null +++ b/serv_nginx/api_bb/internal/repository/user_repository.go @@ -0,0 +1,46 @@ +package repository + +import ( + "go-rest-api/internal/models" + "gorm.io/gorm" +) + +type UserRepository interface { + Create(user *models.User) error + FindByID(id uint) (*models.User, error) + FindByEmail(email string) (*models.User, error) + Update(user *models.User) error + Delete(id uint) error +} + +type userRepository struct { + db *gorm.DB +} + +func NewUserRepository(db *gorm.DB) UserRepository { + return &userRepository{db: db} +} + +func (r *userRepository) Create(user *models.User) error { + return r.db.Create(user).Error +} + +func (r *userRepository) FindByID(id uint) (*models.User, error) { + var user models.User + err := r.db.First(&user, id).Error + return &user, err +} + +func (r *userRepository) FindByEmail(email string) (*models.User, error) { + var user models.User + err := r.db.Where("email = ?", email).First(&user).Error + return &user, err +} + +func (r *userRepository) Update(user *models.User) error { + return r.db.Save(user).Error +} + +func (r *userRepository) Delete(id uint) error { + return r.db.Delete(&models.User{}, id).Error +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/routes/routes.go b/serv_nginx/api_bb/internal/routes/routes.go new file mode 100644 index 0000000..dd01dcb --- /dev/null +++ b/serv_nginx/api_bb/internal/routes/routes.go @@ -0,0 +1,47 @@ +package routes + +import ( + "net/http" + + "github.com/go-chi/chi/v5" + "gorm.io/gorm" + + "go-rest-api/internal/handlers" + "go-rest-api/internal/repository" + "go-rest-api/internal/service" + "go-rest-api/pkg/middleware" +) + +func SetupRouter(db *gorm.DB) http.Handler { + r := chi.NewRouter() + + // Apply common middleware + for _, m := range middleware.CommonMiddleware() { + r.Use(m) + } + + // Initialize repositories + userRepo := repository.NewUserRepository(db) + + // Initialize services + authService := service.NewAuthService(userRepo) + + // Initialize handlers + healthHandler := handlers.NewHealthHandler() + authHandler := handlers.NewAuthHandler(authService) + + // Health routes + r.Mount("/api", healthHandler.Routes()) + + // API v1 routes + r.Route("/api/v1", func(r chi.Router) { + r.Mount("/auth", authHandler.Routes()) + + // Здесь будут добавлены другие маршруты: + // r.Mount("/users", userHandler.Routes()) + // r.Mount("/events", eventHandler.Routes()) + // r.Mount("/reviews", reviewHandler.Routes()) + }) + + return r +} \ No newline at end of file diff --git a/serv_nginx/api_bb/internal/service/auth_service.go b/serv_nginx/api_bb/internal/service/auth_service.go new file mode 100644 index 0000000..60a5fca --- /dev/null +++ b/serv_nginx/api_bb/internal/service/auth_service.go @@ -0,0 +1,52 @@ +package service + +import ( + "errors" + "go-rest-api/internal/models" + "go-rest-api/internal/repository" +) + +type AuthService interface { + Register(user *models.User) error + Login(email, password string) (*models.User, error) +} + +type authService struct { + userRepo repository.UserRepository +} + +func NewAuthService(userRepo repository.UserRepository) AuthService { + return &authService{userRepo: userRepo} +} + +func (s *authService) Register(user *models.User) error { + // Проверка существования пользователя + existingUser, _ := s.userRepo.FindByEmail(user.Email) + if existingUser != nil { + return errors.New("user already exists") + } + + // Здесь должна быть хеширование пароля + // user.Password = hashPassword(user.Password) + + return s.userRepo.Create(user) +} + +func (s *authService) Login(email, password string) (*models.User, error) { + user, err := s.userRepo.FindByEmail(email) + if err != nil { + return nil, errors.New("invalid credentials") + } + + // Здесь должна быть проверка хеша пароля + // if !checkPasswordHash(password, user.Password) { + // return nil, errors.New("invalid credentials") + // } + + // Временно просто проверяем напрямую (для демо) + if user.Password != password { + return nil, errors.New("invalid credentials") + } + + return user, nil +} \ No newline at end of file diff --git a/serv_nginx/api_bb/pkg/database/database.go b/serv_nginx/api_bb/pkg/database/database.go new file mode 100644 index 0000000..fc2db76 --- /dev/null +++ b/serv_nginx/api_bb/pkg/database/database.go @@ -0,0 +1,25 @@ +package database + +import ( + "go-rest-api/internal/models" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +func InitDB(dsn string) (*gorm.DB, error) { + db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{}) + if err != nil { + return nil, err + } + + // Auto migrate models + err = db.AutoMigrate( + &models.User{}, + // Добавьте другие модели здесь по мере расширения + ) + if err != nil { + return nil, err + } + + return db, nil +} \ No newline at end of file diff --git a/serv_nginx/api_bb/pkg/middleware/middleware.go b/serv_nginx/api_bb/pkg/middleware/middleware.go new file mode 100644 index 0000000..0b8b8d4 --- /dev/null +++ b/serv_nginx/api_bb/pkg/middleware/middleware.go @@ -0,0 +1,24 @@ +package middleware + +import ( + "net/http" + + "github.com/go-chi/chi/v5/middleware" + "github.com/go-chi/cors" +) + +func CommonMiddleware() []func(http.Handler) http.Handler { + return []func(http.Handler) http.Handler{ + middleware.Logger, + middleware.Recoverer, + middleware.RequestID, + cors.Handler(cors.Options{ + AllowedOrigins: []string{"https://*", "http://*"}, + AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, + AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, + ExposedHeaders: []string{"Link"}, + AllowCredentials: false, + MaxAge: 300, + }), + } +} \ No newline at end of file diff --git a/serv_nginx/api_bb/readme.md b/serv_nginx/api_bb/readme.md new file mode 100644 index 0000000..453f054 --- /dev/null +++ b/serv_nginx/api_bb/readme.md @@ -0,0 +1,26 @@ +go-rest-api/ +├── cmd/ +│ └── server/ +│ └── main.go +├── internal/ +│ ├── config/ +│ │ └── config.go +│ ├── handlers/ +│ │ ├── health.go +│ │ ├── auth.go +│ │ └── handlers.go +│ ├── models/ +│ │ └── user.go +│ ├── repository/ +│ │ └── user_repository.go +│ ├── service/ +│ │ └── auth_service.go +│ └── routes/ +│ └── routes.go +├── pkg/ +│ ├── database/ +│ │ └── database.go +│ └── middleware/ +│ └── middleware.go +├── go.mod +└── go.sum \ No newline at end of file diff --git a/serv_nginx/docker-compose.yml b/serv_nginx/docker-compose.yml index bc5b125..945bd27 100644 --- a/serv_nginx/docker-compose.yml +++ b/serv_nginx/docker-compose.yml @@ -81,17 +81,64 @@ services: - postgres_data:/var/lib/postgresql/data - ./migrations:/docker-entrypoint-initdb.d networks: - - app-network + - bb-network healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 10s retries: 5 + api_db: + build: + context: ./api_bb + dockerfile: Dockerfile + ports: + - "7777:8080" + container_name: api_bb + restart: unless-stopped + depends_on: + db: + condition: service_healthy + environment: + # Database connection settings + DB_HOST: db + DB_PORT: 5432 + DB_USER: postgres + DB_PASSWORD: postgres + DB_NAME: mydb + APP_PORT: 8080 + networks: + - bb-network + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:8080/health"] + interval: 30s + timeout: 10s + retries: 3 + + bb_db: + image: postgres:15-alpine + ports: + - "5432:5432" + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: mydb + volumes: + - bb_data:/var/lib/postgresql/data + networks: + - bb_network + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 10s + retries: 5 + + volumes: certbot_data: certbot_www: postgres_data: + bb_data; networks: web-network: @@ -100,3 +147,6 @@ networks: driver: bridge app-network: name: serv_golang_rest_api_app-network + bb_network: + name: begushiy_bashkir_api_network +