From 52073bf2f98e3c40d994c9778fc67deb4c61f9bb Mon Sep 17 00:00:00 2001 From: Ruslan Popov Date: Sat, 7 Feb 2026 12:36:13 +0300 Subject: [PATCH] [CI SKIP] Initial commit --- .bumpversion.cfg | 12 +++ .dockerignore | 13 +++ .drone.yml | 140 ++++++++++++++++++++++++++++++ .gitattributes | 3 + .gitignore | 10 +++ Dockerfile | 4 + Makefile | 79 +++++++++++++++++ README.md | 29 +++++++ VERSION | 1 + atlas/README.md | 21 +++++ atlas/migrations/.gitkeep | 0 docs/DEVELOPER.md | 20 +++++ docs/diagrams/mermaid.esm.min.mjs | 14 +++ docs/diagrams/preview.html | 36 ++++++++ ent/schema/README.md | 13 +++ ent/schema/common.go | 38 ++++++++ ent/schema/user.go | 53 +++++++++++ 17 files changed, 486 insertions(+) create mode 100644 .bumpversion.cfg create mode 100644 .dockerignore create mode 100644 .drone.yml create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 README.md create mode 100644 VERSION create mode 100644 atlas/README.md create mode 100644 atlas/migrations/.gitkeep create mode 100644 docs/DEVELOPER.md create mode 100644 docs/diagrams/mermaid.esm.min.mjs create mode 100644 docs/diagrams/preview.html create mode 100644 ent/schema/README.md create mode 100644 ent/schema/common.go create mode 100644 ent/schema/user.go diff --git a/.bumpversion.cfg b/.bumpversion.cfg new file mode 100644 index 0000000..6288116 --- /dev/null +++ b/.bumpversion.cfg @@ -0,0 +1,12 @@ +[bumpversion] +current_version = 1.0.0 +commit = True +tag = True +tag_name = {new_version} +parse = (?P\d+)\.(?P\d+)\.(?P\d+) +serialize = {major}.{minor}.{patch} +message = [skip ci] Bump version: {current_version} → {new_version} + +[bumpversion:file:VERSION] + +[bumpversion:file:README.md] diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ce8bf1c --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +.git +.gitmodules +.gitignore +.dockerignore +.drone.yml +.bumpversion.cfg +.DS_Store +docs/ +ent/ +Makefile +*.md +*.txt +service.run diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..d633e5e --- /dev/null +++ b/.drone.yml @@ -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 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3958a77 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# Ensure all text files use LF line endings +* text=auto eol=lf + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cddb99d --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# игнорируем содержимое ent, кроме ent/schema +ent/* +!ent/schema +!ent/schema/** + +# игнорируем маковский индекс +.DS_Store + +# локальная документация +HELP.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c080724 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,4 @@ +FROM arigaio/atlas:0.38.0 +WORKDIR /migrations +COPY atlas/migrations/ /migrations/ +CMD ["migrate", "apply"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..75e4b6d --- /dev/null +++ b/Makefile @@ -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 diff --git a/README.md b/README.md new file mode 100644 index 0000000..f2fd3da --- /dev/null +++ b/README.md @@ -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 +``` diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..afaf360 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/atlas/README.md b/atlas/README.md new file mode 100644 index 0000000..9dc5d16 --- /dev/null +++ b/atlas/README.md @@ -0,0 +1,21 @@ +# Миграции + +Миграции создаются из схем Ent и хранятся в `atlas/migrations`. + +Создание первичной миграции: + +``` +make initial +``` + +Создание последующих миграций: + +``` +make migration +``` + +Применение миграций: + +``` +make apply +``` diff --git a/atlas/migrations/.gitkeep b/atlas/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/DEVELOPER.md b/docs/DEVELOPER.md new file mode 100644 index 0000000..c841620 --- /dev/null +++ b/docs/DEVELOPER.md @@ -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` из окружения. diff --git a/docs/diagrams/mermaid.esm.min.mjs b/docs/diagrams/mermaid.esm.min.mjs new file mode 100644 index 0000000..7bd0f72 --- /dev/null +++ b/docs/diagrams/mermaid.esm.min.mjs @@ -0,0 +1,14 @@ +import{a as ht}from"./chunks/mermaid.esm.min/chunk-HQLFZTFY.mjs";import{a as Yt}from"./chunks/mermaid.esm.min/chunk-MEBTFSOL.mjs";import{a as Ut,b as qt}from"./chunks/mermaid.esm.min/chunk-7LIB5WBN.mjs";import{a as Bt}from"./chunks/mermaid.esm.min/chunk-L736DJ4U.mjs";import"./chunks/mermaid.esm.min/chunk-QTJCGBHB.mjs";import"./chunks/mermaid.esm.min/chunk-USR3SDWQ.mjs";import{b as St}from"./chunks/mermaid.esm.min/chunk-2VPXETT4.mjs";import"./chunks/mermaid.esm.min/chunk-S67DUUA5.mjs";import"./chunks/mermaid.esm.min/chunk-LM6QDVU5.mjs";import{a as Mt}from"./chunks/mermaid.esm.min/chunk-HESFG3RP.mjs";import{b as Vt,j as yt,l as $t,m as V,n as Nt,o as Ht}from"./chunks/mermaid.esm.min/chunk-YM3XIQPS.mjs";import"./chunks/mermaid.esm.min/chunk-TI4EEUUG.mjs";import{A as G,B as It,C as Y,D as Ft,G as _t,M as Gt,O as zt,aa as z,b as g,ba as X,c as gt,d as At,f as Tt,g as lt,ga as k,h as J,i as Z,j as Ct,k as Rt,r as tt,u as ut,v as kt,w as Ot,x as Pt,y as Dt,z as jt}from"./chunks/mermaid.esm.min/chunk-ZKYS2E5M.mjs";import{d as xt}from"./chunks/mermaid.esm.min/chunk-YPUTD6PB.mjs";import"./chunks/mermaid.esm.min/chunk-6BY5RJGC.mjs";import{a as r}from"./chunks/mermaid.esm.min/chunk-GTKDMUJJ.mjs";var Xt="c4",Ie=r(t=>/^\s*C4Context|C4Container|C4Component|C4Dynamic|C4Deployment/.test(t),"detector"),Fe=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/c4Diagram-GNV6VVOW.mjs");return{id:Xt,diagram:t}},"loader"),_e={id:Xt,detector:Ie,loader:Fe},Wt=_e;var Kt="flowchart",Ge=r((t,e)=>e?.flowchart?.defaultRenderer==="dagre-wrapper"||e?.flowchart?.defaultRenderer==="elk"?!1:/^\s*graph/.test(t),"detector"),ze=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/flowDiagram-RXJ4TZVH.mjs");return{id:Kt,diagram:t}},"loader"),Ve={id:Kt,detector:Ge,loader:ze},Qt=Ve;var Jt="flowchart-v2",$e=r((t,e)=>e?.flowchart?.defaultRenderer==="dagre-d3"?!1:(e?.flowchart?.defaultRenderer==="elk"&&(e.layout="elk"),/^\s*graph/.test(t)&&e?.flowchart?.defaultRenderer==="dagre-wrapper"?!0:/^\s*flowchart/.test(t)),"detector"),Ne=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/flowDiagram-RXJ4TZVH.mjs");return{id:Jt,diagram:t}},"loader"),He={id:Jt,detector:$e,loader:Ne},Zt=He;var tr="er",Ue=r(t=>/^\s*erDiagram/.test(t),"detector"),qe=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/erDiagram-K5RJBHCA.mjs");return{id:tr,diagram:t}},"loader"),Be={id:tr,detector:Ue,loader:qe},rr=Be;var er="gitGraph",Ye=r(t=>/^\s*gitGraph/.test(t),"detector"),Xe=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/gitGraphDiagram-WO7WVN2C.mjs");return{id:er,diagram:t}},"loader"),We={id:er,detector:Ye,loader:Xe},ar=We;var ir="gantt",Ke=r(t=>/^\s*gantt/.test(t),"detector"),Qe=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/ganttDiagram-5MLOKHXO.mjs");return{id:ir,diagram:t}},"loader"),Je={id:ir,detector:Ke,loader:Qe},or=Je;var nr="info",Ze=r(t=>/^\s*info/.test(t),"detector"),ta=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/infoDiagram-E3C2IIUA.mjs");return{id:nr,diagram:t}},"loader"),sr={id:nr,detector:Ze,loader:ta};var cr="pie",ra=r(t=>/^\s*pie/.test(t),"detector"),ea=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/pieDiagram-Q56JBFDI.mjs");return{id:cr,diagram:t}},"loader"),mr={id:cr,detector:ra,loader:ea};var pr="quadrantChart",aa=r(t=>/^\s*quadrantChart/.test(t),"detector"),ia=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/quadrantDiagram-KBTC774P.mjs");return{id:pr,diagram:t}},"loader"),oa={id:pr,detector:aa,loader:ia},dr=oa;var fr="xychart",na=r(t=>/^\s*xychart-beta/.test(t),"detector"),sa=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/xychartDiagram-4C6ER3FX.mjs");return{id:fr,diagram:t}},"loader"),ca={id:fr,detector:na,loader:sa},gr=ca;var lr="requirement",ma=r(t=>/^\s*requirement(Diagram)?/.test(t),"detector"),pa=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/requirementDiagram-OPD2HUS5.mjs");return{id:lr,diagram:t}},"loader"),da={id:lr,detector:ma,loader:pa},ur=da;var Dr="sequence",fa=r(t=>/^\s*sequenceDiagram/.test(t),"detector"),ga=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/sequenceDiagram-ODO66PDE.mjs");return{id:Dr,diagram:t}},"loader"),la={id:Dr,detector:fa,loader:ga},yr=la;var xr="class",ua=r((t,e)=>e?.class?.defaultRenderer==="dagre-wrapper"?!1:/^\s*classDiagram/.test(t),"detector"),Da=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/classDiagram-MKYM2BOE.mjs");return{id:xr,diagram:t}},"loader"),ya={id:xr,detector:ua,loader:Da},hr=ya;var Er="classDiagram",xa=r((t,e)=>/^\s*classDiagram/.test(t)&&e?.class?.defaultRenderer==="dagre-wrapper"?!0:/^\s*classDiagram-v2/.test(t),"detector"),ha=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/classDiagram-v2-PRA2ZCF7.mjs");return{id:Er,diagram:t}},"loader"),Ea={id:Er,detector:xa,loader:ha},wr=Ea;var br="state",wa=r((t,e)=>e?.state?.defaultRenderer==="dagre-wrapper"?!1:/^\s*stateDiagram/.test(t),"detector"),ba=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/stateDiagram-76M766UR.mjs");return{id:br,diagram:t}},"loader"),La={id:br,detector:wa,loader:ba},Lr=La;var vr="stateDiagram",va=r((t,e)=>!!(/^\s*stateDiagram-v2/.test(t)||/^\s*stateDiagram/.test(t)&&e?.state?.defaultRenderer==="dagre-wrapper"),"detector"),Sa=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/stateDiagram-v2-NOSC7VFN.mjs");return{id:vr,diagram:t}},"loader"),Ma={id:vr,detector:va,loader:Sa},Sr=Ma;var Mr="journey",Aa=r(t=>/^\s*journey/.test(t),"detector"),Ta=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/journeyDiagram-UZIDTGLP.mjs");return{id:Mr,diagram:t}},"loader"),Ca={id:Mr,detector:Aa,loader:Ta},Ar=Ca;var Ra=r((t,e,a)=>{g.debug(`rendering svg for syntax error +`);let i=Yt(e),o=i.append("g");i.attr("viewBox","0 0 2412 512"),Gt(i,100,512,!0),o.append("path").attr("class","error-icon").attr("d","m411.313,123.313c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32-9.375,9.375-20.688-20.688c-12.484-12.5-32.766-12.5-45.25,0l-16,16c-1.261,1.261-2.304,2.648-3.31,4.051-21.739-8.561-45.324-13.426-70.065-13.426-105.867,0-192,86.133-192,192s86.133,192 192,192 192-86.133 192-192c0-24.741-4.864-48.327-13.426-70.065 1.402-1.007 2.79-2.049 4.051-3.31l16-16c12.5-12.492 12.5-32.758 0-45.25l-20.688-20.688 9.375-9.375 32.001-31.999zm-219.313,100.687c-52.938,0-96,43.063-96,96 0,8.836-7.164,16-16,16s-16-7.164-16-16c0-70.578 57.422-128 128-128 8.836,0 16,7.164 16,16s-7.164,16-16,16z"),o.append("path").attr("class","error-icon").attr("d","m459.02,148.98c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l16,16c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16.001-16z"),o.append("path").attr("class","error-icon").attr("d","m340.395,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688 6.25-6.25 6.25-16.375 0-22.625l-16-16c-6.25-6.25-16.375-6.25-22.625,0s-6.25,16.375 0,22.625l15.999,16z"),o.append("path").attr("class","error-icon").attr("d","m400,64c8.844,0 16-7.164 16-16v-32c0-8.836-7.156-16-16-16-8.844,0-16,7.164-16,16v32c0,8.836 7.156,16 16,16z"),o.append("path").attr("class","error-icon").attr("d","m496,96.586h-32c-8.844,0-16,7.164-16,16 0,8.836 7.156,16 16,16h32c8.844,0 16-7.164 16-16 0-8.836-7.156-16-16-16z"),o.append("path").attr("class","error-icon").attr("d","m436.98,75.605c3.125,3.125 7.219,4.688 11.313,4.688 4.094,0 8.188-1.563 11.313-4.688l32-32c6.25-6.25 6.25-16.375 0-22.625s-16.375-6.25-22.625,0l-32,32c-6.251,6.25-6.251,16.375-0.001,22.625z"),o.append("text").attr("class","error-text").attr("x",1440).attr("y",250).attr("font-size","150px").style("text-anchor","middle").text("Syntax error in text"),o.append("text").attr("class","error-text").attr("x",1250).attr("y",400).attr("font-size","100px").style("text-anchor","middle").text(`mermaid version ${a}`)},"draw"),Et={draw:Ra},Tr=Et;var ka={db:{},renderer:Et,parser:{parse:r(()=>{},"parse")}},Cr=ka;var Rr="flowchart-elk",Oa=r((t,e={})=>/^\s*flowchart-elk/.test(t)||/^\s*flowchart|graph/.test(t)&&e?.flowchart?.defaultRenderer==="elk"?(e.layout="elk",!0):!1,"detector"),Pa=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/flowDiagram-RXJ4TZVH.mjs");return{id:Rr,diagram:t}},"loader"),ja={id:Rr,detector:Oa,loader:Pa},kr=ja;var Or="timeline",Ia=r(t=>/^\s*timeline/.test(t),"detector"),Fa=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/timeline-definition-VFFECQCT.mjs");return{id:Or,diagram:t}},"loader"),_a={id:Or,detector:Ia,loader:Fa},Pr=_a;var jr="mindmap",Ga=r(t=>/^\s*mindmap/.test(t),"detector"),za=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/mindmap-definition-RP2J3NYQ.mjs");return{id:jr,diagram:t}},"loader"),Va={id:jr,detector:Ga,loader:za},Ir=Va;var Fr="kanban",$a=r(t=>/^\s*kanban/.test(t),"detector"),Na=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/kanban-definition-4PSEFK7X.mjs");return{id:Fr,diagram:t}},"loader"),Ha={id:Fr,detector:$a,loader:Na},_r=Ha;var Gr="sankey",Ua=r(t=>/^\s*sankey-beta/.test(t),"detector"),qa=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/sankeyDiagram-2NKXCTV4.mjs");return{id:Gr,diagram:t}},"loader"),Ba={id:Gr,detector:Ua,loader:qa},zr=Ba;var Vr="packet",Ya=r(t=>/^\s*packet-beta/.test(t),"detector"),Xa=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/diagram-7BLTIMBB.mjs");return{id:Vr,diagram:t}},"loader"),$r={id:Vr,detector:Ya,loader:Xa};var Nr="radar",Wa=r(t=>/^\s*radar-beta/.test(t),"detector"),Ka=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/diagram-R7SGKMCD.mjs");return{id:Nr,diagram:t}},"loader"),Hr={id:Nr,detector:Wa,loader:Ka};var Ur="block",Qa=r(t=>/^\s*block-beta/.test(t),"detector"),Ja=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/blockDiagram-GQNB4GIR.mjs");return{id:Ur,diagram:t}},"loader"),Za={id:Ur,detector:Qa,loader:Ja},qr=Za;var Br="architecture",ti=r(t=>/^\s*architecture/.test(t),"detector"),ri=r(async()=>{let{diagram:t}=await import("./chunks/mermaid.esm.min/architectureDiagram-YZ6UH2CF.mjs");return{id:Br,diagram:t}},"loader"),ei={id:Br,detector:ti,loader:ri},Yr=ei;var Xr=!1,$=r(()=>{Xr||(Xr=!0,z("error",Cr,t=>t.toLowerCase().trim()==="error"),z("---",{db:{clear:r(()=>{},"clear")},styles:{},renderer:{draw:r(()=>{},"draw")},parser:{parse:r(()=>{throw new Error("Diagrams beginning with --- are not valid. If you were trying to use a YAML front-matter, please ensure that you've correctly opened and closed the YAML front-matter with un-indented `---` blocks")},"parse")},init:r(()=>null,"init")},t=>t.toLowerCase().trimStart().startsWith("---")),Z(Wt,_r,wr,hr,rr,or,sr,mr,ur,yr,kr,Zt,Qt,Ir,Pr,ar,Sr,Lr,Ar,dr,zr,$r,gr,qr,Yr,Hr))},"addDiagrams");var Wr=r(async()=>{g.debug("Loading registered diagrams");let e=(await Promise.allSettled(Object.entries(lt).map(async([a,{detector:i,loader:o}])=>{if(o)try{X(a)}catch{try{let{diagram:n,id:m}=await o();z(m,n,i)}catch(n){throw g.error(`Failed to load external diagram with key ${a}. Removing from detectors.`),delete lt[a],n}}}))).filter(a=>a.status==="rejected");if(e.length>0){g.error(`Failed to load ${e.length} external diagrams`);for(let a of e)g.error(a);throw new Error(`Failed to load ${e.length} external diagrams`)}},"loadRegisteredDiagrams");var rt="comm",et="rule",at="decl";var Kr="@import";var Qr="@namespace",Jr="@keyframes";var Zr="@layer";var wt=Math.abs,W=String.fromCharCode;function it(t){return t.trim()}r(it,"trim");function K(t,e,a){return t.replace(e,a)}r(K,"replace");function te(t,e,a){return t.indexOf(e,a)}r(te,"indexof");function j(t,e){return t.charCodeAt(e)|0}r(j,"charat");function I(t,e,a){return t.slice(e,a)}r(I,"substr");function h(t){return t.length}r(h,"strlen");function re(t){return t.length}r(re,"sizeof");function N(t,e){return e.push(t),t}r(N,"append");var ot=1,H=1,ee=0,w=0,D=0,q="";function nt(t,e,a,i,o,n,m,s){return{value:t,root:e,parent:a,type:i,props:o,children:n,line:ot,column:H,length:m,return:"",siblings:s}}r(nt,"node");function ae(){return D}r(ae,"char");function ie(){return D=w>0?j(q,--w):0,H--,D===10&&(H=1,ot--),D}r(ie,"prev");function b(){return D=w2||U(D)>3?"":" "}r(se,"whitespace");function ce(t,e){for(;--e&&b()&&!(D<48||D>102||D>57&&D<65||D>70&&D<97););return st(t,Q()+(e<6&&O()==32&&b()==32))}r(ce,"escaping");function bt(t){for(;b();)switch(D){case t:return w;case 34:case 39:t!==34&&t!==39&&bt(D);break;case 40:t===41&&bt(t);break;case 92:b();break}return w}r(bt,"delimiter");function me(t,e){for(;b()&&t+D!==57;)if(t+D===84&&O()===47)break;return"/*"+st(e,w-1)+"*"+W(t===47?t:b())}r(me,"commenter");function pe(t){for(;!U(O());)b();return st(t,w)}r(pe,"identifier");function ge(t){return ne(mt("",null,null,null,[""],t=oe(t),0,[0],t))}r(ge,"compile");function mt(t,e,a,i,o,n,m,s,c){for(var l=0,y=0,p=m,x=0,A=0,L=0,f=1,C=1,v=1,u=0,S="",R=o,T=n,E=i,d=S;C;)switch(L=u,u=b()){case 40:if(L!=108&&j(d,p-1)==58){te(d+=K(ct(u),"&","&\f"),"&\f",wt(l?s[l-1]:0))!=-1&&(v=-1);break}case 34:case 39:case 91:d+=ct(u);break;case 9:case 10:case 13:case 32:d+=se(L);break;case 92:d+=ce(Q()-1,7);continue;case 47:switch(O()){case 42:case 47:N(ai(me(b(),Q()),e,a,c),c),(U(L||1)==5||U(O()||1)==5)&&h(d)&&I(d,-1,void 0)!==" "&&(d+=" ");break;default:d+="/"}break;case 123*f:s[l++]=h(d)*v;case 125*f:case 59:case 0:switch(u){case 0:case 125:C=0;case 59+y:v==-1&&(d=K(d,/\f/g,"")),A>0&&(h(d)-p||f===0&&L===47)&&N(A>32?fe(d+";",i,a,p-1,c):fe(K(d," ","")+";",i,a,p-2,c),c);break;case 59:d+=";";default:if(N(E=de(d,e,a,l,y,o,s,S,R=[],T=[],p,n),n),u===123)if(y===0)mt(d,e,E,E,R,n,p,s,T);else{switch(x){case 99:if(j(d,3)===110)break;case 108:if(j(d,2)===97)break;default:y=0;case 100:case 109:case 115:}y?mt(t,E,E,i&&N(de(t,E,E,0,0,o,s,S,o,R=[],p,T),T),o,T,p,s,i?R:T):mt(d,E,E,E,[""],T,0,s,T)}}l=y=A=0,f=v=1,S=d="",p=m;break;case 58:p=1+h(d),A=L;default:if(f<1){if(u==123)--f;else if(u==125&&f++==0&&ie()==125)continue}switch(d+=W(u),u*f){case 38:v=y>0?1:(d+="\f",-1);break;case 44:s[l++]=(h(d)-1)*v,v=1;break;case 64:O()===45&&(d+=ct(b())),x=O(),y=p=h(S=d+=pe(Q())),u++;break;case 45:L===45&&h(d)==2&&(f=0)}}return n}r(mt,"parse");function de(t,e,a,i,o,n,m,s,c,l,y,p){for(var x=o-1,A=o===0?n:[""],L=re(A),f=0,C=0,v=0;f0?A[u]+" "+S:K(S,/&\f/g,A[u])))&&(c[v++]=R);return nt(t,e,a,o===0?et:s,c,l,y,p)}r(de,"ruleset");function ai(t,e,a,i){return nt(t,e,a,rt,W(ae()),I(t,2,-2),0,i)}r(ai,"comment");function fe(t,e,a,i,o){return nt(t,e,a,at,I(t,0,i),I(t,i+1,-1),i,o)}r(fe,"declaration");function pt(t,e){for(var a="",i=0;i{ye.forEach(t=>{t()}),ye=[]},"attachFunctions");var he=r(t=>t.replace(/^\s*%%(?!{)[^\n]+\n?/gm,"").trimStart(),"cleanupComments");function Ee(t){let e=t.match(At);if(!e)return{text:t,metadata:{}};let a=qt(e[1],{schema:Ut})??{};a=typeof a=="object"&&!Array.isArray(a)?a:{};let i={};return a.displayMode&&(i.displayMode=a.displayMode.toString()),a.title&&(i.title=a.title.toString()),a.config&&(i.config=a.config),{text:t.slice(e[0].length),metadata:i}}r(Ee,"extractFrontMatter");var ni=r(t=>t.replace(/\r\n?/g,` +`).replace(/<(\w+)([^>]*)>/g,(e,a,i)=>"<"+a+i.replace(/="([^"]*)"/g,"='$1'")+">"),"cleanupText"),si=r(t=>{let{text:e,metadata:a}=Ee(t),{displayMode:i,title:o,config:n={}}=a;return i&&(n.gantt||(n.gantt={}),n.gantt.displayMode=i),{title:o,config:n,text:e}},"processFrontmatter"),ci=r(t=>{let e=V.detectInit(t)??{},a=V.detectDirective(t,"wrap");return Array.isArray(a)?e.wrap=a.some(({type:i})=>i==="wrap"):a?.type==="wrap"&&(e.wrap=!0),{text:Vt(t),directive:e}},"processDirectives");function Lt(t){let e=ni(t),a=si(e),i=ci(a.text),o=$t(a.config,i.directive);return t=he(i.text),{code:t,title:a.title,config:o}}r(Lt,"preprocessDiagram");function we(t){let e=new TextEncoder().encode(t),a=Array.from(e,i=>String.fromCodePoint(i)).join("");return btoa(a)}r(we,"toBase64");var mi=5e4,pi="graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa",di="sandbox",fi="loose",gi="http://www.w3.org/2000/svg",li="http://www.w3.org/1999/xlink",ui="http://www.w3.org/1999/xhtml",Di="100%",yi="100%",xi="border:0;margin:0;",hi="margin:0",Ei="allow-top-navigation-by-user-activation allow-popups",wi='The "iframe" tag is not supported by your browser.',bi=["foreignobject"],Li=["dominant-baseline"];function Se(t){let e=Lt(t);return Y(),It(e.config??{}),e}r(Se,"processAndSetConfigs");async function vi(t,e){$();try{let{code:a,config:i}=Se(t);return{diagramType:(await Me(a)).type,config:i}}catch(a){if(e?.suppressErrors)return!1;throw a}}r(vi,"parse");var be=r((t,e,a=[])=>` +.${t} ${e} { ${a.join(" !important; ")} !important; }`,"cssImportantStyles"),Si=r((t,e=new Map)=>{let a="";if(t.themeCSS!==void 0&&(a+=` +${t.themeCSS}`),t.fontFamily!==void 0&&(a+=` +:root { --mermaid-font-family: ${t.fontFamily}}`),t.altFontFamily!==void 0&&(a+=` +:root { --mermaid-alt-font-family: ${t.altFontFamily}}`),e instanceof Map){let m=t.htmlLabels??t.flowchart?.htmlLabels?["> *","span"]:["rect","polygon","ellipse","circle","path"];e.forEach(s=>{xt(s.styles)||m.forEach(c=>{a+=be(s.id,c,s.styles)}),xt(s.textStyles)||(a+=be(s.id,"tspan",(s?.textStyles||[]).map(c=>c.replace("color","fill"))))})}return a},"createCssStyles"),Mi=r((t,e,a,i)=>{let o=Si(t,a),n=zt(e,o,t.themeVariables);return pt(ge(`${i}{${n}}`),le)},"createUserStyles"),Ai=r((t="",e,a)=>{let i=t;return!a&&!e&&(i=i.replace(/marker-end="url\([\d+./:=?A-Za-z-]*?#/g,'marker-end="url(#')),i=Ht(i),i=i.replace(/
/g,"
"),i},"cleanUpSvgCode"),Ti=r((t="",e)=>{let a=e?.viewBox?.baseVal?.height?e.viewBox.baseVal.height+"px":yi,i=we(`${t}`);return``},"putIntoIFrame"),Le=r((t,e,a,i,o)=>{let n=t.append("div");n.attr("id",a),i&&n.attr("style",i);let m=n.append("svg").attr("id",e).attr("width","100%").attr("xmlns",gi);return o&&m.attr("xmlns:xlink",o),m.append("g"),t},"appendDivSvgG");function ve(t,e){return t.append("iframe").attr("id",e).attr("style","width: 100%; height: 100%;").attr("sandbox","")}r(ve,"sandboxedIframe");var Ci=r((t,e,a,i)=>{t.getElementById(e)?.remove(),t.getElementById(a)?.remove(),t.getElementById(i)?.remove()},"removeExistingElements"),Ri=r(async function(t,e,a){$();let i=Se(e);e=i.code;let o=G();g.debug(o),e.length>(o?.maxTextSize??mi)&&(e=pi);let n="#"+t,m="i"+t,s="#"+m,c="d"+t,l="#"+c,y=r(()=>{let ft=k(x?s:l).node();ft&&"remove"in ft&&ft.remove()},"removeTempElements"),p=k("body"),x=o.securityLevel===di,A=o.securityLevel===fi,L=o.fontFamily;if(a!==void 0){if(a&&(a.innerHTML=""),x){let M=ve(k(a),m);p=k(M.nodes()[0].contentDocument.body),p.node().style.margin=0}else p=k(a);Le(p,t,c,`font-family: ${L}`,li)}else{if(Ci(document,t,c,m),x){let M=ve(k("body"),m);p=k(M.nodes()[0].contentDocument.body),p.node().style.margin=0}else p=k("body");Le(p,t,c)}let f,C;try{f=await B.fromText(e,{title:i.title})}catch(M){if(o.suppressErrorRendering)throw y(),M;f=await B.fromText("error"),C=M}let v=p.select(l).node(),u=f.type,S=v.firstChild,R=S.firstChild,T=f.renderer.getClasses?.(e,f),E=Mi(o,u,T,n),d=document.createElement("style");d.innerHTML=E,S.insertBefore(d,R);try{await f.renderer.draw(e,t,ht.version,f)}catch(M){throw o.suppressErrorRendering?y():Tr.draw(e,t,ht.version),M}let Oe=p.select(`${l} svg`),Pe=f.db.getAccTitle?.(),je=f.db.getAccDescription?.();Oi(u,Oe,Pe,je),p.select(`[id="${t}"]`).selectAll("foreignobject > *").attr("xmlns",ui);let _=p.select(l).node().innerHTML;if(g.debug("config.arrowMarkerAbsolute",o.arrowMarkerAbsolute),_=Ai(_,x,_t(o.arrowMarkerAbsolute)),x){let M=p.select(l+" svg").node();_=Ti(_,M)}else A||(_=Ft.sanitize(_,{ADD_TAGS:bi,ADD_ATTR:Li,HTML_INTEGRATION_POINTS:{foreignobject:!0}}));if(xe(),C)throw C;return y(),{diagramType:u,svg:_,bindFunctions:f.db.bindFunctions}},"render");function ki(t={}){let e=Rt({},t);e?.fontFamily&&!e.themeVariables?.fontFamily&&(e.themeVariables||(e.themeVariables={}),e.themeVariables.fontFamily=e.fontFamily),Ot(e),e?.theme&&e.theme in tt?e.themeVariables=tt[e.theme].getThemeVariables(e.themeVariables):e&&(e.themeVariables=tt.default.getThemeVariables(e.themeVariables));let a=typeof e=="object"?kt(e):Dt();gt(a.logLevel),$()}r(ki,"initialize");var Me=r((t,e={})=>{let{code:a}=Lt(t);return B.fromText(a,e)},"getDiagramFromText");function Oi(t,e,a,i){ue(e,t),De(e,a,i,e.attr("id"))}r(Oi,"addA11yInfo");var F=Object.freeze({render:Ri,parse:vi,getDiagramFromText:Me,initialize:ki,getConfig:G,setConfig:jt,getSiteConfig:Dt,updateSiteConfig:Pt,reset:r(()=>{Y()},"reset"),globalReset:r(()=>{Y(ut)},"globalReset"),defaultConfig:ut});gt(G().logLevel);Y(G());var Pi=r((t,e,a)=>{g.warn(t),yt(t)?(a&&a(t.str,t.hash),e.push({...t,message:t.str,error:t})):(a&&a(t),t instanceof Error&&e.push({str:t.message,message:t.message,hash:t.name,error:t}))},"handleError"),Ae=r(async function(t={querySelector:".mermaid"}){try{await ji(t)}catch(e){if(yt(e)&&g.error(e.str),P.parseError&&P.parseError(e),!t.suppressErrors)throw g.error("Use the suppressErrors option to suppress these errors"),e}},"run"),ji=r(async function({postRenderCallback:t,querySelector:e,nodes:a}={querySelector:".mermaid"}){let i=F.getConfig();g.debug(`${t?"":"No "}Callback function found`);let o;if(a)o=a;else if(e)o=document.querySelectorAll(e);else throw new Error("Nodes and querySelector are both undefined");g.debug(`Found ${o.length} diagrams`),i?.startOnLoad!==void 0&&(g.debug("Start On Load: "+i?.startOnLoad),F.updateSiteConfig({startOnLoad:i?.startOnLoad}));let n=new V.InitIDGenerator(i.deterministicIds,i.deterministicIDSeed),m,s=[];for(let c of Array.from(o)){g.info("Rendering diagram: "+c.id);if(c.getAttribute("data-processed"))continue;c.setAttribute("data-processed","true");let l=`mermaid-${n.next()}`;m=c.innerHTML,m=Mt(V.entityDecode(m)).trim().replace(//gi,"
");let y=V.detectInit(m);y&&g.debug("Detected early reinit: ",y);try{let{svg:p,bindFunctions:x}=await ke(l,m,c);c.innerHTML=p,t&&await t(l),x&&x(c)}catch(p){Pi(p,s,P.parseError)}}if(s.length>0)throw s[0]},"runThrowsErrors"),Te=r(function(t){F.initialize(t)},"initialize"),Ii=r(async function(t,e,a){g.warn("mermaid.init is deprecated. Please use run instead."),t&&Te(t);let i={postRenderCallback:a,querySelector:".mermaid"};typeof e=="string"?i.querySelector=e:e&&(e instanceof HTMLElement?i.nodes=[e]:i.nodes=e),await Ae(i)},"init"),Fi=r(async(t,{lazyLoad:e=!0}={})=>{$(),Z(...t),e===!1&&await Wr()},"registerExternalDiagrams"),Ce=r(function(){if(P.startOnLoad){let{startOnLoad:t}=F.getConfig();t&&P.run().catch(e=>g.error("Mermaid failed to initialize",e))}},"contentLoaded");if(typeof document<"u"){window.addEventListener("load",Ce,!1)}var _i=r(function(t){P.parseError=t},"setParseErrorHandler"),dt=[],vt=!1,Re=r(async()=>{if(!vt){for(vt=!0;dt.length>0;){let t=dt.shift();if(t)try{await t()}catch(e){g.error("Error executing queue",e)}}vt=!1}},"executeQueue"),Gi=r(async(t,e)=>new Promise((a,i)=>{let o=r(()=>new Promise((n,m)=>{F.parse(t,e).then(s=>{n(s),a(s)},s=>{g.error("Error parsing",s),P.parseError?.(s),m(s),i(s)})}),"performCall");dt.push(o),Re().catch(i)}),"parse"),ke=r((t,e,a)=>new Promise((i,o)=>{let n=r(()=>new Promise((m,s)=>{F.render(t,e,a).then(c=>{m(c),i(c)},c=>{g.error("Error parsing",c),P.parseError?.(c),s(c),o(c)})}),"performCall");dt.push(n),Re().catch(o)}),"render"),P={startOnLoad:!0,mermaidAPI:F,parse:Gi,render:ke,init:Ii,run:Ae,registerExternalDiagrams:Fi,registerLayoutLoaders:Bt,initialize:Te,parseError:void 0,contentLoaded:Ce,setParseErrorHandler:_i,detectType:J,registerIconPacks:St},Hs=P;export{Hs as default}; +/*! Check if previously processed */ +/*! + * Wait for document loaded before starting the execution + */ diff --git a/docs/diagrams/preview.html b/docs/diagrams/preview.html new file mode 100644 index 0000000..67a00f8 --- /dev/null +++ b/docs/diagrams/preview.html @@ -0,0 +1,36 @@ + + + + + ORM Template — User ERD + + + +

ORM Template — ER-диаграмма

+
Единственная модель: User и ее атрибуты.
+ +
+erDiagram
+    USER {
+        uuid id PK "первичный ключ пользователя"
+        text login "уникальный логин"
+        text password "хэш пароля"
+        text role "роль пользователя"
+        bool is_active "активность"
+        bool is_temporal "пароль временный, хранится в открытом виде"
+        timestamptz created_at "дата создания"
+    }
+
+ + + + diff --git a/ent/schema/README.md b/ent/schema/README.md new file mode 100644 index 0000000..1e6f4bd --- /dev/null +++ b/ent/schema/README.md @@ -0,0 +1,13 @@ +# Схема базы данных + +Шаблон содержит одну модель — `User`. + +Поля по умолчанию: + +- `id` — UUID, первичный ключ. +- `created_at` — время создания записи. +- `login` — логин пользователя (уникальный). +- `password` — хэш пароля. +- `role` — роль пользователя (по умолчанию `user`). +- `is_active` — активность. +- `is_temporal` — временный пароль (хранится в открытом виде). diff --git a/ent/schema/common.go b/ent/schema/common.go new file mode 100644 index 0000000..ecead63 --- /dev/null +++ b/ent/schema/common.go @@ -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"), + } +} diff --git a/ent/schema/user.go b/ent/schema/user.go new file mode 100644 index 0000000..df2e1ce --- /dev/null +++ b/ent/schema/user.go @@ -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"), + } +}