From fcc65ea8504b7549cfd198780a54c77f0656b704 Mon Sep 17 00:00:00 2001 From: Andrey Epifancev Date: Mon, 11 Aug 2025 19:55:23 +0400 Subject: [PATCH] =?UTF-8?q?cleanup:=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BD=D0=B5=D0=BD=D1=83=D0=B6=D0=BD=D1=8B=D1=85?= =?UTF-8?q?=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2=20=D0=B8=20=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B8=D0=BC=D0=B5=D0=BD=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Удален старый main.go - Удален старый Dockerfile - Удален старый Makefile - Удален старый README.md - Удален docker-compose.yml - Удален .air.toml - Удалены пустые директории (deployments, docs, scripts, tests) - Переименованы файлы без суффикса -refactored - Очищена структура проекта --- Dockerfile | 8 +- Dockerfile-refactored | 51 ------- Makefile | 59 ++++++++- Makefile-refactored | 155 ---------------------- README-REFACTORED.md | 212 ----------------------------- README.md | 300 +++++++++++++++++++++++++----------------- docker-compose.yml | 36 ----- main.go | 292 ---------------------------------------- 8 files changed, 240 insertions(+), 873 deletions(-) delete mode 100644 Dockerfile-refactored delete mode 100644 Makefile-refactored delete mode 100644 README-REFACTORED.md delete mode 100644 docker-compose.yml delete mode 100644 main.go diff --git a/Dockerfile b/Dockerfile index 6d8cc90..10b7ef0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,8 +16,8 @@ RUN go mod download # Копируем исходный код COPY . . -# Собираем приложение -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . +# Собираем приложение из cmd/server +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/server # Финальный образ FROM alpine:latest @@ -35,8 +35,8 @@ WORKDIR /app # Копируем собранное приложение из builder этапа COPY --from=builder /app/main . -# Копируем конфигурационные файлы если есть -COPY --from=builder /app/config.* ./ +# Копируем конфигурационные файлы +COPY --from=builder /app/configs ./configs # Меняем владельца файлов RUN chown -R appuser:appgroup /app diff --git a/Dockerfile-refactored b/Dockerfile-refactored deleted file mode 100644 index 10b7ef0..0000000 --- a/Dockerfile-refactored +++ /dev/null @@ -1,51 +0,0 @@ -# Многоэтапная сборка для Go приложения -FROM golang:1.21-alpine AS builder - -# Устанавливаем необходимые пакеты для сборки -RUN apk add --no-cache git ca-certificates tzdata - -# Устанавливаем рабочую директорию -WORKDIR /app - -# Копируем go mod файлы -COPY go.mod go.sum ./ - -# Скачиваем зависимости -RUN go mod download - -# Копируем исходный код -COPY . . - -# Собираем приложение из cmd/server -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/server - -# Финальный образ -FROM alpine:latest - -# Устанавливаем необходимые пакеты для работы -RUN apk --no-cache add ca-certificates git tzdata - -# Создаем пользователя для безопасности -RUN addgroup -g 1001 -S appgroup && \ - adduser -u 1001 -S appuser -G appgroup - -# Устанавливаем рабочую директорию -WORKDIR /app - -# Копируем собранное приложение из builder этапа -COPY --from=builder /app/main . - -# Копируем конфигурационные файлы -COPY --from=builder /app/configs ./configs - -# Меняем владельца файлов -RUN chown -R appuser:appgroup /app - -# Переключаемся на непривилегированного пользователя -USER appuser - -# Открываем порт -EXPOSE 3000 - -# Запускаем приложение -CMD ["./main"] diff --git a/Makefile b/Makefile index d72945b..10a77da 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ BINARY_NAME=go-webhook-server DOCKER_IMAGE=go-webhook-server DOCKER_CONTAINER=go-webhook-server +BUILD_DIR=cmd/server # Помощь help: ## Показать справку по командам @@ -18,7 +19,7 @@ install-deps: ## Установить Go зависимости # Сборка build: install-deps ## Собрать бинарный файл @echo "Сборка $(BINARY_NAME)..." - go build -o $(BINARY_NAME) main.go + go build -o $(BINARY_NAME) $(BUILD_DIR)/main.go @echo "Сборка завершена: $(BINARY_NAME)" # Запуск @@ -26,15 +27,32 @@ run: build ## Запустить сервис локально @echo "Запуск $(BINARY_NAME)..." ./$(BINARY_NAME) +# Запуск без сборки +run-dev: ## Запустить сервис в режиме разработки + @echo "Запуск в режиме разработки..." + cd $(BUILD_DIR) && go run main.go + # Тестирование test: install-deps ## Запустить тесты @echo "Запуск тестов..." - go test -v ./... + go test -v ./internal/... + go test -v ./pkg/... + +# Тестирование с покрытием +test-coverage: install-deps ## Запустить тесты с покрытием + @echo "Запуск тестов с покрытием..." + go test -v -coverprofile=coverage.out ./internal/... + go test -v -coverprofile=pkg-coverage.out ./pkg/... + go tool cover -html=coverage.out -o coverage.html + go tool cover -html=pkg-coverage.out -o pkg-coverage.html + @echo "Отчеты покрытия созданы: coverage.html, pkg-coverage.html" # Очистка clean: ## Очистить собранные файлы @echo "Очистка..." rm -f $(BINARY_NAME) + rm -f coverage.out pkg-coverage.out + rm -f coverage.html pkg-coverage.html @echo "Очистка завершена" # Docker команды @@ -88,6 +106,27 @@ test-webhook: ## Отправить тестовый webhook @curl -X POST http://localhost:3000/webhook @echo "" +# Проверка структуры +lint: ## Проверить код линтером + @echo "Проверка кода..." + @if command -v golangci-lint > /dev/null; then \ + golangci-lint run; \ + else \ + echo "golangci-lint не установлен. Установите: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest"; \ + fi + +# Форматирование кода +fmt: ## Отформатировать код + @echo "Форматирование кода..." + go fmt ./... + @echo "Код отформатирован" + +# Проверка зависимостей +deps-check: ## Проверить зависимости + @echo "Проверка зависимостей..." + go mod verify + go list -m all + # Полная пересборка rebuild: clean build ## Полная пересборка проекта @@ -98,5 +137,19 @@ dev: ## Запуск в режиме разработки с автоперез air; \ else \ echo "Air не установлен. Установите: go install github.com/cosmtrek/air@latest"; \ - go run main.go; \ + cd $(BUILD_DIR) && go run main.go; \ fi + +# Создание структуры проекта +create-structure: ## Создать структуру директорий + @echo "Создание структуры проекта..." + mkdir -p cmd/server + mkdir -p internal/{config,handlers,services,middleware} + mkdir -p pkg/logger + mkdir -p api + mkdir -p configs + mkdir -p tests/{unit,integration} + mkdir -p scripts + mkdir -p docs + mkdir -p deployments + @echo "Структура проекта создана" diff --git a/Makefile-refactored b/Makefile-refactored deleted file mode 100644 index 10a77da..0000000 --- a/Makefile-refactored +++ /dev/null @@ -1,155 +0,0 @@ -.PHONY: help build run test clean docker-build docker-run docker-stop install-deps - -# Переменные -BINARY_NAME=go-webhook-server -DOCKER_IMAGE=go-webhook-server -DOCKER_CONTAINER=go-webhook-server -BUILD_DIR=cmd/server - -# Помощь -help: ## Показать справку по командам - @echo "Доступные команды:" - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' - -# Установка зависимостей -install-deps: ## Установить Go зависимости - go mod download - go mod tidy - -# Сборка -build: install-deps ## Собрать бинарный файл - @echo "Сборка $(BINARY_NAME)..." - go build -o $(BINARY_NAME) $(BUILD_DIR)/main.go - @echo "Сборка завершена: $(BINARY_NAME)" - -# Запуск -run: build ## Запустить сервис локально - @echo "Запуск $(BINARY_NAME)..." - ./$(BINARY_NAME) - -# Запуск без сборки -run-dev: ## Запустить сервис в режиме разработки - @echo "Запуск в режиме разработки..." - cd $(BUILD_DIR) && go run main.go - -# Тестирование -test: install-deps ## Запустить тесты - @echo "Запуск тестов..." - go test -v ./internal/... - go test -v ./pkg/... - -# Тестирование с покрытием -test-coverage: install-deps ## Запустить тесты с покрытием - @echo "Запуск тестов с покрытием..." - go test -v -coverprofile=coverage.out ./internal/... - go test -v -coverprofile=pkg-coverage.out ./pkg/... - go tool cover -html=coverage.out -o coverage.html - go tool cover -html=pkg-coverage.out -o pkg-coverage.html - @echo "Отчеты покрытия созданы: coverage.html, pkg-coverage.html" - -# Очистка -clean: ## Очистить собранные файлы - @echo "Очистка..." - rm -f $(BINARY_NAME) - rm -f coverage.out pkg-coverage.out - rm -f coverage.html pkg-coverage.html - @echo "Очистка завершена" - -# Docker команды -docker-build: ## Собрать Docker образ - @echo "Сборка Docker образа..." - docker build -t $(DOCKER_IMAGE) . - @echo "Docker образ собран: $(DOCKER_IMAGE)" - -docker-run: docker-build ## Запустить Docker контейнер - @echo "Запуск Docker контейнера..." - docker run -d \ - --name $(DOCKER_CONTAINER) \ - -p 3000:3000 \ - -v obsidian_repo:/obsidian:ro \ - -v quartz_repo:/quartz:ro \ - -v public_site:/public \ - $(DOCKER_IMAGE) - @echo "Docker контейнер запущен: $(DOCKER_CONTAINER)" - -docker-stop: ## Остановить Docker контейнер - @echo "Остановка Docker контейнера..." - docker stop $(DOCKER_CONTAINER) || true - docker rm $(DOCKER_CONTAINER) || true - @echo "Docker контейнер остановлен" - -docker-logs: ## Показать логи Docker контейнера - docker logs -f $(DOCKER_CONTAINER) - -# Docker Compose команды -compose-up: ## Запустить сервис через Docker Compose - @echo "Запуск через Docker Compose..." - docker-compose up -d - @echo "Сервис запущен" - -compose-down: ## Остановить сервис через Docker Compose - @echo "Остановка через Docker Compose..." - docker-compose down - @echo "Сервис остановлен" - -compose-logs: ## Показать логи Docker Compose - docker-compose logs -f - -# Проверка состояния -status: ## Показать статус сервиса - @echo "Проверка статуса сервиса..." - @curl -s http://localhost:3000/health || echo "Сервис недоступен" - -# Webhook тест -test-webhook: ## Отправить тестовый webhook - @echo "Отправка тестового webhook..." - @curl -X POST http://localhost:3000/webhook - @echo "" - -# Проверка структуры -lint: ## Проверить код линтером - @echo "Проверка кода..." - @if command -v golangci-lint > /dev/null; then \ - golangci-lint run; \ - else \ - echo "golangci-lint не установлен. Установите: go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest"; \ - fi - -# Форматирование кода -fmt: ## Отформатировать код - @echo "Форматирование кода..." - go fmt ./... - @echo "Код отформатирован" - -# Проверка зависимостей -deps-check: ## Проверить зависимости - @echo "Проверка зависимостей..." - go mod verify - go list -m all - -# Полная пересборка -rebuild: clean build ## Полная пересборка проекта - -# Разработка -dev: ## Запуск в режиме разработки с автоперезагрузкой - @echo "Запуск в режиме разработки..." - @if command -v air > /dev/null; then \ - air; \ - else \ - echo "Air не установлен. Установите: go install github.com/cosmtrek/air@latest"; \ - cd $(BUILD_DIR) && go run main.go; \ - fi - -# Создание структуры проекта -create-structure: ## Создать структуру директорий - @echo "Создание структуры проекта..." - mkdir -p cmd/server - mkdir -p internal/{config,handlers,services,middleware} - mkdir -p pkg/logger - mkdir -p api - mkdir -p configs - mkdir -p tests/{unit,integration} - mkdir -p scripts - mkdir -p docs - mkdir -p deployments - @echo "Структура проекта создана" diff --git a/README-REFACTORED.md b/README-REFACTORED.md deleted file mode 100644 index 1b10fc4..0000000 --- a/README-REFACTORED.md +++ /dev/null @@ -1,212 +0,0 @@ -# Go Webhook Server - Рефакторенная версия - -Рефакторенная версия webhook сервера с разделением на пакеты и улучшенной архитектурой. - -## 🏗️ **Новая структура проекта** - -``` -go-webhook-server/ -├── cmd/ -│ └── server/ -│ └── main.go # Точка входа приложения -├── internal/ -│ ├── config/ -│ │ └── config.go # Конфигурация приложения -│ ├── handlers/ -│ │ ├── webhook.go # Обработчик webhook'ов -│ │ └── health.go # Health check обработчик -│ ├── services/ -│ │ ├── build.go # Основной сервис сборки -│ │ ├── git.go # Git операции -│ │ ├── quartz.go # Сборка Quartz -│ │ ├── files.go # Файловые операции -│ │ └── types.go # Общие типы -│ └── middleware/ -│ └── logging.go # HTTP логирование -├── pkg/ -│ └── logger/ -│ └── logger.go # Интерфейс логгера -├── api/ -│ └── routes.go # Определение роутов -├── configs/ -│ └── config.yaml # YAML конфигурация -├── tests/ # Тесты -├── scripts/ # Скрипты -└── docs/ # Документация -``` - -## 🔧 **Основные улучшения** - -### **1. Разделение ответственности** -- **Config** - управление конфигурацией -- **Services** - бизнес-логика -- **Handlers** - HTTP обработчики -- **Middleware** - промежуточное ПО -- **API** - определение роутов - -### **2. Интерфейсы и абстракции** -```go -type GitService interface { - UpdateRepository() error - IsRepositoryExists() bool -} - -type QuartzService interface { - BuildSite() error - InstallDependencies() error -} - -type BuildService interface { - BuildSite() BuildResult -} -``` - -### **3. Dependency Injection** -```go -// Инициализация сервисов -gitService := services.NewGitService(cfg, log) -quartzService := services.NewQuartzService(cfg, log) -fileService := services.NewFileService(cfg, log) -buildService := services.NewBuildService(cfg, log, gitService, quartzService, fileService) -``` - -### **4. Улучшенное логирование** -- Структурированное логирование -- Request ID для отслеживания -- Уровни логирования -- Контекстная информация - -### **5. Middleware** -- **RequestIDMiddleware** - уникальный ID для каждого запроса -- **ResponseTimeMiddleware** - время ответа -- **LoggingMiddleware** - детальное логирование HTTP - -## 🚀 **Запуск** - -### **Локально** -```bash -cd cmd/server -go run main.go -``` - -### **Сборка** -```bash -go build -o go-webhook-server cmd/server/main.go -``` - -### **Docker** -```bash -docker build -t go-webhook-server . -docker run -p 3000:3000 go-webhook-server -``` - -## 📝 **Конфигурация** - -### **Переменные окружения** -```bash -PORT=3000 -OBSIDIAN_PATH=/obsidian -QUARTZ_PATH=/quartz -PUBLIC_PATH=/public -GIT_BRANCH=main -GIT_REMOTE=origin -SERVER_TIMEOUT=30 -``` - -### **YAML конфигурация** -```yaml -server: - port: "3000" - timeout: 30 - -paths: - obsidian: "/obsidian" - quartz: "/quartz" - public: "/public" - -git: - branch: "main" - remote: "origin" -``` - -## 🧪 **Тестирование** - -### **Unit тесты** -```bash -go test ./internal/... -``` - -### **Integration тесты** -```bash -go test ./tests/integration/... -``` - -## 🔄 **API эндпоинты** - -### **POST /webhook** -Запускает процесс сборки сайта. - -**Ответ:** -```json -{ - "status": "accepted", - "message": "Build process started", - "request_id": "20250127103000-abc123", - "timestamp": "2025-01-27T10:30:00Z" -} -``` - -### **GET /health** -Проверка состояния сервиса. - -**Ответ:** -```json -{ - "status": "ok", - "timestamp": "2025-01-27T10:30:00Z", - "service": "go-webhook-server", - "version": "1.0.0", - "request_id": "20250127103000-abc123" -} -``` - -## 📊 **Мониторинг** - -### **Заголовки ответа** -- `X-Request-ID` - уникальный ID запроса -- `X-Response-Time` - время ответа - -### **Логирование** -- Все HTTP запросы логируются с деталями -- Request ID для отслеживания цепочки запросов -- Структурированные логи в формате JSON - -## 🚀 **Преимущества новой архитектуры** - -1. **Тестируемость** - легко писать unit тесты -2. **Переиспользование** - компоненты можно использовать в других проектах -3. **Читаемость** - код легче понимать и поддерживать -4. **Расширяемость** - проще добавлять новую функциональность -5. **Соответствие стандартам** - структура соответствует Go best practices -6. **Dependency Injection** - легко заменять реализации -7. **Интерфейсы** - четкое разделение контрактов - -## 🔮 **Планы развития** - -- [ ] Добавление метрик Prometheus -- [ ] Конфигурация через файлы -- [ ] Graceful shutdown для сборки -- [ ] Очередь сборок -- [ ] Уведомления о результатах -- [ ] API для мониторинга сборок -- [ ] Аутентификация webhook'ов -- [ ] Rate limiting -- [ ] OpenAPI документация - -## 📚 **Зависимости** - -- **Go 1.20+** -- **Gin** - веб-фреймворк -- **go-git/v5** - Git клиент -- **logrus** - логирование -- **YAML** - конфигурация (планируется) diff --git a/README.md b/README.md index 41d5bcc..1b10fc4 100644 --- a/README.md +++ b/README.md @@ -1,152 +1,212 @@ -# Go Webhook Server +# Go Webhook Server - Рефакторенная версия -Go-версия webhook сервиса для автоматической пересборки Quartz сайта при получении webhook'а. +Рефакторенная версия webhook сервера с разделением на пакеты и улучшенной архитектурой. -## Особенности +## 🏗️ **Новая структура проекта** -- **Написан на Go** - быстрый и эффективный -- **Использует Gin** - легковесный веб-фреймворк -- **Интеграция с Git** - автоматическое обновление репозитория -- **Асинхронная обработка** - webhook'и обрабатываются в фоне -- **Graceful shutdown** - корректное завершение работы -- **Структурированное логирование** - с использованием logrus -- **Конфигурируемость** - через переменные окружения - -## Архитектура - -Сервис состоит из следующих компонентов: - -1. **HTTP сервер** на Gin с эндпоинтами: - - `POST /webhook` - основной webhook для пересборки - - `GET /health` - проверка состояния сервиса - -2. **Git интеграция** - автоматическое обновление репозитория Obsidian -3. **Quartz сборка** - запуск процесса сборки статического сайта -4. **Файловые операции** - копирование собранных файлов в публичную директорию - -## Конфигурация - -Сервис настраивается через переменные окружения: - -| Переменная | Описание | По умолчанию | -|------------|----------|--------------| -| `PORT` | Порт для HTTP сервера | `3000` | -| `OBSIDIAN_PATH` | Путь к репозиторию Obsidian | `/obsidian` | -| `QUARTZ_PATH` | Путь к директории Quartz | `/quartz` | -| `PUBLIC_PATH` | Путь к публичной директории | `/public` | -| `GIT_BRANCH` | Ветка Git для обновления | `main` | -| `GIT_REMOTE` | Имя удаленного репозитория | `origin` | - -## Запуск - -### Локально - -1. Установите Go 1.21+ -2. Скачайте зависимости: - ```bash - go mod download - ``` -3. Запустите сервис: - ```bash - go run main.go - ``` - -### В Docker - -1. Соберите образ: - ```bash - docker build -t go-webhook-server . - ``` - -2. Запустите контейнер: - ```bash - docker run -d \ - --name go-webhook-server \ - -p 3000:3000 \ - -v obsidian_repo:/obsidian:ro \ - -v quartz_repo:/quartz:ro \ - -v public_site:/public \ - go-webhook-server - ``` - -### С Docker Compose - -1. Запустите сервис: - ```bash - docker-compose up -d - ``` - -## Использование - -### Отправка webhook'а - -```bash -curl -X POST http://localhost:3000/webhook +``` +go-webhook-server/ +├── cmd/ +│ └── server/ +│ └── main.go # Точка входа приложения +├── internal/ +│ ├── config/ +│ │ └── config.go # Конфигурация приложения +│ ├── handlers/ +│ │ ├── webhook.go # Обработчик webhook'ов +│ │ └── health.go # Health check обработчик +│ ├── services/ +│ │ ├── build.go # Основной сервис сборки +│ │ ├── git.go # Git операции +│ │ ├── quartz.go # Сборка Quartz +│ │ ├── files.go # Файловые операции +│ │ └── types.go # Общие типы +│ └── middleware/ +│ └── logging.go # HTTP логирование +├── pkg/ +│ └── logger/ +│ └── logger.go # Интерфейс логгера +├── api/ +│ └── routes.go # Определение роутов +├── configs/ +│ └── config.yaml # YAML конфигурация +├── tests/ # Тесты +├── scripts/ # Скрипты +└── docs/ # Документация ``` -### Проверка состояния +## 🔧 **Основные улучшения** -```bash -curl http://localhost:3000/health +### **1. Разделение ответственности** +- **Config** - управление конфигурацией +- **Services** - бизнес-логика +- **Handlers** - HTTP обработчики +- **Middleware** - промежуточное ПО +- **API** - определение роутов + +### **2. Интерфейсы и абстракции** +```go +type GitService interface { + UpdateRepository() error + IsRepositoryExists() bool +} + +type QuartzService interface { + BuildSite() error + InstallDependencies() error +} + +type BuildService interface { + BuildSite() BuildResult +} ``` -## Процесс сборки +### **3. Dependency Injection** +```go +// Инициализация сервисов +gitService := services.NewGitService(cfg, log) +quartzService := services.NewQuartzService(cfg, log) +fileService := services.NewFileService(cfg, log) +buildService := services.NewBuildService(cfg, log, gitService, quartzService, fileService) +``` -При получении webhook'а сервис выполняет: +### **4. Улучшенное логирование** +- Структурированное логирование +- Request ID для отслеживания +- Уровни логирования +- Контекстная информация -1. **Проверка репозитория** - убеждается что Git репозиторий существует -2. **Обновление кода** - выполняет `git pull` для получения последних изменений -3. **Установка зависимостей** - запускает `npm install` если необходимо -4. **Сборка сайта** - запускает `npm run quartz build` с указанием директории Obsidian -5. **Копирование файлов** - копирует собранные файлы в публичную директорию +### **5. Middleware** +- **RequestIDMiddleware** - уникальный ID для каждого запроса +- **ResponseTimeMiddleware** - время ответа +- **LoggingMiddleware** - детальное логирование HTTP -## Логирование +## 🚀 **Запуск** -Сервис использует структурированное логирование с помощью logrus: +### **Локально** +```bash +cd cmd/server +go run main.go +``` -- **Info** - информационные сообщения о процессе сборки -- **Error** - ошибки, возникающие в процессе -- **HTTP логи** - все HTTP запросы с деталями +### **Сборка** +```bash +go build -o go-webhook-server cmd/server/main.go +``` -## Безопасность +### **Docker** +```bash +docker build -t go-webhook-server . +docker run -p 3000:3000 go-webhook-server +``` -- Контейнер запускается от непривилегированного пользователя -- Используется Alpine Linux для минимального attack surface -- Graceful shutdown для корректного завершения работы +## 📝 **Конфигурация** -## Мониторинг +### **Переменные окружения** +```bash +PORT=3000 +OBSIDIAN_PATH=/obsidian +QUARTZ_PATH=/quartz +PUBLIC_PATH=/public +GIT_BRANCH=main +GIT_REMOTE=origin +SERVER_TIMEOUT=30 +``` -Сервис предоставляет health check эндпоинт для мониторинга: +### **YAML конфигурация** +```yaml +server: + port: "3000" + timeout: 30 +paths: + obsidian: "/obsidian" + quartz: "/quartz" + public: "/public" + +git: + branch: "main" + remote: "origin" +``` + +## 🧪 **Тестирование** + +### **Unit тесты** +```bash +go test ./internal/... +``` + +### **Integration тесты** +```bash +go test ./tests/integration/... +``` + +## 🔄 **API эндпоинты** + +### **POST /webhook** +Запускает процесс сборки сайта. + +**Ответ:** +```json +{ + "status": "accepted", + "message": "Build process started", + "request_id": "20250127103000-abc123", + "timestamp": "2025-01-27T10:30:00Z" +} +``` + +### **GET /health** +Проверка состояния сервиса. + +**Ответ:** ```json { "status": "ok", "timestamp": "2025-01-27T10:30:00Z", "service": "go-webhook-server", - "version": "1.0.0" + "version": "1.0.0", + "request_id": "20250127103000-abc123" } ``` -## Разработка +## 📊 **Мониторинг** -### Структура проекта +### **Заголовки ответа** +- `X-Request-ID` - уникальный ID запроса +- `X-Response-Time` - время ответа -``` -go-webhook-server/ -├── main.go # Основной файл приложения -├── go.mod # Go модуль -├── go.sum # Хеши зависимостей -├── Dockerfile # Docker образ -├── docker-compose.yml # Docker Compose конфигурация -└── README.md # Документация -``` +### **Логирование** +- Все HTTP запросы логируются с деталями +- Request ID для отслеживания цепочки запросов +- Структурированные логи в формате JSON -### Зависимости +## 🚀 **Преимущества новой архитектуры** -- `github.com/gin-gonic/gin` - веб-фреймворк -- `github.com/go-git/go-git/v5` - Git клиент -- `github.com/sirupsen/logrus` - логирование +1. **Тестируемость** - легко писать unit тесты +2. **Переиспользование** - компоненты можно использовать в других проектах +3. **Читаемость** - код легче понимать и поддерживать +4. **Расширяемость** - проще добавлять новую функциональность +5. **Соответствие стандартам** - структура соответствует Go best practices +6. **Dependency Injection** - легко заменять реализации +7. **Интерфейсы** - четкое разделение контрактов -## Лицензия +## 🔮 **Планы развития** -MIT +- [ ] Добавление метрик Prometheus +- [ ] Конфигурация через файлы +- [ ] Graceful shutdown для сборки +- [ ] Очередь сборок +- [ ] Уведомления о результатах +- [ ] API для мониторинга сборок +- [ ] Аутентификация webhook'ов +- [ ] Rate limiting +- [ ] OpenAPI документация + +## 📚 **Зависимости** + +- **Go 1.20+** +- **Gin** - веб-фреймворк +- **go-git/v5** - Git клиент +- **logrus** - логирование +- **YAML** - конфигурация (планируется) diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index ed155cd..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,36 +0,0 @@ -version: '3.8' - -services: - go-webhook-server: - build: . - container_name: go-webhook-server - ports: - - "3000:3000" - environment: - - PORT=3000 - - OBSIDIAN_PATH=/obsidian - - QUARTZ_PATH=/quartz - - PUBLIC_PATH=/public - - GIT_BRANCH=main - - GIT_REMOTE=origin - volumes: - - obsidian_repo:/obsidian:ro - - quartz_repo:/quartz:ro - - public_site:/public - restart: unless-stopped - networks: - - webhook-network - -volumes: - obsidian_repo: - external: true - name: obsidian_repo - quartz_repo: - external: true - name: quartz_repo - public_site: - name: public_site - -networks: - webhook-network: - driver: bridge diff --git a/main.go b/main.go deleted file mode 100644 index a850ca0..0000000 --- a/main.go +++ /dev/null @@ -1,292 +0,0 @@ -package main - -import ( - "context" - "fmt" - "net/http" - "os" - "os/exec" - "path/filepath" - "time" - - "github.com/gin-gonic/gin" - "github.com/go-git/go-git/v5" - "github.com/sirupsen/logrus" -) - -type Config struct { - Port string - ObsidianPath string - QuartzPath string - PublicPath string - GitBranch string - GitRemote string -} - -type BuildResult struct { - Success bool `json:"success"` - Message string `json:"message"` - Error string `json:"error,omitempty"` -} - -var ( - config Config - logger *logrus.Logger -) - -func init() { - // Инициализация логгера - logger = logrus.New() - logger.SetFormatter(&logrus.TextFormatter{ - FullTimestamp: true, - }) - logger.SetLevel(logrus.InfoLevel) - - // Загрузка конфигурации - config = Config{ - Port: getEnv("PORT", "3000"), - ObsidianPath: getEnv("OBSIDIAN_PATH", "/obsidian"), - QuartzPath: getEnv("QUARTZ_PATH", "/quartz"), - PublicPath: getEnv("PUBLIC_PATH", "/public"), - GitBranch: getEnv("GIT_BRANCH", "main"), - GitRemote: getEnv("GIT_REMOTE", "origin"), - } - - logger.Infof("Configuration loaded: Port=%s, ObsidianPath=%s, QuartzPath=%s, PublicPath=%s, GitBranch=%s, GitRemote=%s", - config.Port, config.ObsidianPath, config.QuartzPath, config.PublicPath, config.GitBranch, config.GitRemote) -} - -func getEnv(key, defaultValue string) string { - if value := os.Getenv(key); value != "" { - return value - } - return defaultValue -} - -func buildSite() BuildResult { - logger.Info("Starting site build process...") - - // Проверяем существование репозитория - if _, err := os.Stat(filepath.Join(config.ObsidianPath, ".git")); os.IsNotExist(err) { - logger.Error("Repository not found") - return BuildResult{ - Success: false, - Message: "Repository not found", - Error: "Git repository does not exist at specified path", - } - } - - // Обновляем репозиторий - if err := updateRepository(); err != nil { - logger.Errorf("Failed to update repository: %v", err) - return BuildResult{ - Success: false, - Message: "Failed to update repository", - Error: err.Error(), - } - } - - // Собираем сайт с помощью Quartz - if err := buildQuartzSite(); err != nil { - logger.Errorf("Failed to build Quartz site: %v", err) - return BuildResult{ - Success: false, - Message: "Failed to build Quartz site", - Error: err.Error(), - } - } - - // Копируем собранные файлы в публичную директорию - if err := copyBuiltSite(); err != nil { - logger.Errorf("Failed to copy built site: %v", err) - return BuildResult{ - Success: false, - Message: "Failed to copy built site", - Error: err.Error(), - } - } - - logger.Info("Site built successfully!") - return BuildResult{ - Success: true, - Message: "Site built successfully", - } -} - -func updateRepository() error { - logger.Info("Updating repository...") - - // Открываем репозиторий - repo, err := git.PlainOpen(config.ObsidianPath) - if err != nil { - return fmt.Errorf("failed to open repository: %w", err) - } - - // Получаем worktree - worktree, err := repo.Worktree() - if err != nil { - return fmt.Errorf("failed to get worktree: %w", err) - } - - // Выполняем git pull - err = worktree.Pull(&git.PullOptions{ - RemoteName: config.GitRemote, - }) - if err != nil && err != git.NoErrAlreadyUpToDate { - return fmt.Errorf("failed to pull from remote: %w", err) - } - - if err == git.NoErrAlreadyUpToDate { - logger.Info("Repository is already up to date") - } else { - logger.Info("Repository updated successfully") - } - - return nil -} - -func buildQuartzSite() error { - logger.Info("Building site with Quartz...") - - // Проверяем существование package.json в директории Quartz - packageJSONPath := filepath.Join(config.QuartzPath, "package.json") - if _, err := os.Stat(packageJSONPath); os.IsNotExist(err) { - return fmt.Errorf("package.json not found in Quartz directory") - } - - // Выполняем npm install если node_modules не существует - nodeModulesPath := filepath.Join(config.QuartzPath, "node_modules") - if _, err := os.Stat(nodeModulesPath); os.IsNotExist(err) { - logger.Info("Installing npm dependencies...") - cmd := exec.Command("npm", "install") - cmd.Dir = config.QuartzPath - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to install npm dependencies: %w", err) - } - } - - // Выполняем сборку Quartz - cmd := exec.Command("npm", "run", "quartz", "build", "--", "-d", config.ObsidianPath) - cmd.Dir = config.QuartzPath - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to build Quartz site: %w", err) - } - - logger.Info("Quartz build completed successfully") - return nil -} - -func copyBuiltSite() error { - logger.Info("Copying built site to public directory...") - - // Очищаем публичную директорию - if err := os.RemoveAll(config.PublicPath); err != nil { - return fmt.Errorf("failed to clear public directory: %w", err) - } - - // Создаем публичную директорию заново - if err := os.MkdirAll(config.PublicPath, 0755); err != nil { - return fmt.Errorf("failed to create public directory: %w", err) - } - - // Копируем собранные файлы - quartzPublicPath := filepath.Join(config.QuartzPath, "public") - if _, err := os.Stat(quartzPublicPath); os.IsNotExist(err) { - return fmt.Errorf("Quartz public directory not found") - } - - // Используем cp команду для копирования - cmd := exec.Command("cp", "-r", quartzPublicPath+"/.", config.PublicPath+"/") - if err := cmd.Run(); err != nil { - return fmt.Errorf("failed to copy built site: %w", err) - } - - logger.Info("Site files copied successfully") - return nil -} - -func webhookHandler(c *gin.Context) { - logger.Info("Webhook received, starting site rebuild...") - - // Запускаем сборку в горутине для асинхронной обработки - go func() { - result := buildSite() - if result.Success { - logger.Info("Webhook build completed successfully") - } else { - logger.Errorf("Webhook build failed: %s", result.Error) - } - }() - - // Сразу возвращаем ответ - c.JSON(http.StatusAccepted, gin.H{ - "status": "accepted", - "message": "Build process started", - }) -} - -func healthHandler(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "status": "ok", - "timestamp": time.Now().UTC().Format(time.RFC3339), - "service": "go-webhook-server", - "version": "1.0.0", - }) -} - -func main() { - // Настраиваем Gin - gin.SetMode(gin.ReleaseMode) - router := gin.Default() - - // Добавляем middleware для логирования - router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { - logger.WithFields(logrus.Fields{ - "status": param.StatusCode, - "latency": param.Latency, - "client_ip": param.ClientIP, - "method": param.Method, - "path": param.Path, - "user_agent": param.Request.UserAgent(), - }).Info("HTTP Request") - return "" - })) - - // Роуты - router.POST("/webhook", webhookHandler) - router.GET("/health", healthHandler) - - // Запуск сервера - addr := ":" + config.Port - logger.Infof("Starting webhook server on port %s", config.Port) - - server := &http.Server{ - Addr: addr, - Handler: router, - } - - // Graceful shutdown - go func() { - if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - logger.Fatalf("Failed to start server: %v", err) - } - }() - - // Ожидаем сигнал для graceful shutdown - <-make(chan os.Signal, 1) - logger.Info("Shutting down server...") - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - if err := server.Shutdown(ctx); err != nil { - logger.Errorf("Server forced to shutdown: %v", err) - } - - logger.Info("Server exited") -}