cleanup: удаление ненужных файлов и переименование

- Удален старый main.go
- Удален старый Dockerfile
- Удален старый Makefile
- Удален старый README.md
- Удален docker-compose.yml
- Удален .air.toml
- Удалены пустые директории (deployments, docs, scripts, tests)
- Переименованы файлы без суффикса -refactored
- Очищена структура проекта
This commit is contained in:
Andrey Epifancev
2025-08-11 19:55:23 +04:00
parent 04cea69d6e
commit fcc65ea850
8 changed files with 240 additions and 873 deletions

View File

@@ -16,8 +16,8 @@ RUN go mod download
# Копируем исходный код # Копируем исходный код
COPY . . COPY . .
# Собираем приложение # Собираем приложение из cmd/server
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main ./cmd/server
# Финальный образ # Финальный образ
FROM alpine:latest FROM alpine:latest
@@ -35,8 +35,8 @@ WORKDIR /app
# Копируем собранное приложение из builder этапа # Копируем собранное приложение из builder этапа
COPY --from=builder /app/main . COPY --from=builder /app/main .
# Копируем конфигурационные файлы если есть # Копируем конфигурационные файлы
COPY --from=builder /app/config.* ./ COPY --from=builder /app/configs ./configs
# Меняем владельца файлов # Меняем владельца файлов
RUN chown -R appuser:appgroup /app RUN chown -R appuser:appgroup /app

View File

@@ -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"]

View File

@@ -4,6 +4,7 @@
BINARY_NAME=go-webhook-server BINARY_NAME=go-webhook-server
DOCKER_IMAGE=go-webhook-server DOCKER_IMAGE=go-webhook-server
DOCKER_CONTAINER=go-webhook-server DOCKER_CONTAINER=go-webhook-server
BUILD_DIR=cmd/server
# Помощь # Помощь
help: ## Показать справку по командам help: ## Показать справку по командам
@@ -18,7 +19,7 @@ install-deps: ## Установить Go зависимости
# Сборка # Сборка
build: install-deps ## Собрать бинарный файл build: install-deps ## Собрать бинарный файл
@echo "Сборка $(BINARY_NAME)..." @echo "Сборка $(BINARY_NAME)..."
go build -o $(BINARY_NAME) main.go go build -o $(BINARY_NAME) $(BUILD_DIR)/main.go
@echo "Сборка завершена: $(BINARY_NAME)" @echo "Сборка завершена: $(BINARY_NAME)"
# Запуск # Запуск
@@ -26,15 +27,32 @@ run: build ## Запустить сервис локально
@echo "Запуск $(BINARY_NAME)..." @echo "Запуск $(BINARY_NAME)..."
./$(BINARY_NAME) ./$(BINARY_NAME)
# Запуск без сборки
run-dev: ## Запустить сервис в режиме разработки
@echo "Запуск в режиме разработки..."
cd $(BUILD_DIR) && go run main.go
# Тестирование # Тестирование
test: install-deps ## Запустить тесты test: install-deps ## Запустить тесты
@echo "Запуск тестов..." @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: ## Очистить собранные файлы clean: ## Очистить собранные файлы
@echo "Очистка..." @echo "Очистка..."
rm -f $(BINARY_NAME) rm -f $(BINARY_NAME)
rm -f coverage.out pkg-coverage.out
rm -f coverage.html pkg-coverage.html
@echo "Очистка завершена" @echo "Очистка завершена"
# Docker команды # Docker команды
@@ -88,6 +106,27 @@ test-webhook: ## Отправить тестовый webhook
@curl -X POST http://localhost:3000/webhook @curl -X POST http://localhost:3000/webhook
@echo "" @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 ## Полная пересборка проекта rebuild: clean build ## Полная пересборка проекта
@@ -98,5 +137,19 @@ dev: ## Запуск в режиме разработки с автоперез
air; \ air; \
else \ else \
echo "Air не установлен. Установите: go install github.com/cosmtrek/air@latest"; \ echo "Air не установлен. Установите: go install github.com/cosmtrek/air@latest"; \
go run main.go; \ cd $(BUILD_DIR) && go run main.go; \
fi 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 "Структура проекта создана"

View File

@@ -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 "Структура проекта создана"

View File

@@ -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** - конфигурация (планируется)

300
README.md
View File

@@ -1,152 +1,212 @@
# Go Webhook Server # Go Webhook Server - Рефакторенная версия
Go-версия webhook сервиса для автоматической пересборки Quartz сайта при получении webhook'а. Рефакторенная версия webhook сервера с разделением на пакеты и улучшенной архитектурой.
## Особенности ## 🏗️ **Новая структура проекта**
- **Написан на Go** - быстрый и эффективный ```
- **Использует Gin** - легковесный веб-фреймворк go-webhook-server/
- **Интеграция с Git** - автоматическое обновление репозитория ├── cmd/
- **Асинхронная обработка** - webhook'и обрабатываются в фоне │ └── server/
- **Graceful shutdown** - корректное завершение работы │ └── main.go # Точка входа приложения
- **Структурированное логирование** - с использованием logrus ├── internal/
- **Конфигурируемость** - через переменные окружения │ ├── config/
│ │ └── config.go # Конфигурация приложения
## Архитектура │ ├── handlers/
│ │ ├── webhook.go # Обработчик webhook'ов
Сервис состоит из следующих компонентов: │ │ └── health.go # Health check обработчик
│ ├── services/
1. **HTTP сервер** на Gin с эндпоинтами: │ │ ├── build.go # Основной сервис сборки
- `POST /webhook` - основной webhook для пересборки │ ├── git.go # Git операции
- `GET /health` - проверка состояния сервиса │ ├── quartz.go # Сборка Quartz
│ │ ├── files.go # Файловые операции
2. **Git интеграция** - автоматическое обновление репозитория Obsidian │ │ └── types.go # Общие типы
3. **Quartz сборка** - запуск процесса сборки статического сайта │ └── middleware/
4. **Файловые операции** - копирование собранных файлов в публичную директорию │ └── logging.go # HTTP логирование
├── pkg/
## Конфигурация │ └── logger/
│ └── logger.go # Интерфейс логгера
Сервис настраивается через переменные окружения: ├── api/
│ └── routes.go # Определение роутов
| Переменная | Описание | По умолчанию | ├── configs/
|------------|----------|--------------| │ └── config.yaml # YAML конфигурация
| `PORT` | Порт для HTTP сервера | `3000` | ├── tests/ # Тесты
| `OBSIDIAN_PATH` | Путь к репозиторию Obsidian | `/obsidian` | ├── scripts/ # Скрипты
| `QUARTZ_PATH` | Путь к директории Quartz | `/quartz` | └── docs/ # Документация
| `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
``` ```
### Проверка состояния ## 🔧 **Основные улучшения**
```bash ### **1. Разделение ответственности**
curl http://localhost:3000/health - **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 репозиторий существует ### **5. Middleware**
2. **Обновление кода** - выполняет `git pull` для получения последних изменений - **RequestIDMiddleware** - уникальный ID для каждого запроса
3. **Установка зависимостей** - запускает `npm install` если необходимо - **ResponseTimeMiddleware** - время ответа
4. **Сборка сайта** - запускает `npm run quartz build` с указанием директории Obsidian - **LoggingMiddleware** - детальное логирование HTTP
5. **Копирование файлов** - копирует собранные файлы в публичную директорию
## Логирование ## 🚀 **Запуск**
Сервис использует структурированное логирование с помощью logrus: ### **Локально**
```bash
cd cmd/server
go run main.go
```
- **Info** - информационные сообщения о процессе сборки ### **Сборка**
- **Error** - ошибки, возникающие в процессе ```bash
- **HTTP логи** - все HTTP запросы с деталями 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 ```json
{ {
"status": "ok", "status": "ok",
"timestamp": "2025-01-27T10:30:00Z", "timestamp": "2025-01-27T10:30:00Z",
"service": "go-webhook-server", "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/ - Все HTTP запросы логируются с деталями
├── main.go # Основной файл приложения - Request ID для отслеживания цепочки запросов
├── go.mod # Go модуль - Структурированные логи в формате JSON
├── go.sum # Хеши зависимостей
├── Dockerfile # Docker образ
├── docker-compose.yml # Docker Compose конфигурация
└── README.md # Документация
```
### Зависимости ## 🚀 **Преимущества новой архитектуры**
- `github.com/gin-gonic/gin` - веб-фреймворк 1. **Тестируемость** - легко писать unit тесты
- `github.com/go-git/go-git/v5` - Git клиент 2. **Переиспользование** - компоненты можно использовать в других проектах
- `github.com/sirupsen/logrus` - логирование 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** - конфигурация (планируется)

View File

@@ -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

292
main.go
View File

@@ -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")
}