Merge pull request #2 from anxi0uz/feature/back-02
Added dockerfile, docker compose, created server implementation with …
This commit is contained in:
2
.env
2
.env
@@ -12,4 +12,4 @@ LOGIFLOW_JWT_KEY=98c5772ae16aaa4fd0013eb338252a93b198fb40e9337506334b3aeb21abbe4
|
||||
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
|
||||
GOOSE_TABLE=goose_migrations
|
||||
|
||||
25
Dockerfile
Normal file
25
Dockerfile
Normal file
@@ -0,0 +1,25 @@
|
||||
FROM docker.io/library/golang:1.25-alpine AS builder
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -o /bin/logiflow ./cmd/main.go
|
||||
|
||||
FROM docker.io/library/alpine:3.20
|
||||
|
||||
RUN addgroup -g 1000 app && adduser -u 1000 -G app -D -H -s /sbin/nologin app
|
||||
|
||||
COPY --from=builder /bin/logiflow /app/logiflow
|
||||
COPY --chown=app:app configs/ /app/configs/
|
||||
COPY --chown=app:app migrations/ /app/migrations/
|
||||
|
||||
USER app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
EXPOSE 3001
|
||||
|
||||
CMD ["/app/logiflow"]
|
||||
42
cmd/main.go
42
cmd/main.go
@@ -4,8 +4,12 @@ import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/anxi0uz/logiflow/internal/config"
|
||||
"github.com/anxi0uz/logiflow/internal/database"
|
||||
"github.com/anxi0uz/logiflow/internal/handler"
|
||||
"github.com/golang-cz/devslog"
|
||||
)
|
||||
|
||||
@@ -34,7 +38,45 @@ func main() {
|
||||
cfg, err := config.NewConfig(ctx, "configs/config.toml")
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Cant load configs", slog.String("Error", err.Error()))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
slog.SetLogLoggerLevel(cfg.Logiflow.LogLevel)
|
||||
connectionPool, err := database.NewConnectionPool(ctx, cfg.DatabaseURL())
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Ошибка подключения к postgres", slog.String("error", err.Error()))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer connectionPool.Close()
|
||||
|
||||
redis, err := database.NewRedisConnection(ctx, cfg)
|
||||
if err != nil {
|
||||
slog.ErrorContext(ctx, "Ошибка подключения к redis", slog.String("error", err.Error()))
|
||||
os.Exit(1)
|
||||
}
|
||||
defer redis.Close()
|
||||
|
||||
if err := database.RunMigrations(ctx, cfg.DatabaseURL()); err != nil {
|
||||
slog.ErrorContext(ctx, "Ошибка миграций", slog.String("error", err.Error()))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP)
|
||||
|
||||
serverErr := make(chan error, 1)
|
||||
go func() {
|
||||
serverErr <- handler.NewServer(connectionPool, redis, cfg).Run()
|
||||
}()
|
||||
|
||||
select {
|
||||
case sig := <-sigChan:
|
||||
slog.Info("Получен сигнал завершения", "signal", sig.String())
|
||||
case err := <-serverErr:
|
||||
slog.Error("Сервер упал", "error", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
cancel()
|
||||
|
||||
slog.Info("Приложение остановлено")
|
||||
}
|
||||
|
||||
11
configs/prometheus.yml
Normal file
11
configs/prometheus.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: "prometheus"
|
||||
static_configs:
|
||||
- targets: ["localhost:9090"]
|
||||
|
||||
- job_name: "handbooks"
|
||||
static_configs:
|
||||
- targets: ["app:3001"]
|
||||
8
go.mod
8
go.mod
@@ -4,7 +4,10 @@ go 1.25.7
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.2.5
|
||||
github.com/go-chi/cors v1.2.2
|
||||
github.com/golang-cz/devslog v0.0.15
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/huandu/go-sqlbuilder v1.39.1
|
||||
github.com/jackc/pgx/v5 v5.8.0
|
||||
github.com/joho/godotenv v1.5.1
|
||||
@@ -15,6 +18,8 @@ require (
|
||||
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
|
||||
github.com/rs/xid v1.6.0
|
||||
github.com/samber/slog-chi v1.19.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -22,7 +27,6 @@ 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
|
||||
@@ -34,6 +38,8 @@ require (
|
||||
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.opentelemetry.io/otel v1.40.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.40.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
|
||||
|
||||
14
go.sum
14
go.sum
@@ -15,10 +15,16 @@ github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S
|
||||
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-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
||||
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/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
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=
|
||||
@@ -74,6 +80,10 @@ github.com/redis/go-redis/v9 v9.18.0 h1:pMkxYPkEbMPwRdenAzUNyFNrDgHx9U+DrBabWNfS
|
||||
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/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
github.com/samber/slog-chi v1.19.0 h1:fl4qH5Hhk7feHtyp4CxJUt7U1TqjPrZ1uueDW9D+Cps=
|
||||
github.com/samber/slog-chi v1.19.0/go.mod h1:a1iIuofF2gS1ii8aXIQhC6TEguLOhOvSM958fY5hToU=
|
||||
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=
|
||||
@@ -84,6 +94,10 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/zeebo/xxh3 v1.0.2 h1:xZmwmqxHZA8AI603jOQ0tMqmBr9lPeFwGg6d+xy9DC0=
|
||||
github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA=
|
||||
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||
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=
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"log/slog"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
"github.com/pressly/goose/v3"
|
||||
)
|
||||
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/anxi0uz/logiflow/internal/api"
|
||||
"github.com/anxi0uz/logiflow/internal/config"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/rs/xid"
|
||||
slogchi "github.com/samber/slog-chi"
|
||||
)
|
||||
|
||||
type ctxKey string
|
||||
|
||||
const (
|
||||
requestIDKey ctxKey = "X-Request-ID"
|
||||
tokenKey ctxKey = "Authorization"
|
||||
)
|
||||
|
||||
type responseOptions struct {
|
||||
respType string
|
||||
requestID string
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
DB *pgxpool.Pool
|
||||
Config *config.Config
|
||||
ctx context.Context
|
||||
Redis *redis.Client
|
||||
JwtKey []byte
|
||||
}
|
||||
|
||||
func NewServer(db *pgxpool.Pool, redis *redis.Client, cfg *config.Config) *Server {
|
||||
return &Server{
|
||||
DB: db,
|
||||
Redis: redis,
|
||||
ctx: context.Background(),
|
||||
Config: cfg,
|
||||
JwtKey: []byte(cfg.JwtOpt.Key),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Run() error {
|
||||
r := chi.NewMux()
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{"https://*", "http://*"},
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
||||
ExposedHeaders: []string{"Link"},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 300,
|
||||
}))
|
||||
|
||||
r.Use(slogchi.NewWithConfig(slog.Default(), slogchi.Config{
|
||||
DefaultLevel: slog.LevelInfo,
|
||||
ClientErrorLevel: slog.LevelWarn, // 400–499 → Warn
|
||||
ServerErrorLevel: slog.LevelError, // 500+ → Error
|
||||
WithRequestID: true, // берёт request-id из контекста
|
||||
Filters: []slogchi.Filter{
|
||||
slogchi.IgnorePath("/health", "/metrics", "/favicon.ico"),
|
||||
},
|
||||
}))
|
||||
|
||||
r.Use(s.AuthMiddleware)
|
||||
r.Use(s.MiddlewareRequestID)
|
||||
|
||||
h := api.HandlerFromMux(s, r)
|
||||
|
||||
srv := &http.Server{
|
||||
Handler: h,
|
||||
Addr: s.Config.ServerURL(),
|
||||
ReadTimeout: s.Config.ReadTimeout(),
|
||||
IdleTimeout: s.Config.IdleTimeout(),
|
||||
WriteTimeout: s.Config.WriteTimeout(),
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||
slog.Error("HTTP сервер упал", "error", err)
|
||||
}
|
||||
}()
|
||||
|
||||
slog.Info("Приложение запущено успешно ", slog.String("URL", s.Config.ServerURL()))
|
||||
|
||||
<-s.ctx.Done()
|
||||
|
||||
slog.Info("Остановка HTTP сервера...")
|
||||
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer shutdownCancel()
|
||||
|
||||
return srv.Shutdown(shutdownCtx)
|
||||
}
|
||||
|
||||
func (s *Server) AuthMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/auth/register" || r.URL.Path == "/auth/login" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
tokenStr := r.Header.Get("Authorization")
|
||||
if tokenStr == "" {
|
||||
s.JSON(w, r, http.StatusUnauthorized, "missing token", "error")
|
||||
return
|
||||
}
|
||||
|
||||
slog.Info("Проверка авторизации")
|
||||
|
||||
claims, err := s.validateAccessToken(r.Context(), tokenStr)
|
||||
if err != nil {
|
||||
slog.WarnContext(r.Context(), "Токен не прошел валидацию", slog.String("Token", tokenStr), slog.String("error", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
key := "access_hash:" + tokenStr
|
||||
if _, err := s.Redis.Get(r.Context(), key).Result(); err == redis.Nil {
|
||||
s.JSON(w, r, http.StatusUnauthorized, "token revoked or expired", "error")
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), "user", claims)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) MiddlewareRequestID(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
rid := r.Header.Get("X-Request-ID")
|
||||
if rid == "" {
|
||||
rid = xid.New().String()
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), requestIDKey, rid)
|
||||
|
||||
w.Header().Set("X-Request-ID", rid)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Server) JSON(w http.ResponseWriter, r *http.Request, status int, payload any, respType string) {
|
||||
options := responseOptions{
|
||||
respType: respType,
|
||||
requestID: extractRequestID(r),
|
||||
}
|
||||
|
||||
success := status >= 200 && status < 300
|
||||
|
||||
resp := api.ApiResponse{
|
||||
RequestID: &options.requestID,
|
||||
Status: &status,
|
||||
Success: &success,
|
||||
Data: &map[string]interface{}{respType: payload},
|
||||
}
|
||||
w.Header().Set("Content-Type", "applicaton/json; charset=utf-8")
|
||||
w.WriteHeader(status)
|
||||
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
slog.ErrorContext(r.Context(), "json encode failed after header written",
|
||||
slog.Int("status", status),
|
||||
slog.String("error", err.Error()),
|
||||
slog.String("request_id", options.requestID),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func extractRequestID(r *http.Request) string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
if v := r.Context().Value(requestIDKey); v != nil {
|
||||
if id, ok := v.(string); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Server) validateAccessToken(ctx context.Context, tokenStr string) (*Claims, error) {
|
||||
claims := &Claims{}
|
||||
token, err := jwt.ParseWithClaims(tokenStr, claims, func(t *jwt.Token) (interface{}, error) {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
slog.WarnContext(ctx, "неизвестный алгоритм", "alg", t.Header["alg"])
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
|
||||
}
|
||||
return s.JwtKey, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("token parse error: %w", err)
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
return nil, errors.New("invalid token")
|
||||
}
|
||||
|
||||
if token.Header["alg"] != "HS256" {
|
||||
return nil, errors.New("only HS256 allowed")
|
||||
}
|
||||
|
||||
redisKey := "access_hash:" + tokenStr
|
||||
if _, err := s.Redis.Get(ctx, redisKey).Result(); err != nil {
|
||||
slog.ErrorContext(ctx, "redis error during token validation", "error", err.Error())
|
||||
return nil, fmt.Errorf("redis error: %w", err)
|
||||
}
|
||||
|
||||
if claims.TokenID == "" {
|
||||
return nil, errors.New("missing token id (jti)")
|
||||
}
|
||||
|
||||
if claims.ID == uuid.Nil {
|
||||
return nil, errors.New("missing user id in claims")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
26
internal/handler/user.go
Normal file
26
internal/handler/user.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Claims struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Email string `json:"email"`
|
||||
Role string `json:"role"`
|
||||
TokenID string `json:"token_id"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func (s *Server) AuthLogin(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
}
|
||||
func (s *Server) AuthLogout(w http.ResponseWriter, r *http.Request) {}
|
||||
func (s *Server) AuthRefresh(w http.ResponseWriter, r *http.Request) {}
|
||||
func (s *Server) AuthRegister(w http.ResponseWriter, r *http.Request) {}
|
||||
func (s *Server) DeleteMe(w http.ResponseWriter, r *http.Request) {}
|
||||
func (s *Server) GetMe(w http.ResponseWriter, r *http.Request) {}
|
||||
func (s *Server) UpdateMe(w http.ResponseWriter, r *http.Request) {}
|
||||
98
podman-compose.yml
Normal file
98
podman-compose.yml
Normal file
@@ -0,0 +1,98 @@
|
||||
networks:
|
||||
default:
|
||||
name: LogiflowNetwork
|
||||
external: true
|
||||
|
||||
services:
|
||||
postgres:
|
||||
networks:
|
||||
- default
|
||||
image: docker.io/library/postgres:latest
|
||||
restart: always
|
||||
container_name: logiflow-postgres
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql
|
||||
- ./migrations:/docker-entrypoint-initdb.d
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${LOGIFLOW_DATABASE_PASSWORD}
|
||||
POSTGRES_USER: ${LOGIFLOW_DATABASE_USER}
|
||||
POSTGRES_DB: ${LOGIFLOW_DATABASE_NAME}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${LOGIFLOW_DATABASE_USER}"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
redis:
|
||||
networks:
|
||||
- default
|
||||
image: docker.io/library/redis:7-alpine
|
||||
container_name: logiflow-redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
command: redis-server --requirepass ${LOGIFLOW_REDIS_PASSWORD}
|
||||
env_file:
|
||||
- .env
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "${LOGIFLOW_REDIS_PASSWORD}", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
prometheus:
|
||||
networks:
|
||||
- default
|
||||
image: docker.io/prom/prometheus:latest
|
||||
container_name: logiflow-prometheus
|
||||
ports:
|
||||
- "9090:9090"
|
||||
volumes:
|
||||
- ./configs/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||||
command:
|
||||
- "--config.file=/etc/prometheus/prometheus.yml"
|
||||
|
||||
grafana:
|
||||
networks:
|
||||
- default
|
||||
image: docker.io/grafana/grafana:latest
|
||||
container_name: logiflow-grafana
|
||||
ports:
|
||||
- "3000:3000"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
GF_SECURITY_ADMIN_USER: admin
|
||||
GF_SECURITY_ADMIN_PASSWORD: admin
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
- ./configs/datasources:/etc/grafana/provisioning/datasources:ro
|
||||
depends_on:
|
||||
- prometheus
|
||||
|
||||
app:
|
||||
networks:
|
||||
- default
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: logiflow-app
|
||||
ports:
|
||||
- "3001:3001"
|
||||
env_file:
|
||||
- .env
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
redis_data:
|
||||
grafana_data:
|
||||
Reference in New Issue
Block a user