[CI SKIP] Initial commit

This commit is contained in:
2026-02-07 12:36:13 +03:00
commit 52073bf2f9
17 changed files with 486 additions and 0 deletions

12
.bumpversion.cfg Normal file
View File

@@ -0,0 +1,12 @@
[bumpversion]
current_version = 1.0.0
commit = True
tag = True
tag_name = {new_version}
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)
serialize = {major}.{minor}.{patch}
message = [skip ci] Bump version: {current_version} → {new_version}
[bumpversion:file:VERSION]
[bumpversion:file:README.md]

13
.dockerignore Normal file
View File

@@ -0,0 +1,13 @@
.git
.gitmodules
.gitignore
.dockerignore
.drone.yml
.bumpversion.cfg
.DS_Store
docs/
ent/
Makefile
*.md
*.txt
service.run

140
.drone.yml Normal file
View File

@@ -0,0 +1,140 @@
---
kind: pipeline
type: docker
name: templates-orm
platform:
os: linux
arch: amd64
clone:
disable: true
steps:
- name: clone
image: plugins/git
settings:
depth: 5
skip_verify: true
recursive: true
- name: "[branch] bump version"
image: registry.halfakop.ru/golang/bumpversion:1.1.0
environment:
GIT_USERNAME:
from_secret: GIT_USERNAME
GIT_EMAIL:
from_secret: GIT_EMAIL
command: ["--no-commit", "--no-tag", "patch"]
load: false # do not use /bin/sh -c here
when:
branch:
- task-*
event:
- push
- name: "[branch] build"
image: plugins/docker
volumes:
- name: docker-sock
path: /var/run/docker.sock
environment:
DOCKER_USERNAME:
from_secret: DOCKER_USERNAME
DOCKER_PASSWORD:
from_secret: DOCKER_PASSWORD
GOPROXY:
from_secret: GOPROXY
GONOSUMDB:
from_secret: GONOSUMDB
commands:
- apk add make git bash
- make build DOCKER_OPTS="--network devtools"
when:
branch:
- task-*
event:
- push
status:
- success
- name: "[master] bump version"
image: registry.halfakop.ru/golang/bumpversion:1.1.0
environment:
GIT_USERNAME:
from_secret: GIT_USERNAME
GIT_EMAIL:
from_secret: GIT_EMAIL
command: ["patch"]
load: false # do not use /bin/sh -c here
when:
branch:
- master
event:
- push
- name: "[master] build"
image: plugins/docker
volumes:
- name: docker-sock
path: /var/run/docker.sock
environment:
DOCKER_USERNAME:
from_secret: DOCKER_USERNAME
DOCKER_PASSWORD:
from_secret: DOCKER_PASSWORD
GOPROXY:
from_secret: GOPROXY
GONOSUMDB:
from_secret: GONOSUMDB
commands:
- apk add make git bash
- make login build DOCKER_OPTS="--network devtools"
when:
branch:
- master
status:
- success
- name: "[master] tagging"
image: alpine/git
commands:
- git push origin master
- git push origin master --tags
when:
branch:
- master
status:
- success
- name: "[master] push"
image: plugins/docker
volumes:
- name: docker-sock
path: /var/run/docker.sock
environment:
DOCKER_USERNAME:
from_secret: DOCKER_USERNAME
DOCKER_PASSWORD:
from_secret: DOCKER_PASSWORD
commands:
- apk add make git bash
- make login push DOCKER_OPTS="--network devtools"
when:
branch:
- master
status:
- success
trigger:
event:
exclude:
- tag
volumes:
- name: docker-sock
host:
path: /var/run/docker.sock
image_pull_secrets:
- DOCKER_AUTH

3
.gitattributes vendored Normal file
View File

@@ -0,0 +1,3 @@
# Ensure all text files use LF line endings
* text=auto eol=lf

10
.gitignore vendored Normal file
View File

@@ -0,0 +1,10 @@
# игнорируем содержимое ent, кроме ent/schema
ent/*
!ent/schema
!ent/schema/**
# игнорируем маковский индекс
.DS_Store
# локальная документация
HELP.md

4
Dockerfile Normal file
View File

@@ -0,0 +1,4 @@
FROM arigaio/atlas:0.38.0
WORKDIR /migrations
COPY atlas/migrations/ /migrations/
CMD ["migrate", "apply"]

79
Makefile Normal file
View File

@@ -0,0 +1,79 @@
NAMESPACE ?= templates
PACKAGE := database_migrations
REGISTRY := registry.halfakop.ru
REPOSITORY := $(NAMESPACE)/$(PACKAGE)
PLATFORM ?= --platform=linux/amd64
SOURCE_VERSION ?= $(shell cat VERSION)
IMAGE_NAME_TAGGED := $(REPOSITORY):$(SOURCE_VERSION)
MIGTATIONS_PATH ?= atlas/migrations
SCHEMA_PATH ?= ent/schema
DEV_DB_URL ?= "postgresql://user:pass@localhost:5432/project-dev?sslmode=disable"
DB_URL ?= "postgresql://user:pass@localhost:5432/project?sslmode=disable"
all: help
help:
@printf "\nORM make targets:\n"
@printf " make generate - Generate ent code from schema in %s\n" "$(SCHEMA_PATH)"
@printf " make clean - Remove generated ent code (schema is kept)\n"
@printf " make initial - Clean + generate + create initial migration into %s (uses DEV_DB_URL)\n" "$(MIGTATIONS_PATH)"
@printf " make migration - Generate and diff a new migration into %s (uses DEV_DB_URL)\n" "$(MIGTATIONS_PATH)"
@printf " make apply - Apply migrations to DB_URL\n"
@printf " make build - Build backend migrations image tagged %s\n" "$(IMAGE_NAME_TAGGED)"
@printf " make login - Login to docker registry %s\n" "$(REGISTRY)"
@printf " make push - Push backend migrations image tagged %s to %s\n" "$(IMAGE_NAME_TAGGED)" "$(REGISTRY)"
@printf "\nVariables:\n"
@printf " MIGTATIONS_PATH=%s\n" "$(MIGTATIONS_PATH)"
@printf " SCHEMA_PATH=%s\n" "$(SCHEMA_PATH)"
@printf " DEV_DB_URL=%s\n" "$(DEV_DB_URL)"
@printf " DB_URL=%s\n\n" "$(DB_URL)"
clean:
@echo "Cleaning orm/ent but keep the schema itself"
@find ent -mindepth 1 -maxdepth 1 ! -name 'schema' -exec rm -rf {} \;
@rm -f .DS_Store
generate:
@echo "Generating schema"
@ent generate "./$(SCHEMA_PATH)"
initial: clean generate
@echo "Creating initial migration"
@atlas migrate diff initial \
--dir "file://$(MIGTATIONS_PATH)" \
--to "ent://$(SCHEMA_PATH)" \
--dev-url="$(DEV_DB_URL)"
migration: generate
@echo "Creating new migration"
@atlas migrate diff add_changes \
--dir "file://$(MIGTATIONS_PATH)" \
--to "ent://$(SCHEMA_PATH)" \
--dev-url="$(DEV_DB_URL)"
apply:
@echo "Applying migrations"
@atlas migrate apply \
--dir "file://$(MIGTATIONS_PATH)" \
--url="$(DB_URL)"
build:
DOCKER_BUILDKIT=0 \
docker build $(PLATFORM) --progress=plain --compress \
-t $(IMAGE_NAME_TAGGED) \
-t $(REGISTRY)/$(IMAGE_NAME_TAGGED) \
${DOCKER_OPTS} \
-f Dockerfile .
login:
$(call check-var-defined,DOCKER_USERNAME)
$(call check-var-defined,DOCKER_PASSWORD)
@echo ${DOCKER_PASSWORD} | \
docker login -u ${DOCKER_USERNAME} --password-stdin $(REGISTRY)
push:
docker push $(REGISTRY)/$(IMAGE_NAME_TAGGED)
.PHONY: all help clean generate initial migration apply build login push

29
README.md Normal file
View File

@@ -0,0 +1,29 @@
# ORM template (backend)
[![Build Status](https://drone.halfakop.ru/api/badges/templates/orm/status.svg)](https://drone.halfakop.ru/templates/orm)
Version: 1.0.0
Базовый шаблон ORM для backend-проектов. Использует Ent для схем и Atlas для миграций.
## Состав
- Единственная модель: `User` (см. `ent/schema/user.go`).
- Общие поля вынесены в `ent/schema/common.go`.
- Миграции создаются в `atlas/migrations`.
## Работа с Makefile
- `make help` — список целей и используемых переменных окружения.
- `make migration` — создать новую миграцию (использует `DEV_DB_URL`).
- `make apply` — применить миграции (использует `DB_URL`).
- `make initial`с нуля: очистка, генерация ent, стартовая миграция.
- `make generate` — обновить сгенерированный код без миграций.
- `make clean` — удалить сгенерированный код, не трогая схемы.
Примеры переменных в `.env`:
```
DEV_DB_URL=postgresql://user:pass@localhost:5432/project-dev?sslmode=disable
DB_URL=postgresql://user:pass@localhost:5432/project?sslmode=disable
```

1
VERSION Normal file
View File

@@ -0,0 +1 @@
1.0.0

21
atlas/README.md Normal file
View File

@@ -0,0 +1,21 @@
# Миграции
Миграции создаются из схем Ent и хранятся в `atlas/migrations`.
Создание первичной миграции:
```
make initial
```
Создание последующих миграций:
```
make migration
```
Применение миграций:
```
make apply
```

View File

20
docs/DEVELOPER.md Normal file
View File

@@ -0,0 +1,20 @@
# Разработчику
## Окружение
Перейдите в каталог репозитория и настройте переменные окружения (см. `README.md`).
Пример `.env`:
```
DEV_DB_URL=postgresql://user:pass@localhost:5432/project-dev?sslmode=disable
DB_URL=postgresql://user:pass@localhost:5432/project?sslmode=disable
```
## Миграции
- Первичная миграция: `make initial`.
- Новая миграция после изменений схем: `make migration`.
- Применить миграции: `make apply`.
Все команды используют `DEV_DB_URL` и `DB_URL` из окружения.

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8" />
<title>ORM Template — User ERD</title>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Ubuntu, Cantarell, 'Helvetica Neue', Arial; padding: 16px; }
h2 { margin-bottom: 4px; }
h3 { margin-top: 28px; }
.hint { color: #666; font-size: 0.9em; }
pre.mermaid { border: 1px solid #ddd; border-radius: 8px; padding: 12px; }
</style>
</head>
<body>
<h2>ORM Template — ER-диаграмма</h2>
<div class="hint">Единственная модель: User и ее атрибуты.</div>
<pre class="mermaid">
erDiagram
USER {
uuid id PK "первичный ключ пользователя"
text login "уникальный логин"
text password "хэш пароля"
text role "роль пользователя"
bool is_active "активность"
bool is_temporal "пароль временный, хранится в открытом виде"
timestamptz created_at "дата создания"
}
</pre>
<script type="module">
import mermaid from "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs";
mermaid.initialize({ startOnLoad: true, theme: "neutral" });
</script>
</body>
</html>

13
ent/schema/README.md Normal file
View File

@@ -0,0 +1,13 @@
# Схема базы данных
Шаблон содержит одну модель — `User`.
Поля по умолчанию:
- `id` — UUID, первичный ключ.
- `created_at` — время создания записи.
- `login` — логин пользователя (уникальный).
- `password` — хэш пароля.
- `role` — роль пользователя (по умолчанию `user`).
- `is_active` — активность.
- `is_temporal` — временный пароль (хранится в открытом виде).

38
ent/schema/common.go Normal file
View File

@@ -0,0 +1,38 @@
package schema
import (
"time"
"github.com/google/uuid"
"entgo.io/ent"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
"entgo.io/ent/schema/mixin"
)
type PkMixin struct {
mixin.Schema
}
func (PkMixin) Fields() []ent.Field {
return []ent.Field{
field.UUID("id", uuid.Nil).Default(uuid.New),
}
}
type RegisteredMixin struct {
mixin.Schema
}
func (RegisteredMixin) Fields() []ent.Field {
return []ent.Field{
field.Time("created_at").Immutable().Default(time.Now),
}
}
func (RegisteredMixin) Indexes() []ent.Index {
return []ent.Index{
index.Fields("created_at"),
}
}

53
ent/schema/user.go Normal file
View File

@@ -0,0 +1,53 @@
package schema
import (
"entgo.io/ent"
"entgo.io/ent/dialect/entsql"
"entgo.io/ent/schema"
"entgo.io/ent/schema/field"
"entgo.io/ent/schema/index"
)
type User struct{ ent.Schema }
func (User) Annotations() []schema.Annotation {
return []schema.Annotation{
entsql.WithComments(true),
schema.Comment("Пользователи системы"),
}
}
func (User) Mixin() []ent.Mixin {
return []ent.Mixin{
PkMixin{},
RegisteredMixin{},
}
}
func (User) Fields() []ent.Field {
return []ent.Field{
field.String("login").NotEmpty().Unique().
MaxLen(128).Annotations(
entsql.Check("char_length(login) <= 128"),
),
field.String("password").NotEmpty().
MaxLen(256).Annotations(
entsql.Check("char_length(password) <= 256"),
).Comment("Хэш пароля"),
field.String("role").NotEmpty().
Default("user").
MaxLen(64).Annotations(
entsql.Check("char_length(role) <= 64"),
).Comment("Роль пользователя"),
field.Bool("is_active").Default(true).Comment("Активность"),
field.Bool("is_temporal").Default(true).Comment("Пароль временный и хранится в открытом виде"),
// field.UUID("ldap_id").NotEmpty().Unique(),
// field.String("ldap_dn").NotEmpty().Unique(),
}
}
func (User) Indexes() []ent.Index {
return []ent.Index{
index.Fields("is_active"),
}
}