cleanup: удаление ненужных файлов и переименование
- Удален старый main.go - Удален старый Dockerfile - Удален старый Makefile - Удален старый README.md - Удален docker-compose.yml - Удален .air.toml - Удалены пустые директории (deployments, docs, scripts, tests) - Переименованы файлы без суффикса -refactored - Очищена структура проекта
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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"]
|
|
||||||
59
Makefile
59
Makefile
@@ -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 "Структура проекта создана"
|
||||||
|
|||||||
@@ -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 "Структура проекта создана"
|
|
||||||
@@ -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
300
README.md
@@ -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** - конфигурация (планируется)
|
||||||
|
|||||||
@@ -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
292
main.go
@@ -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")
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user