Skip to content

Commit 0b48350

Browse files
committed
feat: add basic structure
Signed-off-by: Vladislav Byrgazov <vlados.byrgazov@gmail.com>
1 parent 3146524 commit 0b48350

18 files changed

Lines changed: 496 additions & 0 deletions

File tree

.devcontainer/Dockerfile

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
FROM mcr.microsoft.com/devcontainers/go:dev-1.25-trixie
2+
3+
SHELL ["/bin/bash", "-c"]
4+
5+
RUN apt-get update && apt-get -y install --no-install-recommends \
6+
bash \
7+
make \
8+
git \
9+
curl \
10+
ca-certificates \
11+
findutils \
12+
coreutils
13+
14+
RUN curl -sSfL https://taskfile.dev/install.sh | sh -s -- -b /usr/local/bin

.devcontainer/devcontainer.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"name": "test-backend",
3+
"build": {
4+
"dockerfile": "Dockerfile"
5+
},
6+
"postCreateCommand": "go version && task --version && task install-golangci-lint && task install-formatters"
7+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/bin/bash
2+
3+
# Скрипт для извлечения версий инструментов из Taskfile.yml
4+
# Использование: .github/scripts/extract-versions.sh
5+
6+
# Путь к Taskfile.yml
7+
TASKFILE="Taskfile.yml"
8+
9+
# Проверка наличия файла
10+
if [ ! -f "$TASKFILE" ]; then
11+
echo "Ошибка: Файл $TASKFILE не найден" >&2
12+
exit 1
13+
fi
14+
15+
# Извлечение всех переменных из секции vars
16+
echo "Извлекаем переменные из Taskfile.yml:"
17+
18+
# Определяем начало и конец секции vars
19+
VARS_START=$(grep -n "^vars:" "$TASKFILE" | cut -d: -f1)
20+
if [ -z "$VARS_START" ]; then
21+
echo "Ошибка: секция vars не найдена в $TASKFILE" >&2
22+
exit 1
23+
fi
24+
25+
VARS_START=$((VARS_START + 1))
26+
27+
# Ищем следующую секцию после vars или конец файла
28+
NEXT_SECTION=$(tail -n +$VARS_START "$TASKFILE" | grep -n "^[a-z]" | head -1 | cut -d: -f1)
29+
if [ -n "$NEXT_SECTION" ]; then
30+
VARS_END=$((VARS_START + NEXT_SECTION - 2))
31+
else
32+
VARS_END=$(wc -l < "$TASKFILE")
33+
fi
34+
35+
# Извлекаем все строки из секции vars
36+
VARS_SECTION=$(sed -n "${VARS_START},${VARS_END}p" "$TASKFILE")
37+
38+
# Инициализируем ассоциативный массив для хранения переменных
39+
declare -A VARS
40+
41+
# Извлекаем имя и значение каждой переменной
42+
while IFS= read -r line; do
43+
# Пропускаем пустые строки и строки с комментариями
44+
if [[ "$line" =~ ^[[:space:]]*$ || "$line" =~ ^[[:space:]]*# ]]; then
45+
continue
46+
fi
47+
48+
# Извлекаем имя и значение
49+
if [[ "$line" =~ ^[[:space:]]*([A-Z_0-9]+):\ *\'([^\']*)\' ]]; then
50+
var_name=${BASH_REMATCH[1]}
51+
var_value=${BASH_REMATCH[2]}
52+
VARS["$var_name"]="$var_value"
53+
echo "- $var_name: ${VARS[$var_name]}"
54+
elif [[ "$line" =~ ^[[:space:]]*([A-Z_0-9]+):\ *\"([^\"]*)\" ]]; then
55+
var_name=${BASH_REMATCH[1]}
56+
var_value=${BASH_REMATCH[2]}
57+
VARS["$var_name"]="$var_value"
58+
echo "- $var_name: ${VARS[$var_name]}"
59+
elif [[ "$line" =~ ^[[:space:]]*([A-Z_0-9]+):\ *(.*) ]]; then
60+
var_name=${BASH_REMATCH[1]}
61+
var_value=${BASH_REMATCH[2]}
62+
VARS["$var_name"]="$var_value"
63+
echo "- $var_name: ${VARS[$var_name]}"
64+
fi
65+
done <<< "$VARS_SECTION"
66+
67+
# Находим список модулей
68+
if [ -n "${VARS[MODULES]}" ]; then
69+
MODULES="${VARS[MODULES]}"
70+
echo "- найдены модули: $MODULES"
71+
else
72+
# Если не найдено в vars, пытаемся найти в другом месте (для обратной совместимости)
73+
MODULES=$(sed -n 's/.*MODULES: \(.*\)/\1/p' "$TASKFILE" | head -1)
74+
echo "- модули (из старого формата): $MODULES"
75+
fi
76+
77+
# Установка переменных GitHub Actions
78+
if [ -n "$GITHUB_ENV" ]; then
79+
echo "Устанавливаем переменные в GITHUB_ENV:"
80+
# Экспортируем все переменные
81+
for var_name in "${!VARS[@]}"; do
82+
echo "$var_name=${VARS[$var_name]}" >> $GITHUB_ENV
83+
echo " $var_name -> GITHUB_ENV"
84+
done
85+
# Для совместимости добавляем MODULES отдельно, если оно не в vars
86+
if [ -z "${VARS[MODULES]}" ] && [ -n "$MODULES" ]; then
87+
echo "MODULES=$MODULES" >> $GITHUB_ENV
88+
echo " MODULES -> GITHUB_ENV"
89+
fi
90+
fi
91+
92+
if [ -n "$GITHUB_OUTPUT" ]; then
93+
echo "Устанавливаем переменные в GITHUB_OUTPUT:"
94+
# Экспортируем все переменные
95+
for var_name in "${!VARS[@]}"; do
96+
echo "$var_name=${VARS[$var_name]}" >> $GITHUB_OUTPUT
97+
echo " $var_name -> GITHUB_OUTPUT"
98+
done
99+
# Для совместимости добавляем MODULES отдельно, если оно не в vars
100+
if [ -z "${VARS[MODULES]}" ] && [ -n "$MODULES" ]; then
101+
echo "MODULES=$MODULES" >> $GITHUB_OUTPUT
102+
echo " MODULES -> GITHUB_OUTPUT"
103+
fi
104+
fi

.github/workflows/ci.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
pull_request:
6+
7+
jobs:
8+
extract-vars:
9+
name: Extract Variables from Taskfile
10+
runs-on: ubuntu-latest
11+
outputs:
12+
go-version: ${{ steps.extract.outputs.GO_VERSION }}
13+
golangci-lint-version: ${{ steps.extract.outputs.GOLANGCI_LINT_VERSION }}
14+
modules: ${{ steps.extract.outputs.MODULES }}
15+
16+
steps:
17+
- name: Checkout code
18+
uses: actions/checkout@v4.2.2
19+
20+
- name: Extract variables from Taskfile
21+
id: extract
22+
run: |
23+
chmod +x .github/scripts/extract-versions.sh
24+
.github/scripts/extract-versions.sh
25+
26+
lint:
27+
needs: extract-vars
28+
uses: ./.github/workflows/lint-reusable.yml
29+
with:
30+
modules: ${{ needs.extract-vars.outputs.modules }}
31+
go-version: ${{ needs.extract-vars.outputs.go-version }}
32+
golangci-lint-version: ${{ needs.extract-vars.outputs.golangci-lint-version }}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Lint Reusable
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
modules:
7+
required: true
8+
type: string
9+
go-version:
10+
required: true
11+
type: string
12+
golangci-lint-version:
13+
required: true
14+
type: string
15+
16+
jobs:
17+
golangci-lint:
18+
name: Lint all Go modules
19+
runs-on: ubuntu-latest
20+
21+
steps:
22+
- name: 📦 Checkout code
23+
uses: actions/checkout@v4.2.2
24+
25+
- name: 🛠 Set up Go
26+
uses: actions/setup-go@v5.4.0
27+
with:
28+
go-version: ${{ inputs.go-version }}
29+
30+
- name: 🐾 Show go.work (debug)
31+
run: cat go.work || echo "❗ go.work not found"
32+
33+
- name: 📌 Install Task
34+
uses: arduino/setup-task@v2.0.0
35+
36+
- name: ✅ Run golangci-lint via Taskfile
37+
env:
38+
MODULES: ${{ inputs.modules }}
39+
GOLANGCI_LINT_VERSION: ${{ inputs.golangci-lint-version }}
40+
run: task lint

.gitignore

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# If you prefer the allow list template instead of the deny list, see community template:
2+
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
3+
#
4+
# Binaries for programs and plugins
5+
*.exe
6+
*.exe~
7+
*.dll
8+
*.so
9+
*.dylib
10+
11+
# Test binary, built with `go test -c`
12+
*.test
13+
14+
# Output of the go coverage tool, specifically when used with LiteIDE
15+
*.out
16+
17+
# Dependency directories (remove the comment below to include it)
18+
# vendor/
19+
20+
# env file
21+
.env
22+
/.idea
23+
/.vscode
24+
/.cursor
25+
.DS_Store
26+
27+
/bin
28+
node_modules
29+
/shared/api/bundles/
30+
/coverage/

.golangci.yml

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
version: "2" # Версия формата конфигурационного файла (v2 — для golangci-lint 2.x)
2+
3+
run: # Параметры запуска линтера
4+
timeout: 5m # Максимальное время выполнения анализа
5+
relative-path-mode: gomod # Пути к файлам интерпретируются относительно корня go-модуля
6+
issues-exit-code: 1 # Код выхода = 1, если найдены ошибки (полезно для CI)
7+
tests: true # Анализировать также _test.go файлы
8+
modules-download-mode: readonly # Не разрешать загрузку зависимостей во время анализа (безопасно и быстрее на CI)
9+
10+
output: # Настройки вывода результатов линтинга
11+
formats:
12+
text: # Формат вывода в терминал
13+
print-linter-name: true # Показывать имя линтера рядом с каждым сообщением
14+
print-issued-lines: true # Показывать строку кода, где найдена проблема
15+
colors: true # Использовать цветной вывод (для лучшей читаемости)
16+
17+
issues: # Общие настройки для обработки проблем
18+
max-issues-per-linter: 0 # Без лимита на количество ошибок от одного линтера
19+
max-same-issues: 0 # Без лимита на количество одинаковых ошибок
20+
uniq-by-line: true # Отображать максимум одну ошибку на строку (избегает спама от разных линтеров по одной строке)
21+
22+
linters: # Список активных и отключённых линтеров
23+
default: standard # Использовать стандартный набор линтеров golangci-lint
24+
enable: # Явно включенные дополнительные линтеры
25+
- errcheck # Проверяет, что ошибки не игнорируются
26+
- staticcheck # Глубокий статический анализ (includes gosimple, stylecheck)
27+
- govet # Встроенный в Go инструмент анализа (ловит потенциальные баги)
28+
- gocritic # Набор продвинутых правил для улучшения качества кода
29+
- revive # Проверяет стиль кода, оформление, названия
30+
- unused # Находит неиспользуемые переменные, типы, функции
31+
- gosec # Проверка на уязвимости и небезопасные практики
32+
- depguard # Запрещает импорт указанных пакетов (контроль зависимостей)
33+
- bodyclose # Проверяет, что закрывается `resp.Body` у HTTP-запросов
34+
- asciicheck # Предупреждает о не-ASCII символах в коде
35+
- cyclop # Контролирует цикломатическую сложность функций
36+
- dupl # Находит дублированные фрагменты кода
37+
- ineffassign # Обнаруживает неиспользуемые присваивания
38+
- unparam # Находит неиспользуемые параметры функций
39+
- errorlint # Рекомендует использовать `errors.Is` / `errors.As` вместо прямых сравнений
40+
- errname # Проверяет имена переменных/типов ошибок (должны содержать "Err")
41+
- forbidigo # Запрещает определённые вызовы или конструкции по regex-паттернам
42+
- contextcheck # Проверяет, что `context.Context` передаётся в методы/функции
43+
- containedctx # Предупреждает, если `context.Context` сохраняется в структуре (плохо)
44+
disable: # Линтеры, которые намеренно отключены
45+
- gocyclo # Старый линтер сложности (заменён на cyclop)
46+
- lll # Проверка длины строк (часто шумная и мешает)
47+
48+
exclusions: # Исключения из анализа
49+
generated: strict # Игнорировать сгенерированные файлы (по "// Code generated ... DO NOT EDIT.")
50+
rules: # Локальные исключения по пути/файлу/линерам
51+
- path: _test\.go # Для всех тестов:
52+
linters:
53+
- cyclop # Не проверять сложность тестов
54+
- dupl # Не проверять дублирование в тестах
55+
- gosec # Не искать уязвимости в тестах
56+
57+
settings: # Индивидуальные настройки для конкретных линтеров
58+
59+
gosec:
60+
# Включаем все правила, включая экспериментальные
61+
config:
62+
global:
63+
audit: true # Активировать все правила безопасности
64+
show-ignored: true # Показывать проигнорированные проблемы в комментариях
65+
severity: "medium" # Минимальная важность проблем, high/medium/low
66+
confidence: "medium" # Минимальный уровень уверенности, high/medium/low
67+
68+
cyclop:
69+
max-complexity: 20 # Допустимая сложность функции (больше 10, но не слишком жёстко)
70+
71+
depguard:
72+
rules:
73+
main:
74+
deny:
75+
- pkg: io/ioutil # Запрет использовать устаревший пакет
76+
desc: "Использование устаревшего пакета io/ioutil запрещено (замените вызовы на аналоги из пакетов os/io)"
77+
78+
revive:
79+
severity: warning # Нарушения revive будут как предупреждения, а не ошибки
80+
81+
forbidigo:
82+
exclude-godoc-examples: true # Не применять запреты к Godoc-примерам
83+
analyze-types: true # Искать запреты и в типах/константах, а не только в функциях
84+
forbid: # Список запрещённых паттернов
85+
- pattern: '^fmt\.Print.*$' # Запрет использования fmt.Print*, fmt.Println и т.д.
86+
msg: "Запрещено напрямую использовать fmt.Print* для логирования (вместо этого используйте структурированный логгер)"
87+
- pattern: '^time\.Sleep$' # Запрет использования time.Sleep
88+
msg: "Запрещено использовать time.Sleep в продакшен-коде (используй таймеры/контекст)"
89+
- pattern: '^http\.DefaultClient$' # Запрет использования небезопасного http.DefaultClient
90+
msg: "Не используй http.DefaultClient (нет таймаутов); создай *http.Client с таймаутами"
91+
92+
formatters: # Форматтеры кода (проверка стиля)
93+
enable:
94+
- gofumpt # Строгая версия gofmt с дополнительными правилами
95+
- gci # Форматтер импортов (гибкая альтернатива goimports)
96+
settings:
97+
gofumpt:
98+
extra-rules: true # Включить дополнительные строгие правила (например, удаление лишних пустых строк)
99+
gci:
100+
sections: # Порядок группировки импортов
101+
- Standard # Стандартная библиотека
102+
- Default # Сторонние зависимости
103+
- Prefix(github.com/qyrlabs/test-backend) # Локальные импорты проекта
104+
no-inline-comments: false # Разрешить комментарии после импортов (true — запретит)

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# test-backend

0 commit comments

Comments
 (0)