diff --git a/main_dc/Makefile b/main_dc/Makefile index 059c975..7597b0c 100644 --- a/main_dc/Makefile +++ b/main_dc/Makefile @@ -139,6 +139,9 @@ easysite_build: easysite_start: docker compose up easysite -d && docker ps +# all +easysite: easysite_stop git easysite_build easysite_start easysite_logs + # Мониторинг системных ресурсов top: htop diff --git a/main_dc/yalarba/api_tp/.env b/main_dc/yalarba/api_tp/.env deleted file mode 100644 index 78090e2..0000000 --- a/main_dc/yalarba/api_tp/.env +++ /dev/null @@ -1,7 +0,0 @@ -# DB environment variabels -DB_HOST=localhost -DB_PORT=5432 -DB_USER=postgres -DB_PASSWORD=postgres -DB_NAME=mydb -APP_PORT=8080 \ No newline at end of file diff --git a/main_dc/yalarba/api_tp/Dockerfile b/main_dc/yalarba/api_tp/Dockerfile deleted file mode 100644 index bb8ab94..0000000 --- a/main_dc/yalarba/api_tp/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -# Билд стадия -FROM golang:1.25.1-alpine AS builder - -WORKDIR /app - -# Копируем зависимости -COPY go.mod go.sum ./ -RUN go mod download - -# Копируем исходный код -COPY . . - -# Собираем приложение -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/api - -# Финальная стадия -FROM alpine:latest - -RUN apk --no-cache add ca-certificates - -WORKDIR /root/ - -# Копируем бинарник из builder стадии -COPY --from=builder /app/main . - -# Копируем миграции -COPY --from=builder /app/migrations ./migrations - -# Экспозим порт -EXPOSE 8080 - -# Запускаем приложение -CMD ["./main"] \ No newline at end of file diff --git a/main_dc/yalarba/api_tp/cmd/api/main.go b/main_dc/yalarba/api_tp/cmd/api/main.go deleted file mode 100644 index 4bfe475..0000000 --- a/main_dc/yalarba/api_tp/cmd/api/main.go +++ /dev/null @@ -1,27 +0,0 @@ -package main - -import ( - "log" - "api_tp/internal/config" - "api_tp/internal/server" - "api_tp/pkg/database" -) - -func main() { - // Загрузка конфигурации - cfg := config.Load() - - // Подключение к БД - db, err := database.NewPostgresConnection(cfg) - if err != nil { - log.Fatal("Failed to connect to database:", err) - } - - // Создание и запуск сервера - srv := server.New(db) - - log.Printf("Server starting on port %s", cfg.AppPort) - if err := srv.Run(cfg.AppPort); err != nil { - log.Fatal("Failed to start server:", err) - } -} \ No newline at end of file diff --git a/main_dc/yalarba/api_tp/documentation/docs.md b/main_dc/yalarba/api_tp/documentation/docs.md deleted file mode 100644 index 5fd5ba7..0000000 --- a/main_dc/yalarba/api_tp/documentation/docs.md +++ /dev/null @@ -1,303 +0,0 @@ -# Документация REST API сервиса поиска мест отдыха - -## Общая информация - -### Технологический стек -- **Язык программирования**: Go 1.21+ -- **Веб-фреймворк**: Chi Router -- **ORM**: GORM -- **База данных**: PostgreSQL -- **Аутентификация**: JWT токены - -### Архитектура проекта -Проект построен по принципам чистой архитектуры с разделением на слои: -- **Repository** - работа с базой данных -- **Service** - бизнес-логика -- **Handler** - обработка HTTP запросов -- **Middleware** - промежуточные обработчики - -## Структура проекта -``` -api_tp/ -├── internal/ -│ ├── config/ # Конфигурация приложения -│ ├── handlers/ # HTTP обработчики -│ ├── middleware/ # Промежуточные обработчики -│ ├── models/ # Модели данных -│ ├── repository/ # Репозитории для работы с БД -│ ├── server/ # Конфигурация сервера -│ └── service/ # Бизнес-логика -├── pkg/ -│ └── database/ # Подключение к БД -└── main.go # Точка входа -``` - -## Текущие эндпоинты API - -### Версия API: v1 - -#### 1. Проверка здоровья сервиса -``` -GET /health -GET /v1/check -GET /v1/auth/check -GET /v1/api/users/check -``` -**Ответ:** -```json -{ - "status": "healthy", - "timestamp": "Tue, 02 Jan 2024 10:00:00 UTC" -} -``` - -#### 2. Аутентификация (публичные маршруты) - -##### Регистрация пользователя -``` -POST /v1/auth/register -``` -**Тело запроса (ожидается):** -```json -{ - "email": "user@example.com", - "password": "secure_password", - "name": "Имя пользователя" -} -``` - -##### Авторизация пользователя -``` -POST /v1/auth/login -``` -**Тело запроса (ожидается):** -```json -{ - "email": "user@example.com", - "password": "secure_password" -} -``` - -#### 3. Пользователи (защищенные маршруты - требуется JWT токен) - -##### Получение всех пользователей -``` -GET /v1/api/users/ -``` -**Заголовок:** -``` -Authorization: Bearer -``` - -##### Создание пользователя -``` -POST /v1/api/users/ -``` -**Заголовок:** -``` -Authorization: Bearer -``` - -##### Получение пользователя по ID -``` -GET /v1/api/users/{id} -``` -**Заголовок:** -``` -Authorization: Bearer -``` - -## Модель данных - -### Пользователь (UserT) -```go -type UserT struct { - ID uint `gorm:"primaryKey"` - Email string `gorm:"uniqueIndex;not null"` - Password string `gorm:"not null"` - Name string - CreatedAt time.Time - UpdatedAt time.Time -} -``` - -## Аутентификация -Защищенные маршруты требуют JWT токен, который должен передаваться в заголовке: -``` -Authorization: Bearer <ваш_jwt_токен> -``` - -## Запуск сервиса - -### Требования -1. Go 1.21 или выше -2. PostgreSQL 12+ -3. Установленные переменные окружения (детали в `config` пакете) - -### Запуск -```bash -go run main.go -``` - -Сервер запускается на порту, указанном в конфигурации (по умолчанию 8080). - -## Будущие реализации эндпоинтов - -### 1. Работа с местами отдыха - -#### Категории мест -``` -GET /v1/api/categories # Получение всех категорий -POST /v1/api/categories # Создание категории -GET /v1/api/categories/{id} # Получение категории по ID -PUT /v1/api/categories/{id} # Обновление категории -DELETE /v1/api/categories/{id} # Удаление категории -``` - -#### Сами места отдыха -``` -GET /v1/api/places # Получение всех мест -POST /v1/api/places # Создание места -GET /v1/api/places/{id} # Получение места по ID -PUT /v1/api/places/{id} # Обновление места -DELETE /v1/api/places/{id} # Удаление места -GET /v1/api/places/search # Поиск мест по параметрам -GET /v1/api/places/category/{id} # Места по категории -``` - -### 2. Отзывы и рейтинги - -``` -POST /v1/api/places/{id}/reviews # Добавление отзыва -GET /v1/api/places/{id}/reviews # Получение отзывов места -PUT /v1/api/reviews/{id} # Обновление отзыва -DELETE /v1/api/reviews/{id} # Удаление отзыва -POST /v1/api/places/{id}/rating # Добавление/обновление рейтинга -``` - -### 3. Избранное - -``` -GET /v1/api/user/favorites # Получение избранного пользователя -POST /v1/api/places/{id}/favorite # Добавление в избранное -DELETE /v1/api/places/{id}/favorite # Удаление из избранного -``` - -### 4. Фильтрация и поиск - -``` -GET /v1/api/places/filter # Расширенная фильтрация -``` -**Параметры запроса:** -- `category` - ID категории -- `min_price`, `max_price` - диапазон цен -- `rating` - минимальный рейтинг -- `location` - географическое положение -- `amenities` - удобства (wi-fi, парковка и т.д.) - -### 5. Административные функции - -``` -GET /v1/admin/users # Получение всех пользователей (админ) -PUT /v1/admin/users/{id}/status # Изменение статуса пользователя -GET /v1/admin/places/pending # Места ожидающие модерации -PUT /v1/admin/places/{id}/approve # Одобрение места -DELETE /v1/admin/places/{id} # Удаление места (админ) -``` - -### 6. Статистика и аналитика - -``` -GET /v1/api/analytics/popular-places # Популярные места -GET /v1/api/analytics/user-activity # Активность пользователя -GET /v1/admin/analytics/overview # Общая статистика (админ) -``` - -### 7. Геолокационные функции - -``` -GET /v1/api/places/nearby # Ближайшие места -POST /v1/api/location/suggest # Предложения по локации -``` - -### 8. Медиа-контент - -``` -POST /v1/api/places/{id}/photos # Добавление фотографий -DELETE /v1/api/photos/{id} # Удаление фотографии -GET /v1/api/places/{id}/photos # Получение фотографий места -``` - -## Планируемые модели данных - -### Место отдыха (Place) -```go -type Place struct { - ID uint `gorm:"primaryKey"` - Name string `gorm:"not null"` - Description string - CategoryID uint // Связь с категорией - Address string - Latitude float64 - Longitude float64 - PriceRange string // "низкий", "средний", "высокий" - Rating float64 `gorm:"default:0"` - CreatedBy uint // ID пользователя-создателя - CreatedAt time.Time - UpdatedAt time.Time -} -``` - -### Отзыв (Review) -```go -type Review struct { - ID uint `gorm:"primaryKey"` - PlaceID uint `gorm:"not null"` - UserID uint `gorm:"not null"` - Rating int `gorm:"check:rating >= 1 AND rating <= 5"` - Comment string - CreatedAt time.Time -} -``` - -### Категория (Category) -```go -type Category struct { - ID uint `gorm:"primaryKey"` - Name string `gorm:"uniqueIndex;not null"` - Description string - Icon string // URL иконки -} -``` - -## Безопасность и валидация - -1. **Валидация входных данных** на всех эндпоинтах -2. **Rate limiting** для предотвращения DDoS атак -3. **CORS** настройка для веб-клиентов -4. **Хеширование паролей** с использованием bcrypt -5. **JWT токены** с ограниченным временем жизни -6. **Ролевая модель** (пользователь, модератор, администратор) - -## Мониторинг и логирование - -1. **Structured logging** с использованием zap или logrus -2. **Метрики Prometheus** для мониторинга -3. **Health checks** расширенные -4. **Tracing** с использованием OpenTelemetry - -## Деплоймент - -### Docker -Планируется создание Dockerfile и docker-compose для развертывания: -- Приложение API -- PostgreSQL -- Redis (для кэширования) -- Nginx (как reverse proxy) - -### Kubernetes -Конфигурации для развертывания в Kubernetes кластере. - ---- - -*Примечание: Это предварительная документация. Реализация новых эндпоинтов будет сопровождаться обновлением документации.* \ No newline at end of file diff --git a/main_dc/yalarba/api_tp/go.mod b/main_dc/yalarba/api_tp/go.mod deleted file mode 100644 index 8312445..0000000 --- a/main_dc/yalarba/api_tp/go.mod +++ /dev/null @@ -1,25 +0,0 @@ -module api_tp - -go 1.25.1 - -require ( - github.com/go-chi/chi/v5 v5.2.3 - github.com/go-chi/cors v1.2.2 - github.com/golang-jwt/jwt/v4 v4.5.2 - golang.org/x/crypto v0.43.0 - golang.org/x/oauth2 v0.32.0 - gorm.io/driver/postgres v1.6.0 - gorm.io/gorm v1.31.0 -) - -require ( - cloud.google.com/go/compute/metadata v0.3.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.6.0 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.5 // indirect - golang.org/x/sync v0.17.0 // indirect - golang.org/x/text v0.30.0 // indirect -) diff --git a/main_dc/yalarba/api_tp/go.sum b/main_dc/yalarba/api_tp/go.sum deleted file mode 100644 index 5a82eee..0000000 --- a/main_dc/yalarba/api_tp/go.sum +++ /dev/null @@ -1,46 +0,0 @@ -cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= -cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= -github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= -github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE= -github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= -github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= -github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= -github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= -golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= -golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= -golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= -golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= -golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= -gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= -gorm.io/gorm v1.31.0 h1:0VlycGreVhK7RF/Bwt51Fk8v0xLiiiFdbGDPIZQ7mJY= -gorm.io/gorm v1.31.0/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/main_dc/yalarba/api_tp/internal/config/config.go b/main_dc/yalarba/api_tp/internal/config/config.go deleted file mode 100644 index f1485ff..0000000 --- a/main_dc/yalarba/api_tp/internal/config/config.go +++ /dev/null @@ -1,30 +0,0 @@ -package config - -import "os" - -type Config struct { - DBHost string - DBPort string - DBUser string - DBPassword string - DBName string - AppPort string -} - -func Load() *Config { - return &Config{ - DBHost: getEnv("DB_HOST", "localhost"), - DBPort: getEnv("DB_PORT", "5432"), - DBUser: getEnv("DB_USER", "postgres"), - DBPassword: getEnv("DB_PASSWORD", "postgres"), - DBName: getEnv("DB_NAME", "mydb"), - AppPort: getEnv("APP_PORT", "8080"), - } -} - -func getEnv(key, defaultValue string) string { - if value := os.Getenv(key); value != "" { - return value - } - return defaultValue -} \ No newline at end of file diff --git a/main_dc/yalarba/api_tp/internal/config/oauth.go b/main_dc/yalarba/api_tp/internal/config/oauth.go deleted file mode 100644 index be0d5cb..0000000 --- a/main_dc/yalarba/api_tp/internal/config/oauth.go +++ /dev/null @@ -1,36 +0,0 @@ -// config/oauth.go -package config - -import ( - "golang.org/x/oauth2" - "golang.org/x/oauth2/google" - "golang.org/x/oauth2/yandex" - "golang.org/x/oauth2/vk" -) - - -var ( - GoogleOAuthConfig = &oauth2.Config{ - ClientID: "your-google-client-id", - ClientSecret: "your-google-client-secret", - RedirectURL: "http://localhost:8080/auth/google/callback", - Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"}, - Endpoint: google.Endpoint, - } - - YandexOAuthConfig = &oauth2.Config{ - ClientID: "your-yandex-client-id", - ClientSecret: "your-yandex-client-secret", - RedirectURL: "http://localhost:8080/auth/yandex/callback", - Scopes: []string{"login:email", "login:info", "login:avatar"}, - Endpoint: yandex.Endpoint, - } - - VKOAuthConfig = &oauth2.Config{ - ClientID: "your-vk-client-id", - ClientSecret: "your-vk-client-secret", - RedirectURL: "http://localhost:8080/auth/vk/callback", - Scopes: []string{"email", "photos"}, - Endpoint: vk.Endpoint, - } -) \ No newline at end of file diff --git a/main_dc/yalarba/api_tp/internal/handlers/auth.go b/main_dc/yalarba/api_tp/internal/handlers/auth.go deleted file mode 100644 index 47b4610..0000000 --- a/main_dc/yalarba/api_tp/internal/handlers/auth.go +++ /dev/null @@ -1,104 +0,0 @@ -// handlers/auth.go -package handlers - -import ( - "net/http" - "api_tp/internal/models" - "api_tp/internal/utils" - - "gorm.io/gorm" -) - -type AuthHandler struct { - DB *gorm.DB -} - -type RegisterRequest struct { - Email string `json:"email" validate:"required,email"` - Password string `json:"password" validate:"required,min=6"` - Name string `json:"name" validate:"required"` -} - -type LoginRequest struct { - Email string `json:"email" validate:"required,email"` - Password string `json:"password" validate:"required"` -} - -func (h *AuthHandler) Register(w http.ResponseWriter, r *http.Request) { - var req RegisterRequest - if err := utils.DecodeJSON(r, &req); err != nil { - utils.WriteError(w, http.StatusBadRequest, "Invalid request") - return - } - - // Проверяем, существует ли пользователь - var existingUser models.UserT - if err := h.DB.Where("email = ?", req.Email).First(&existingUser).Error; err == nil { - utils.WriteError(w, http.StatusConflict, "User already exists") - return - } - - // Хешируем пароль - hashedPassword, err := utils.HashPassword(req.Password) - if err != nil { - utils.WriteError(w, http.StatusInternalServerError, "Error creating user") - return - } - - // Создаем пользователя - user := models.UserT{ - Email: req.Email, - Password: hashedPassword, - Name: req.Name, - } - - if err := h.DB.Create(&user).Error; err != nil { - utils.WriteError(w, http.StatusInternalServerError, "Error creating user") - return - } - - // Генерируем JWT токен - token, err := utils.GenerateJWT(user.ID, user.Email) - if err != nil { - utils.WriteError(w, http.StatusInternalServerError, "Error generating token") - return - } - - utils.WriteJSON(w, http.StatusCreated, map[string]interface{}{ - "token": token, - "user": user, - }) -} - -func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { - var req LoginRequest - if err := utils.DecodeJSON(r, &req); err != nil { - utils.WriteError(w, http.StatusBadRequest, "Invalid request") - return - } - - // Ищем пользователя - var user models.UserT - if err := h.DB.Where("email = ?", req.Email).First(&user).Error; err != nil { - utils.WriteError(w, http.StatusUnauthorized, "Invalid credentials") - return - } - - // Проверяем пароль - if !utils.CheckPasswordHash(req.Password, user.Password) { - utils.WriteError(w, http.StatusUnauthorized, "Invalid credentials") - return - } - - // Генерируем JWT токен - token, err := utils.GenerateJWT(user.ID, user.Email) - if err != nil { - utils.WriteError(w, http.StatusInternalServerError, "Error generating token") - return - } - - utils.WriteJSON(w, http.StatusOK, map[string]interface{}{ - "token": token, - "user": user, - }) -} \ No newline at end of file diff --git a/main_dc/yalarba/api_tp/internal/handlers/middleware.go b/main_dc/yalarba/api_tp/internal/handlers/middleware.go deleted file mode 100644 index fad6890..0000000 --- a/main_dc/yalarba/api_tp/internal/handlers/middleware.go +++ /dev/null @@ -1,25 +0,0 @@ -package handlers - -import ( - "net/http" - "time" - - "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.Timeout(60 * time.Second), - 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/main_dc/yalarba/api_tp/internal/handlers/user_handler.go b/main_dc/yalarba/api_tp/internal/handlers/user_handler.go deleted file mode 100644 index 8bf0f3f..0000000 --- a/main_dc/yalarba/api_tp/internal/handlers/user_handler.go +++ /dev/null @@ -1,66 +0,0 @@ -package handlers - -import ( - "encoding/json" - "net/http" - "strconv" - - "github.com/go-chi/chi/v5" - "api_tp/internal/models" - "api_tp/internal/service" -) - -type UserHandler struct { - userService *service.UserService -} - -func NewUserHandler(userService *service.UserService) *UserHandler { - return &UserHandler{userService: userService} -} - -func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) { - var req models.CreateUserRequest - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, "Invalid request body", http.StatusBadRequest) - return - } - - user, err := h.userService.CreateUser(&req) - if err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusCreated) - json.NewEncoder(w).Encode(user) -} - -func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) { - idStr := chi.URLParam(r, "id") - id, err := strconv.ParseUint(idStr, 10, 32) - if err != nil { - http.Error(w, "Invalid user ID", http.StatusBadRequest) - return - } - - user, err := h.userService.GetUserByID(uint(id)) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(user) -} - -func (h *UserHandler) GetAllUsers(w http.ResponseWriter, r *http.Request) { - users, err := h.userService.GetAllUsers() - if err != nil { - http.Error(w, "Internal server error", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(users) -} \ No newline at end of file diff --git a/main_dc/yalarba/api_tp/internal/middleware/auth.go b/main_dc/yalarba/api_tp/internal/middleware/auth.go deleted file mode 100644 index 4bd3307..0000000 --- a/main_dc/yalarba/api_tp/internal/middleware/auth.go +++ /dev/null @@ -1,35 +0,0 @@ -// middleware/auth.go -package middleware - -import ( - "context" - "net/http" - "api_tp/internal/utils" - "strings" -) - -func AuthMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - authHeader := r.Header.Get("Authorization") - if authHeader == "" { - utils.WriteError(w, http.StatusUnauthorized, "Authorization header required") - return - } - - parts := strings.Split(authHeader, " ") - if len(parts) != 2 || parts[0] != "Bearer" { - utils.WriteError(w, http.StatusUnauthorized, "Invalid authorization format") - return - } - - claims, err := utils.ValidateJWT(parts[1]) - if err != nil { - utils.WriteError(w, http.StatusUnauthorized, "Invalid token") - return - } - - // Добавляем claims в контекст - ctx := context.WithValue(r.Context(), "userClaims", claims) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} \ No newline at end of file diff --git a/main_dc/yalarba/api_tp/internal/models/user.go b/main_dc/yalarba/api_tp/internal/models/user.go deleted file mode 100644 index a31bb79..0000000 --- a/main_dc/yalarba/api_tp/internal/models/user.go +++ /dev/null @@ -1,37 +0,0 @@ -package models - -import ( - "time" - - "gorm.io/gorm" -) - -type UserT 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 CreateUserRequest struct { - Name string `json:"name" validate:"required,min=2,max=100"` - Email string `json:"email" validate:"required,email"` - Password string `json:"password" validate:"required,min=6"` -} - -type UpdateUserRequest struct { - Name string `json:"name" validate:"omitempty,min=2,max=100"` - Email string `json:"email" validate:"omitempty,email"` -} - -type UserResponse struct { - ID uint `json:"id"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Name string `json:"name"` - Email string `json:"email"` -} diff --git a/main_dc/yalarba/api_tp/internal/repository/user_repository.go b/main_dc/yalarba/api_tp/internal/repository/user_repository.go deleted file mode 100644 index 9167d86..0000000 --- a/main_dc/yalarba/api_tp/internal/repository/user_repository.go +++ /dev/null @@ -1,45 +0,0 @@ -package repository - -import ( - "api_tp/internal/models" - - "gorm.io/gorm" -) - -type UserRepository struct { - db *gorm.DB -} - -func NewUserRepository(db *gorm.DB) *UserRepository { - return &UserRepository{db: db} -} - -func (r *UserRepository) Create(user *models.UserT) error { - return r.db.Create(user).Error -} - -func (r *UserRepository) FindByID(id uint) (*models.UserT, error) { - var user models.UserT - err := r.db.First(&user, id).Error - return &user, err -} - -func (r *UserRepository) FindByEmail(email string) (*models.UserT, error) { - var user models.UserT - err := r.db.Where("email = ?", email).First(&user).Error - return &user, err -} - -func (r *UserRepository) FindAll() ([]models.UserT, error) { - var users []models.UserT - err := r.db.Find(&users).Error - return users, err -} - -func (r *UserRepository) Update(user *models.UserT) error { - return r.db.Save(user).Error -} - -func (r *UserRepository) Delete(id uint) error { - return r.db.Delete(&models.UserT{}, id).Error -} \ No newline at end of file diff --git a/main_dc/yalarba/api_tp/internal/server/server.go b/main_dc/yalarba/api_tp/internal/server/server.go deleted file mode 100644 index 923feb5..0000000 --- a/main_dc/yalarba/api_tp/internal/server/server.go +++ /dev/null @@ -1,104 +0,0 @@ -package server - -import ( - "encoding/json" - "fmt" - "net/http" - "api_tp/internal/handlers" - "api_tp/internal/middleware" - "api_tp/internal/repository" - "api_tp/internal/service" - "time" - - "github.com/go-chi/chi/v5" - "gorm.io/gorm" -) - -type Server struct { - router *chi.Mux - db *gorm.DB -} - -func New(db *gorm.DB) *Server { - s := &Server{ - router: chi.NewRouter(), - db: db, - } - s.configureRouter(db) - return s -} - -func (s *Server) configureRouter(db *gorm.DB) { - // Общие middleware - for _, middleware := range handlers.CommonMiddleware() { - s.router.Use(middleware) - } - - // Health check - s.router.Get("/health", s.healthCheck) - - // API routes - s.router.Route("/v1", func(r chi.Router) { - r.Get("/check", s.healthCheck) - s.setupUserRoutes(r, db) - }) - - // Для отладки - выводим все маршруты - chi.Walk(s.router, func(method string, route string, handler http.Handler, middlewares ...func(http.Handler) http.Handler) error { - fmt.Printf("[%s] %s\n", method, route) - return nil - }) -} - -func (s *Server) setupUserRoutes(r chi.Router, db *gorm.DB) { - userRepo := repository.NewUserRepository(s.db) - userService := service.NewUserService(userRepo) - userHandler := handlers.NewUserHandler(userService) - - authHandler := &handlers.AuthHandler{DB: db} - - // Публичные маршруты - r.Route("/auth", func(r chi.Router) { - r.Post("/register", authHandler.Register) - r.Post("/login", authHandler.Login) - r.Get("/check", s.healthCheck) - - }) - - // Защищенные маршруты - r.Route("/api", func(r chi.Router) { - r.Use(middleware.AuthMiddleware) - - r.Route("/users", func(r chi.Router) { - r.Get("/", userHandler.GetAllUsers) - r.Post("/", userHandler.CreateUser) - r.Get("/{id}", userHandler.GetUser) - r.Get("/check", s.healthCheck) - }) - - }) -} - -func (s *Server) healthCheck(w http.ResponseWriter, r *http.Request) { - // Проверяем соединение с БД - sqlDB, err := s.db.DB() - if err != nil { - http.Error(w, "Database connection error", http.StatusServiceUnavailable) - return - } - - if err := sqlDB.Ping(); err != nil { - http.Error(w, "Database ping failed", http.StatusServiceUnavailable) - return - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(map[string]string{ - "status": "healthy", - "timestamp": time.Now().UTC().Format(time.RFC1123), - }) -} - -func (s *Server) Run(port string) error { - return http.ListenAndServe(":"+port, s.router) -} diff --git a/main_dc/yalarba/api_tp/internal/service/user_service.go b/main_dc/yalarba/api_tp/internal/service/user_service.go deleted file mode 100644 index 732b0d8..0000000 --- a/main_dc/yalarba/api_tp/internal/service/user_service.go +++ /dev/null @@ -1,77 +0,0 @@ -package service - -import ( - "errors" - "api_tp/internal/models" - "api_tp/internal/repository" - - "golang.org/x/crypto/bcrypt" -) - -type UserService struct { - userRepo *repository.UserRepository -} - -func NewUserService(userRepo *repository.UserRepository) *UserService { - return &UserService{userRepo: userRepo} -} - -func (s *UserService) CreateUser(req *models.CreateUserRequest) (*models.UserResponse, error) { - // Проверяем существует ли пользователь с таким email - existingUser, err := s.userRepo.FindByEmail(req.Email) - // Проверяем как на nil, так на пустой ID - if existingUser != nil && existingUser.ID != 0 { - return nil, errors.New("user with this email already exists") - } - - // Хешируем пароль - hashedPassword, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) - if err != nil { - return nil, err - } - - user := &models.UserT{ - Name: req.Name, - Email: req.Email, - Password: string(hashedPassword), - } - - if err := s.userRepo.Create(user); err != nil { - return nil, err - } - - return s.toUserResponse(user), nil -} - -func (s *UserService) GetUserByID(id uint) (*models.UserResponse, error) { - user, err := s.userRepo.FindByID(id) - if err != nil { - return nil, errors.New("user not found") - } - - return s.toUserResponse(user), nil -} - -func (s *UserService) GetAllUsers() ([]models.UserResponse, error) { - users, err := s.userRepo.FindAll() - if err != nil { - return nil, err - } - - var responses []models.UserResponse - for _, user := range users { - responses = append(responses, *s.toUserResponse(&user)) - } - - return responses, nil -} - -func (s *UserService) toUserResponse(user *models.UserT) *models.UserResponse { - return &models.UserResponse{ - ID: user.ID, - CreatedAt: user.CreatedAt, - UpdatedAt: user.UpdatedAt, - Name: user.Name, - Email: user.Email, - } -} \ No newline at end of file diff --git a/main_dc/yalarba/api_tp/internal/utils/errors.go b/main_dc/yalarba/api_tp/internal/utils/errors.go deleted file mode 100644 index 5bce68b..0000000 --- a/main_dc/yalarba/api_tp/internal/utils/errors.go +++ /dev/null @@ -1,39 +0,0 @@ -// utils/errors.go -package utils - -import ( - "fmt" - "net/http" -) - -// APIError представляет ошибку API -type APIError struct { - Code int `json:"code"` - Message string `json:"message"` -} - -func (e *APIError) Error() string { - return fmt.Sprintf("API Error %d: %s", e.Code, e.Message) -} - -// ErrorResponse представляет стандартный ответ с ошибкой -type ErrorResponse struct { - Error bool `json:"error"` - Message string `json:"message"` - Code int `json:"code"` -} - -// ValidationErrorResponse представляет ответ с ошибками валидации -type ValidationErrorResponse struct { - Error bool `json:"error"` - Message string `json:"message"` - Code int `json:"code"` - Errors map[string]string `json:"errors,omitempty"` -} - -// Predefined errors -var ( - ErrInvalidJSON = &APIError{Code: http.StatusBadRequest, Message: "Invalid JSON"} - ErrEmptyRequestBody = &APIError{Code: http.StatusBadRequest, Message: "Request body is empty"} - ErrRequestBodyTooLarge = &APIError{Code: http.StatusRequestEntityTooLarge, Message: "Request body too large"} -) \ No newline at end of file diff --git a/main_dc/yalarba/api_tp/internal/utils/json.go b/main_dc/yalarba/api_tp/internal/utils/json.go deleted file mode 100644 index 7a61644..0000000 --- a/main_dc/yalarba/api_tp/internal/utils/json.go +++ /dev/null @@ -1,115 +0,0 @@ -// utils/json.go -package utils - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "strings" -) - -// DecodeJSON декодирует JSON из тела запроса с валидацией -func DecodeJSON(r *http.Request, v interface{}) error { - // Ограничиваем размер тела запроса (например, 1MB) - maxBytes := int64(1_048_576) // 1MB - r.Body = http.MaxBytesReader(nil, r.Body, maxBytes) - - decoder := json.NewDecoder(r.Body) - decoder.DisallowUnknownFields() // Запрещаем неизвестные поля - - err := decoder.Decode(v) - if err != nil { - var syntaxError *json.SyntaxError - var unmarshalTypeError *json.UnmarshalTypeError - var invalidUnmarshalError *json.InvalidUnmarshalError - - switch { - case err == io.EOF: - return &APIError{ - Code: http.StatusBadRequest, - Message: "Request body is empty", - } - case err.Error() == "http: request body too large": - return &APIError{ - Code: http.StatusRequestEntityTooLarge, - Message: fmt.Sprintf("Request body must not be larger than %d bytes", maxBytes), - } - case strings.HasPrefix(err.Error(), "json: unknown field"): - fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ") - return &APIError{ - Code: http.StatusBadRequest, - Message: fmt.Sprintf("Unknown field in JSON: %s", fieldName), - } - case errors.As(err, &syntaxError): - return &APIError{ - Code: http.StatusBadRequest, - Message: fmt.Sprintf("Malformed JSON at position %d", syntaxError.Offset), - } - case errors.As(err, &unmarshalTypeError): - return &APIError{ - Code: http.StatusBadRequest, - Message: fmt.Sprintf("Invalid value for field '%s'. Expected type %s", unmarshalTypeError.Field, unmarshalTypeError.Type), - } - case errors.As(err, &invalidUnmarshalError): - return &APIError{ - Code: http.StatusInternalServerError, - Message: "Internal server error", - } - default: - return &APIError{ - Code: http.StatusBadRequest, - Message: "Invalid JSON", - } - } - } - - // Проверяем, что нет лишних данных после JSON - if err = decoder.Decode(&struct{}{}); err != io.EOF { - return &APIError{ - Code: http.StatusBadRequest, - Message: "Request body must contain only single JSON object", - } - } - - return nil -} - -// WriteJSON записывает JSON ответ -func WriteJSON(w http.ResponseWriter, status int, data interface{}) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(status) - - if data == nil { - return nil - } - - encoder := json.NewEncoder(w) - encoder.SetEscapeHTML(true) // Экранируем HTML для безопасности - - return encoder.Encode(data) -} - -// WriteError записывает ошибку в формате JSON -func WriteError(w http.ResponseWriter, status int, message string) { - errorResponse := ErrorResponse{ - Error: true, - Message: message, - Code: status, - } - - WriteJSON(w, status, errorResponse) -} - -// WriteValidationError записывает ошибки валидации -func WriteValidationError(w http.ResponseWriter, errors map[string]string) { - errorResponse := ValidationErrorResponse{ - Error: true, - Message: "Validation failed", - Code: http.StatusBadRequest, - Errors: errors, - } - - WriteJSON(w, http.StatusBadRequest, errorResponse) -} \ No newline at end of file diff --git a/main_dc/yalarba/api_tp/internal/utils/jwt.go b/main_dc/yalarba/api_tp/internal/utils/jwt.go deleted file mode 100644 index c77479c..0000000 --- a/main_dc/yalarba/api_tp/internal/utils/jwt.go +++ /dev/null @@ -1,49 +0,0 @@ -// utils/jwt.go -package utils - -import ( - "time" - "github.com/golang-jwt/jwt/v4" -) - -var jwtSecret = []byte("your-secret-key") // вынеси в env variables - -type Claims struct { - UserID uint `json:"user_id"` - Email string `json:"email"` - jwt.RegisteredClaims -} - -func GenerateJWT(userID uint, email string) (string, error) { - expirationTime := time.Now().Add(24 * time.Hour) - - claims := &Claims{ - UserID: userID, - Email: email, - RegisteredClaims: jwt.RegisteredClaims{ - ExpiresAt: jwt.NewNumericDate(expirationTime), - IssuedAt: jwt.NewNumericDate(time.Now()), - }, - } - - token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) - return token.SignedString(jwtSecret) -} - -func ValidateJWT(tokenString string) (*Claims, error) { - claims := &Claims{} - - token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { - return jwtSecret, nil - }) - - if err != nil { - return nil, err - } - - if !token.Valid { - return nil, jwt.ErrSignatureInvalid - } - - return claims, nil -} \ No newline at end of file diff --git a/main_dc/yalarba/api_tp/internal/utils/password.go b/main_dc/yalarba/api_tp/internal/utils/password.go deleted file mode 100644 index d2cbc03..0000000 --- a/main_dc/yalarba/api_tp/internal/utils/password.go +++ /dev/null @@ -1,29 +0,0 @@ -// utils/password.go -package utils - -import ( - "crypto/rand" - "encoding/base64" - "golang.org/x/crypto/bcrypt" -) - -func HashPassword(password string) (string, error) { - bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) - return string(bytes), err -} - -func CheckPasswordHash(password, hash string) bool { - err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) - return err == nil -} - -// GenerateRandomPassword генерирует случайный пароль для OAuth пользователей -func GenerateRandomPassword() string { - bytes := make([]byte, 32) // 256 бит - _, err := rand.Read(bytes) - if err != nil { - // Fallback - используем временный пароль - return "temp_oauth_password_123" - } - return base64.URLEncoding.EncodeToString(bytes) -} diff --git a/main_dc/yalarba/api_tp/main b/main_dc/yalarba/api_tp/main deleted file mode 100644 index d5b3618..0000000 Binary files a/main_dc/yalarba/api_tp/main and /dev/null differ diff --git a/main_dc/yalarba/api_tp/migrations/001_create_users.sql b/main_dc/yalarba/api_tp/migrations/001_create_users.sql deleted file mode 100644 index 19e641e..0000000 --- a/main_dc/yalarba/api_tp/migrations/001_create_users.sql +++ /dev/null @@ -1,16 +0,0 @@ --- Таблица пользователей -CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - name VARCHAR(100) NOT NULL, - email VARCHAR(255) UNIQUE NOT NULL, - password VARCHAR(255) NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - deleted_at TIMESTAMP WITH TIME ZONE NULL -); - --- Индекс для быстрого поиска по email -CREATE INDEX IF NOT EXISTS idx_users_email ON users(email); - --- Индекс для мягкого удаления -CREATE INDEX IF NOT EXISTS idx_users_deleted_at ON users(deleted_at); \ No newline at end of file diff --git a/main_dc/yalarba/api_tp/pkg/database/postgres.go b/main_dc/yalarba/api_tp/pkg/database/postgres.go deleted file mode 100644 index fb2ad23..0000000 --- a/main_dc/yalarba/api_tp/pkg/database/postgres.go +++ /dev/null @@ -1,37 +0,0 @@ -package database - -import ( - "fmt" - "log" - "api_tp/internal/config" - "api_tp/internal/models" - - "gorm.io/driver/postgres" - "gorm.io/gorm" -) - -func NewPostgresConnection(cfg *config.Config) (*gorm.DB, error) { - dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=disable TimeZone=UTC", - cfg.DBHost, cfg.DBUser, cfg.DBPassword, cfg.DBName, cfg.DBPort) - - db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) - if err != nil { - return nil, fmt.Errorf("failed to connect to database: %w", err) - } - - // Автомиграция - if err := autoMigrate(db); err != nil { - return nil, err - } - - log.Println("Successfully connected to database") - return db, nil -} - -func autoMigrate(db *gorm.DB) error { - // автоматические миграции GORM - return db.AutoMigrate( - &models.UserT{}, - // другие модели... - ) -} \ No newline at end of file