forked from templates/template-go-orm
[CI SKIP] Initial commit
This commit is contained in:
12
.bumpversion.cfg
Normal file
12
.bumpversion.cfg
Normal 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
13
.dockerignore
Normal 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
140
.drone.yml
Normal 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
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Ensure all text files use LF line endings
|
||||||
|
* text=auto eol=lf
|
||||||
|
|
||||||
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# игнорируем содержимое ent, кроме ent/schema
|
||||||
|
ent/*
|
||||||
|
!ent/schema
|
||||||
|
!ent/schema/**
|
||||||
|
|
||||||
|
# игнорируем маковский индекс
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# локальная документация
|
||||||
|
HELP.md
|
||||||
4
Dockerfile
Normal file
4
Dockerfile
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
FROM arigaio/atlas:0.38.0
|
||||||
|
WORKDIR /migrations
|
||||||
|
COPY atlas/migrations/ /migrations/
|
||||||
|
CMD ["migrate", "apply"]
|
||||||
79
Makefile
Normal file
79
Makefile
Normal 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
29
README.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# ORM template (backend)
|
||||||
|
|
||||||
|
[](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
|
||||||
|
```
|
||||||
21
atlas/README.md
Normal file
21
atlas/README.md
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Миграции
|
||||||
|
|
||||||
|
Миграции создаются из схем Ent и хранятся в `atlas/migrations`.
|
||||||
|
|
||||||
|
Создание первичной миграции:
|
||||||
|
|
||||||
|
```
|
||||||
|
make initial
|
||||||
|
```
|
||||||
|
|
||||||
|
Создание последующих миграций:
|
||||||
|
|
||||||
|
```
|
||||||
|
make migration
|
||||||
|
```
|
||||||
|
|
||||||
|
Применение миграций:
|
||||||
|
|
||||||
|
```
|
||||||
|
make apply
|
||||||
|
```
|
||||||
0
atlas/migrations/.gitkeep
Normal file
0
atlas/migrations/.gitkeep
Normal file
20
docs/DEVELOPER.md
Normal file
20
docs/DEVELOPER.md
Normal 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` из окружения.
|
||||||
14
docs/diagrams/mermaid.esm.min.mjs
Normal file
14
docs/diagrams/mermaid.esm.min.mjs
Normal file
File diff suppressed because one or more lines are too long
36
docs/diagrams/preview.html
Normal file
36
docs/diagrams/preview.html
Normal 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
13
ent/schema/README.md
Normal 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
38
ent/schema/common.go
Normal 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
53
ent/schema/user.go
Normal 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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user