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