First swagger version, added auto-migrate on startup
This commit is contained in:
15
.env
15
.env
@@ -0,0 +1,15 @@
|
||||
LOGIFLOW_DATABASE_HOST=postgres
|
||||
LOGIFLOW_DATABASE_PORT=5432
|
||||
LOGIFLOW_DATABASE_USER=postgres
|
||||
LOGIFLOW_DATABASE_PASSWORD=pass1234
|
||||
LOGIFLOW_DATABASE_NAME=logiflow
|
||||
LOGIFLOW_DATABASE_SSLMODE=disable
|
||||
|
||||
LOGIFLOW_REDIS_ADDR=redis:6379
|
||||
LOGIFLOW_REDIS_PASSWORD=redis123
|
||||
LOGIFLOW_JWT_KEY=98c5772ae16aaa4fd0013eb338252a93b198fb40e9337506334b3aeb21abbe4cd9289cdd
|
||||
|
||||
GOOSE_DRIVER=postgres
|
||||
GOOSE_DBSTRING=postgres://${LOGIFLOW_DATABASE_USER}:${LOGIFLOW_DATABASE_PASSWORD}@localhost:${LOGIFLOW_DATABASE_PORT}/${LOGIFLOW_DATABASE_NAME}?sslmode=disable
|
||||
GOOSE_MIGRATION_DIR=./migrations
|
||||
GOOSE_TABLE=goose_migrations
|
||||
40
cmd/main.go
Normal file
40
cmd/main.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
|
||||
"github.com/anxi0uz/logiflow/internal/config"
|
||||
"github.com/golang-cz/devslog"
|
||||
)
|
||||
|
||||
func NewDevLogger() {
|
||||
opts := &devslog.Options{
|
||||
MaxSlicePrintSize: 4,
|
||||
SortKeys: true,
|
||||
TimeFormat: "15:04:05.000",
|
||||
NewLineAfterLog: true,
|
||||
DebugColor: devslog.Cyan,
|
||||
StringerFormatter: true,
|
||||
}
|
||||
|
||||
handler := devslog.NewHandler(os.Stdout, opts)
|
||||
logger := slog.New(handler)
|
||||
|
||||
slog.SetDefault(logger)
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
|
||||
NewDevLogger()
|
||||
|
||||
cfg, err := config.NewConfig(ctx, "configs/config.toml")
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Cant load configs", slog.String("Error", err.Error()))
|
||||
}
|
||||
|
||||
slog.SetLogLoggerLevel(cfg.Logiflow.LogLevel)
|
||||
}
|
||||
12
go.mod
12
go.mod
@@ -3,6 +3,8 @@ module github.com/anxi0uz/logiflow
|
||||
go 1.25.7
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/golang-cz/devslog v0.0.15
|
||||
github.com/huandu/go-sqlbuilder v1.39.1
|
||||
github.com/jackc/pgx/v5 v5.8.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
@@ -10,6 +12,8 @@ require (
|
||||
github.com/knadh/koanf/providers/env v1.1.0
|
||||
github.com/knadh/koanf/providers/file v1.2.1
|
||||
github.com/knadh/koanf/v2 v2.3.2
|
||||
github.com/oapi-codegen/runtime v1.2.0
|
||||
github.com/pressly/goose/v3 v3.27.0
|
||||
github.com/redis/go-redis/v9 v9.18.0
|
||||
)
|
||||
|
||||
@@ -18,17 +22,21 @@ require (
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/huandu/go-clone v1.7.3 // indirect
|
||||
github.com/huandu/xstrings v1.4.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/knadh/koanf/maps v0.1.2 // indirect
|
||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/sync v0.19.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/sys v0.41.0 // indirect
|
||||
golang.org/x/text v0.34.0 // indirect
|
||||
)
|
||||
|
||||
46
go.sum
46
go.sum
@@ -9,10 +9,18 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
|
||||
github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/golang-cz/devslog v0.0.15 h1:ejoBLTCwJHWGbAmDf2fyTJJQO3AkzcPjw8SC9LaOQMI=
|
||||
github.com/golang-cz/devslog v0.0.15/go.mod h1:bSe5bm0A7Nyfqtijf1OMNgVJHlWEuVSXnkuASiE1vV8=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U=
|
||||
github.com/huandu/go-assert v1.1.6 h1:oaAfYxq9KNDi9qswn/6aE0EydfxSa+tWZC1KabNitYs=
|
||||
github.com/huandu/go-assert v1.1.6/go.mod h1:JuIfbmYG9ykwvuxoJ3V8TB5QP+3+ajIA54Y44TmkMxs=
|
||||
@@ -32,8 +40,8 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo=
|
||||
github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||
github.com/knadh/koanf/parsers/toml v0.1.0 h1:S2hLqS4TgWZYj4/7mI5m1CQQcWurxUz6ODgOub/6LCI=
|
||||
@@ -44,16 +52,30 @@ github.com/knadh/koanf/providers/file v1.2.1 h1:bEWbtQwYrA+W2DtdBrQWyXqJaJSG3KrP
|
||||
github.com/knadh/koanf/providers/file v1.2.1/go.mod h1:bp1PM5f83Q+TOUu10J/0ApLBd9uIzg+n9UgthfY+nRA=
|
||||
github.com/knadh/koanf/v2 v2.3.2 h1:Ee6tuzQYFwcZXQpc2MiVeC6qHMandf5SMUJJNoFp/c4=
|
||||
github.com/knadh/koanf/v2 v2.3.2/go.mod h1:gRb40VRAbd4iJMYYD5IxZ6hfuopFcXBpc9bbQpZwo28=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
||||
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/oapi-codegen/runtime v1.2.0 h1:RvKc1CVS1QeKSNzO97FBQbSMZyQ8s6rZd+LpmzwHMP4=
|
||||
github.com/oapi-codegen/runtime v1.2.0/go.mod h1:Y7ZhmmlE8ikZOmuHRRndiIm7nf3xcVv+YMweKgG1DT0=
|
||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pressly/goose/v3 v3.27.0 h1:/D30gVTuQhu0WsNZYbJi4DMOsx1lNq+6SkLe+Wp59BM=
|
||||
github.com/pressly/goose/v3 v3.27.0/go.mod h1:3ZBeCXqzkgIRvrEMDkYh1guvtoJTU5oMMuDdkutoM78=
|
||||
github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfSRQs=
|
||||
github.com/redis/go-redis/v9 v9.18.0/go.mod h1:k3ufPphLU5YXwNTUcCRXGxUoF1fqxnhFQmscfkCoDA0=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
||||
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
@@ -64,14 +86,26 @@ github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa h1:Zt3DZoOFFYkKhDT3v7Lm9FDMEV06GpzjG2jrqW+QTE0=
|
||||
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa/go.mod h1:K79w1Vqn7PoiZn+TkNpx3BUWUQksGO3JcVX6qIjytmA=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/libc v1.68.0 h1:PJ5ikFOV5pwpW+VqCK1hKJuEWsonkIJhhIXyuF/91pQ=
|
||||
modernc.org/libc v1.68.0/go.mod h1:NnKCYeoYgsEqnY3PgvNgAeaJnso968ygU8Z0DxjoEc0=
|
||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||
modernc.org/sqlite v1.46.1 h1:eFJ2ShBLIEnUWlLy12raN0Z1plqmFX9Qe3rjQTKt6sU=
|
||||
modernc.org/sqlite v1.46.1/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
||||
|
||||
@@ -1,5 +1,345 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: logiflow
|
||||
description: API для информационной системы для логистической компании
|
||||
version: 1.0.0
|
||||
title: Logiflow API
|
||||
description: API для логистической информационной системы
|
||||
version: 0.1.0
|
||||
contact:
|
||||
name: anxi0uz
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3001
|
||||
description: Local development
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
|
||||
schemas:
|
||||
ApiResponse:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: integer
|
||||
nullable: false
|
||||
data:
|
||||
type: object
|
||||
nullable: true
|
||||
success:
|
||||
type: boolean
|
||||
nullable: false
|
||||
requestID:
|
||||
type: string
|
||||
nullable: false
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: integer
|
||||
message:
|
||||
type: string
|
||||
requestID:
|
||||
type: string
|
||||
|
||||
User:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
slug:
|
||||
type: string
|
||||
fullName:
|
||||
type: string
|
||||
nullable: true
|
||||
avatarUrl:
|
||||
type: string
|
||||
nullable: true
|
||||
passwordHash:
|
||||
type: string
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
lastLoginAt:
|
||||
type: string
|
||||
format: date-time
|
||||
nullable: true
|
||||
|
||||
UserResponse:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
slug:
|
||||
type: string
|
||||
fullName:
|
||||
type: string
|
||||
nullable: true
|
||||
avatarUrl:
|
||||
type: string
|
||||
nullable: true
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
|
||||
RegisterRequest:
|
||||
type: object
|
||||
required: [email, password, fullName]
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
password:
|
||||
type: string
|
||||
minLength: 8
|
||||
fullName:
|
||||
type: string
|
||||
|
||||
LoginRequest:
|
||||
type: object
|
||||
required: [email, password]
|
||||
properties:
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
password:
|
||||
type: string
|
||||
|
||||
TokenRefreshRequest:
|
||||
type: object
|
||||
required: [refreshToken]
|
||||
properties:
|
||||
refreshToken:
|
||||
type: string
|
||||
description: Refresh токен, полученный при логине
|
||||
|
||||
TokenResponse:
|
||||
type: object
|
||||
required: [accessToken, refreshToken]
|
||||
properties:
|
||||
accessToken:
|
||||
type: string
|
||||
description: Access токен (JWT)
|
||||
refreshToken:
|
||||
type: string
|
||||
description: Refresh токен (opaque token, rotation)
|
||||
expiresIn:
|
||||
type: integer
|
||||
description: Время жизни access токена в секундах
|
||||
|
||||
UserUpdate:
|
||||
type: object
|
||||
properties:
|
||||
fullName:
|
||||
type: string
|
||||
minLength: 2
|
||||
maxLength: 150
|
||||
nullable: true
|
||||
avatarUrl:
|
||||
type: string
|
||||
format: uri
|
||||
nullable: true
|
||||
password:
|
||||
type: string
|
||||
minLength: 8
|
||||
description: Новый пароль (если меняется)
|
||||
currentPassword:
|
||||
type: string
|
||||
minLength: 8
|
||||
description: Текущий пароль (обязателен при смене пароля)
|
||||
|
||||
UserDeleteRequest:
|
||||
type: object
|
||||
required: [password]
|
||||
properties:
|
||||
password:
|
||||
type: string
|
||||
description: Текущий пароль для подтверждения удаления
|
||||
|
||||
paths:
|
||||
/auth/register:
|
||||
post:
|
||||
operationId: authRegister
|
||||
summary: Регистрация нового пользователя
|
||||
tags: [Auth]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/RegisterRequest"
|
||||
responses:
|
||||
"201":
|
||||
description: Пользователь успешно создан
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ApiResponse"
|
||||
"400":
|
||||
description: Некорректные данные
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
"409":
|
||||
description: Email уже занят
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
/auth/login:
|
||||
post:
|
||||
operationId: authLogin
|
||||
summary: Авторизация пользователя
|
||||
tags: [Auth]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/LoginRequest"
|
||||
responses:
|
||||
"200":
|
||||
description: Успешный вход
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ApiResponse"
|
||||
"401":
|
||||
description: Неверный email или пароль
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
/auth/refresh:
|
||||
post:
|
||||
operationId: authRefresh
|
||||
summary: Обновление access-токена через refresh-токен
|
||||
tags: [Auth]
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/TokenRefreshRequest"
|
||||
responses:
|
||||
"200":
|
||||
description: Токены успешно обновлены
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ApiResponse"
|
||||
"401":
|
||||
description: Недействительный или истёкший refresh-токен
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
"400":
|
||||
description: Некорректный запрос
|
||||
|
||||
/auth/logout:
|
||||
post:
|
||||
operationId: authLogout
|
||||
summary: Выход из системы
|
||||
tags: [Auth]
|
||||
security:
|
||||
- BearerAuth: []
|
||||
responses:
|
||||
"204":
|
||||
description: Успешный выход
|
||||
"401":
|
||||
description: Не авторизован
|
||||
|
||||
/me:
|
||||
get:
|
||||
operationId: getMe
|
||||
summary: Получить текущего пользователя
|
||||
tags: [Me]
|
||||
security:
|
||||
- BearerAuth: []
|
||||
responses:
|
||||
"200":
|
||||
description: Данные пользователя
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ApiResponse"
|
||||
"401":
|
||||
description: Не авторизован
|
||||
|
||||
patch:
|
||||
operationId: updateMe
|
||||
summary: Обновить данные текущего пользователя
|
||||
tags: [Me]
|
||||
security:
|
||||
- BearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UserUpdate"
|
||||
responses:
|
||||
"200":
|
||||
description: Пользователь обновлён
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ApiResponse"
|
||||
"400":
|
||||
description: Некорректные данные
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
"401":
|
||||
description: Не авторизован
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
delete:
|
||||
operationId: deleteMe
|
||||
summary: Удалить аккаунт
|
||||
tags: [Me]
|
||||
security:
|
||||
- BearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/UserDeleteRequest"
|
||||
responses:
|
||||
"204":
|
||||
description: Аккаунт удалён
|
||||
"400":
|
||||
description: Неверный пароль
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
"401":
|
||||
description: Не авторизован
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "#/components/schemas/ErrorResponse"
|
||||
|
||||
424
internal/api/gen.go
Normal file
424
internal/api/gen.go
Normal file
@@ -0,0 +1,424 @@
|
||||
// Package api provides primitives to interact with the openapi HTTP API.
|
||||
//
|
||||
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.6.0 DO NOT EDIT.
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
openapi_types "github.com/oapi-codegen/runtime/types"
|
||||
)
|
||||
|
||||
const (
|
||||
BearerAuthScopes = "BearerAuth.Scopes"
|
||||
)
|
||||
|
||||
// ApiResponse defines model for ApiResponse.
|
||||
type ApiResponse struct {
|
||||
Data *map[string]interface{} `json:"data,omitempty"`
|
||||
RequestID *string `json:"requestID,omitempty"`
|
||||
Status *int `json:"status,omitempty"`
|
||||
Success *bool `json:"success,omitempty"`
|
||||
}
|
||||
|
||||
// ErrorResponse defines model for ErrorResponse.
|
||||
type ErrorResponse struct {
|
||||
Message *string `json:"message,omitempty"`
|
||||
RequestID *string `json:"requestID,omitempty"`
|
||||
Status *int `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// LoginRequest defines model for LoginRequest.
|
||||
type LoginRequest struct {
|
||||
Email openapi_types.Email `json:"email"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// RegisterRequest defines model for RegisterRequest.
|
||||
type RegisterRequest struct {
|
||||
Email openapi_types.Email `json:"email"`
|
||||
FullName string `json:"fullName"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// TokenRefreshRequest defines model for TokenRefreshRequest.
|
||||
type TokenRefreshRequest struct {
|
||||
// RefreshToken Refresh токен, полученный при логине
|
||||
RefreshToken string `json:"refreshToken"`
|
||||
}
|
||||
|
||||
// UserDeleteRequest defines model for UserDeleteRequest.
|
||||
type UserDeleteRequest struct {
|
||||
// Password Текущий пароль для подтверждения удаления
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
// UserUpdate defines model for UserUpdate.
|
||||
type UserUpdate struct {
|
||||
AvatarUrl *string `json:"avatarUrl,omitempty"`
|
||||
|
||||
// CurrentPassword Текущий пароль (обязателен при смене пароля)
|
||||
CurrentPassword *string `json:"currentPassword,omitempty"`
|
||||
FullName *string `json:"fullName,omitempty"`
|
||||
|
||||
// Password Новый пароль (если меняется)
|
||||
Password *string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
// AuthLoginJSONRequestBody defines body for AuthLogin for application/json ContentType.
|
||||
type AuthLoginJSONRequestBody = LoginRequest
|
||||
|
||||
// AuthRefreshJSONRequestBody defines body for AuthRefresh for application/json ContentType.
|
||||
type AuthRefreshJSONRequestBody = TokenRefreshRequest
|
||||
|
||||
// AuthRegisterJSONRequestBody defines body for AuthRegister for application/json ContentType.
|
||||
type AuthRegisterJSONRequestBody = RegisterRequest
|
||||
|
||||
// DeleteMeJSONRequestBody defines body for DeleteMe for application/json ContentType.
|
||||
type DeleteMeJSONRequestBody = UserDeleteRequest
|
||||
|
||||
// UpdateMeJSONRequestBody defines body for UpdateMe for application/json ContentType.
|
||||
type UpdateMeJSONRequestBody = UserUpdate
|
||||
|
||||
// ServerInterface represents all server handlers.
|
||||
type ServerInterface interface {
|
||||
// Авторизация пользователя
|
||||
// (POST /auth/login)
|
||||
AuthLogin(w http.ResponseWriter, r *http.Request)
|
||||
// Выход из системы
|
||||
// (POST /auth/logout)
|
||||
AuthLogout(w http.ResponseWriter, r *http.Request)
|
||||
// Обновление access-токена через refresh-токен
|
||||
// (POST /auth/refresh)
|
||||
AuthRefresh(w http.ResponseWriter, r *http.Request)
|
||||
// Регистрация нового пользователя
|
||||
// (POST /auth/register)
|
||||
AuthRegister(w http.ResponseWriter, r *http.Request)
|
||||
// Удалить аккаунт
|
||||
// (DELETE /me)
|
||||
DeleteMe(w http.ResponseWriter, r *http.Request)
|
||||
// Получить текущего пользователя
|
||||
// (GET /me)
|
||||
GetMe(w http.ResponseWriter, r *http.Request)
|
||||
// Обновить данные текущего пользователя
|
||||
// (PATCH /me)
|
||||
UpdateMe(w http.ResponseWriter, r *http.Request)
|
||||
}
|
||||
|
||||
// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint.
|
||||
|
||||
type Unimplemented struct{}
|
||||
|
||||
// Авторизация пользователя
|
||||
// (POST /auth/login)
|
||||
func (_ Unimplemented) AuthLogin(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// Выход из системы
|
||||
// (POST /auth/logout)
|
||||
func (_ Unimplemented) AuthLogout(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// Обновление access-токена через refresh-токен
|
||||
// (POST /auth/refresh)
|
||||
func (_ Unimplemented) AuthRefresh(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// Регистрация нового пользователя
|
||||
// (POST /auth/register)
|
||||
func (_ Unimplemented) AuthRegister(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// Удалить аккаунт
|
||||
// (DELETE /me)
|
||||
func (_ Unimplemented) DeleteMe(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// Получить текущего пользователя
|
||||
// (GET /me)
|
||||
func (_ Unimplemented) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// Обновить данные текущего пользователя
|
||||
// (PATCH /me)
|
||||
func (_ Unimplemented) UpdateMe(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotImplemented)
|
||||
}
|
||||
|
||||
// ServerInterfaceWrapper converts contexts to parameters.
|
||||
type ServerInterfaceWrapper struct {
|
||||
Handler ServerInterface
|
||||
HandlerMiddlewares []MiddlewareFunc
|
||||
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
|
||||
}
|
||||
|
||||
type MiddlewareFunc func(http.Handler) http.Handler
|
||||
|
||||
// AuthLogin operation middleware
|
||||
func (siw *ServerInterfaceWrapper) AuthLogin(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.AuthLogin(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// AuthLogout operation middleware
|
||||
func (siw *ServerInterfaceWrapper) AuthLogout(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.AuthLogout(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// AuthRefresh operation middleware
|
||||
func (siw *ServerInterfaceWrapper) AuthRefresh(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.AuthRefresh(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// AuthRegister operation middleware
|
||||
func (siw *ServerInterfaceWrapper) AuthRegister(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.AuthRegister(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// DeleteMe operation middleware
|
||||
func (siw *ServerInterfaceWrapper) DeleteMe(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.DeleteMe(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// GetMe operation middleware
|
||||
func (siw *ServerInterfaceWrapper) GetMe(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.GetMe(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// UpdateMe operation middleware
|
||||
func (siw *ServerInterfaceWrapper) UpdateMe(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, BearerAuthScopes, []string{})
|
||||
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
siw.Handler.UpdateMe(w, r)
|
||||
}))
|
||||
|
||||
for _, middleware := range siw.HandlerMiddlewares {
|
||||
handler = middleware(handler)
|
||||
}
|
||||
|
||||
handler.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
type UnescapedCookieParamError struct {
|
||||
ParamName string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *UnescapedCookieParamError) Error() string {
|
||||
return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName)
|
||||
}
|
||||
|
||||
func (e *UnescapedCookieParamError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
type UnmarshalingParamError struct {
|
||||
ParamName string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *UnmarshalingParamError) Error() string {
|
||||
return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error())
|
||||
}
|
||||
|
||||
func (e *UnmarshalingParamError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
type RequiredParamError struct {
|
||||
ParamName string
|
||||
}
|
||||
|
||||
func (e *RequiredParamError) Error() string {
|
||||
return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName)
|
||||
}
|
||||
|
||||
type RequiredHeaderError struct {
|
||||
ParamName string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *RequiredHeaderError) Error() string {
|
||||
return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName)
|
||||
}
|
||||
|
||||
func (e *RequiredHeaderError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
type InvalidParamFormatError struct {
|
||||
ParamName string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e *InvalidParamFormatError) Error() string {
|
||||
return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error())
|
||||
}
|
||||
|
||||
func (e *InvalidParamFormatError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
type TooManyValuesForParamError struct {
|
||||
ParamName string
|
||||
Count int
|
||||
}
|
||||
|
||||
func (e *TooManyValuesForParamError) Error() string {
|
||||
return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count)
|
||||
}
|
||||
|
||||
// Handler creates http.Handler with routing matching OpenAPI spec.
|
||||
func Handler(si ServerInterface) http.Handler {
|
||||
return HandlerWithOptions(si, ChiServerOptions{})
|
||||
}
|
||||
|
||||
type ChiServerOptions struct {
|
||||
BaseURL string
|
||||
BaseRouter chi.Router
|
||||
Middlewares []MiddlewareFunc
|
||||
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
|
||||
}
|
||||
|
||||
// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux.
|
||||
func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler {
|
||||
return HandlerWithOptions(si, ChiServerOptions{
|
||||
BaseRouter: r,
|
||||
})
|
||||
}
|
||||
|
||||
func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler {
|
||||
return HandlerWithOptions(si, ChiServerOptions{
|
||||
BaseURL: baseURL,
|
||||
BaseRouter: r,
|
||||
})
|
||||
}
|
||||
|
||||
// HandlerWithOptions creates http.Handler with additional options
|
||||
func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler {
|
||||
r := options.BaseRouter
|
||||
|
||||
if r == nil {
|
||||
r = chi.NewRouter()
|
||||
}
|
||||
if options.ErrorHandlerFunc == nil {
|
||||
options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
wrapper := ServerInterfaceWrapper{
|
||||
Handler: si,
|
||||
HandlerMiddlewares: options.Middlewares,
|
||||
ErrorHandlerFunc: options.ErrorHandlerFunc,
|
||||
}
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Post(options.BaseURL+"/auth/login", wrapper.AuthLogin)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Post(options.BaseURL+"/auth/logout", wrapper.AuthLogout)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Post(options.BaseURL+"/auth/refresh", wrapper.AuthRefresh)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Post(options.BaseURL+"/auth/register", wrapper.AuthRegister)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Delete(options.BaseURL+"/me", wrapper.DeleteMe)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Get(options.BaseURL+"/me", wrapper.GetMe)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Patch(options.BaseURL+"/me", wrapper.UpdateMe)
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
@@ -66,8 +66,8 @@ func NewConfig(ctx context.Context, configPath string) (*Config, error) {
|
||||
|
||||
k := koanf.New(".")
|
||||
|
||||
if err := k.Load(env.Provider("HANDBOOKS_", ".", func(s string) string {
|
||||
return strings.ReplaceAll(strings.ToLower(strings.TrimPrefix(s, "HANDBOOKS_")), "_", ".")
|
||||
if err := k.Load(env.Provider("LOGIFLOW_", ".", func(s string) string {
|
||||
return strings.ReplaceAll(strings.ToLower(strings.TrimPrefix(s, "LOGIFLOW_")), "_", ".")
|
||||
}), nil); err != nil {
|
||||
return nil, fmt.Errorf("ошибка загрузки ENV: %w", err)
|
||||
}
|
||||
|
||||
@@ -2,9 +2,18 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/pressly/goose/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
pgDriverName = "pgx"
|
||||
migrationsDir = "./migrations"
|
||||
gooseDriverName = "postgres"
|
||||
)
|
||||
|
||||
func NewConnectionPool(ctx context.Context, connectionString string) (*pgxpool.Pool, error) {
|
||||
@@ -21,3 +30,29 @@ func NewConnectionPool(ctx context.Context, connectionString string) (*pgxpool.P
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func RunMigrations(ctx context.Context, dbURL string) error {
|
||||
db, err := sql.Open(pgDriverName, dbURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("не удалось открыть соединение для миграций: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
goose.SetDialect(gooseDriverName)
|
||||
|
||||
statusErr := goose.Status(db, migrationsDir)
|
||||
if statusErr != nil {
|
||||
slog.WarnContext(ctx, "Не удалось получить статус миграций", "error", statusErr)
|
||||
}
|
||||
|
||||
current, _ := goose.GetDBVersion(db)
|
||||
slog.InfoContext(ctx, "Текущая версия БД", "version", current)
|
||||
|
||||
slog.DebugContext(ctx, "Запуск миграций Goose...")
|
||||
if err := goose.Up(db, migrationsDir); err != nil {
|
||||
return fmt.Errorf("ошибка применения миграций: %w", err)
|
||||
}
|
||||
|
||||
slog.InfoContext(ctx, "Миграции успешно применены или уже актуальны")
|
||||
return nil
|
||||
}
|
||||
|
||||
0
internal/handler/server_impl.go
Normal file
0
internal/handler/server_impl.go
Normal file
19
internal/models/user.go
Normal file
19
internal/models/user.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
Email string `db:"email" json:"email"`
|
||||
Slug string `db:"slug" json:"slug"`
|
||||
PasswordHash string `db:"password_hash" json:"-"`
|
||||
FullName *string `db:"full_name" json:"full_name,omitempty"`
|
||||
AvatarURL *string `db:"avatar_url" json:"avatar_url,omitempty"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt *time.Time `db:"updated_at" json:"updated_at,omitempty"`
|
||||
LastLoginAt *time.Time `db:"last_login_at" json:"last_login_at,omitempty"`
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE EXTENSION IF NOT EXISTS pg_tgrm;
|
||||
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP EXTENSION IF EXISTS pg_tgrm;
|
||||
DROP EXTENSION IF EXISTS pg_trgm;
|
||||
-- +goose StatementEnd
|
||||
|
||||
@@ -7,7 +7,6 @@ CREATE TABLE IF NOT EXISTS users (
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
full_name VARCHAR(150),
|
||||
avatar_url VARCHAR(512),
|
||||
role VARCHAR(50) DEFAULT 'student',
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ,
|
||||
last_login_at TIMESTAMPTZ
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
SELECT 'up SQL query';
|
||||
CREATE TABLE IF NOT EXISTS vehicles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
plate_number VARCHAR(20) UNIQUE NOT NULL,
|
||||
brand VARCHAR(50),
|
||||
model VARCHAR(50),
|
||||
year INTEGER,
|
||||
capacity_kg NUMERIC(10,2),
|
||||
capacity_m3 NUMERIC(10,2),
|
||||
status VARCHAR(20) DEFAULT 'available',
|
||||
slug VARCHAR(120) UNIQUE NOT NULL
|
||||
);
|
||||
|
||||
|
||||
CREATE INDEX idx_vehicles_slug ON vehicles(slug);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
SELECT 'down SQL query';
|
||||
DROP TABLE IF EXISTS vehicles;
|
||||
-- +goose StatementEnd
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
SELECT 'up SQL query';
|
||||
CREATE TABLE IF NOT EXISTS drivers(
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
vehicle_id UUID REFERENCES vehicles(id) ON DELETE SET NULL,
|
||||
license_number VARCHAR (50) NOT NULL,
|
||||
license_expiry DATE NOT NULL,
|
||||
rating NUMERIC(3,2) DEFAULT 5.00,
|
||||
slug VARCHAR(120) UNIQUE NOT NULL,
|
||||
status VARCHAR(20) DEFAULT 'available'
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
SELECT 'down SQL query';
|
||||
DROP TABLE IF EXISTS drivers;
|
||||
-- +goose StatementEnd
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
SELECT 'up SQL query';
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
SELECT 'down SQL query';
|
||||
-- +goose StatementEnd
|
||||
14
migrations/20260304163413_create_roles.sql
Normal file
14
migrations/20260304163413_create_roles.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE IF NOT EXISTS roles(
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
title VARCHAR(20),
|
||||
code VARCHAR(20)
|
||||
);
|
||||
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE IF EXISTS roles;
|
||||
-- +goose StatementEnd
|
||||
13
migrations/20260304163423_create_users_roles.sql
Normal file
13
migrations/20260304163423_create_users_roles.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
CREATE TABLE IF NOT EXISTS user_roles(
|
||||
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
||||
role_id UUID REFERENCES roles(id) ON DELETE CASCADE,
|
||||
PRIMARY KEY (user_id, role_id)
|
||||
);
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
-- +goose StatementBegin
|
||||
DROP TABLE IF EXISTS user_roles;
|
||||
-- +goose StatementEnd
|
||||
@@ -1,4 +1,4 @@
|
||||
package models
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
Reference in New Issue
Block a user