From cf2c133ba60531433e37aff41ad5cad09019cc40 Mon Sep 17 00:00:00 2001 From: anxi0uz Date: Wed, 18 Mar 2026 20:51:30 +0500 Subject: [PATCH] Full auth handlers, refactored swagger and some other changes --- go.mod | 3 + go.sum | 6 + internal/api/api.swagger.yaml | 8 - internal/api/gen.go | 29 -- internal/handler/server_impl.go | 11 +- internal/handler/user.go | 261 +++++++++++++++++- internal/models/user.go | 19 +- migrations/20260303130413_create_user.sql | 1 + migrations/20260304163413_create_roles.sql | 14 - .../20260304163423_create_users_roles.sql | 13 - pkg/storage.go | 5 - 11 files changed, 279 insertions(+), 91 deletions(-) delete mode 100644 migrations/20260304163413_create_roles.sql delete mode 100644 migrations/20260304163423_create_users_roles.sql diff --git a/go.mod b/go.mod index d534557..11ac763 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( 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/gosimple/slug v1.15.0 github.com/huandu/go-sqlbuilder v1.39.1 github.com/jackc/pgx/v5 v5.8.0 github.com/joho/godotenv v1.5.1 @@ -20,6 +21,7 @@ require ( github.com/redis/go-redis/v9 v9.18.0 github.com/rs/xid v1.6.0 github.com/samber/slog-chi v1.19.0 + golang.org/x/crypto v0.48.0 ) require ( @@ -27,6 +29,7 @@ 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/gosimple/unidecode v1.0.1 // 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 diff --git a/go.sum b/go.sum index a4e227b..c8a0e13 100644 --- a/go.sum +++ b/go.sum @@ -27,6 +27,10 @@ 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/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo= +github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= +github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= +github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc= 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= @@ -102,6 +106,8 @@ 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/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= 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= diff --git a/internal/api/api.swagger.yaml b/internal/api/api.swagger.yaml index 10d2b6c..59eec69 100644 --- a/internal/api/api.swagger.yaml +++ b/internal/api/api.swagger.yaml @@ -259,8 +259,6 @@ paths: operationId: authLogout summary: Выход из системы tags: [Auth] - security: - - BearerAuth: [] responses: "204": description: Успешный выход @@ -272,8 +270,6 @@ paths: operationId: getMe summary: Получить текущего пользователя tags: [Me] - security: - - BearerAuth: [] responses: "200": description: Данные пользователя @@ -288,8 +284,6 @@ paths: operationId: updateMe summary: Обновить данные текущего пользователя tags: [Me] - security: - - BearerAuth: [] requestBody: required: true content: @@ -320,8 +314,6 @@ paths: operationId: deleteMe summary: Удалить аккаунт tags: [Me] - security: - - BearerAuth: [] requestBody: required: true content: diff --git a/internal/api/gen.go b/internal/api/gen.go index 6f99a22..7abb2b3 100644 --- a/internal/api/gen.go +++ b/internal/api/gen.go @@ -4,7 +4,6 @@ package api import ( - "context" "fmt" "net/http" @@ -12,10 +11,6 @@ import ( 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"` @@ -180,12 +175,6 @@ func (siw *ServerInterfaceWrapper) AuthLogin(w http.ResponseWriter, r *http.Requ // 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) })) @@ -228,12 +217,6 @@ func (siw *ServerInterfaceWrapper) AuthRegister(w http.ResponseWriter, r *http.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) })) @@ -248,12 +231,6 @@ func (siw *ServerInterfaceWrapper) DeleteMe(w http.ResponseWriter, r *http.Reque // 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) })) @@ -268,12 +245,6 @@ func (siw *ServerInterfaceWrapper) GetMe(w http.ResponseWriter, r *http.Request) // 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) })) diff --git a/internal/handler/server_impl.go b/internal/handler/server_impl.go index 7bedc63..d4ac264 100644 --- a/internal/handler/server_impl.go +++ b/internal/handler/server_impl.go @@ -62,6 +62,8 @@ func (s *Server) Run() error { MaxAge: 300, })) + r.Use(s.MiddlewareRequestID) + r.Use(slogchi.NewWithConfig(slog.Default(), slogchi.Config{ DefaultLevel: slog.LevelInfo, ClientErrorLevel: slog.LevelWarn, // 400–499 → Warn @@ -73,7 +75,6 @@ func (s *Server) Run() error { })) r.Use(s.AuthMiddleware) - r.Use(s.MiddlewareRequestID) h := api.HandlerFromMux(s, r) @@ -120,14 +121,10 @@ func (s *Server) AuthMiddleware(next http.Handler) http.Handler { claims, err := s.validateAccessToken(r.Context(), tokenStr) if err != nil { slog.WarnContext(r.Context(), "Токен не прошел валидацию", slog.String("Token", tokenStr), slog.String("error", err.Error())) + s.JSON(w, r, http.StatusUnauthorized, "токен не прошёл валидацию", "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)) }) @@ -208,7 +205,7 @@ func (s *Server) validateAccessToken(ctx context.Context, tokenStr string) (*Cla return nil, errors.New("only HS256 allowed") } - redisKey := "access_hash:" + tokenStr + redisKey := "access_token:" + 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) diff --git a/internal/handler/user.go b/internal/handler/user.go index edbe2dc..e41866b 100644 --- a/internal/handler/user.go +++ b/internal/handler/user.go @@ -1,10 +1,23 @@ package handler import ( + "crypto/rand" + "encoding/hex" + "encoding/json" + "errors" + "log/slog" "net/http" + "time" + "github.com/anxi0uz/logiflow/internal/api" + "github.com/anxi0uz/logiflow/internal/models" + storage "github.com/anxi0uz/logiflow/pkg" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" + "github.com/gosimple/slug" + "github.com/huandu/go-sqlbuilder" + "github.com/redis/go-redis/v9" + "golang.org/x/crypto/bcrypt" ) type Claims struct { @@ -16,11 +29,247 @@ type Claims struct { } func (s *Server) AuthLogin(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var req api.LoginRequest + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + s.JSON(w, r, http.StatusBadRequest, "Ошибка при получении данных", "error") + return + } + + user, err := storage.GetOne[models.User](ctx, s.DB, "users", func(sb *sqlbuilder.SelectBuilder) { + sb.Where(sb.Equal("email", req.Email)) + }) + if err != nil { + slog.WarnContext(ctx, "user with that email not found in db", slog.String("Email", string(req.Email)), slog.String("error", err.Error())) + s.JSON(w, r, http.StatusBadRequest, "Пользователь с таким Email не найден", "error") + return + } + + if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(req.Password)); err != nil { + slog.WarnContext(ctx, "Failed login to account with", slog.String("email:", string(req.Email)), slog.String("password from request", req.Password)) + s.JSON(w, r, http.StatusBadRequest, "Неверный пароль", "error") + return + } + + s.issueTokens(w, r, user) +} + +func (s *Server) AuthLogout(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + cookie, err := r.Cookie("refresh_token") + if err != nil { + s.JSON(w, r, http.StatusUnauthorized, "Missing refresh token", "error") + return + } + refreshStr := cookie.Value + if refreshStr == "" { + s.JSON(w, r, http.StatusUnauthorized, "Empty refresh token", "error") + return + } + + refreshKey := "refresh_token:" + refreshStr + if err := s.Redis.Del(ctx, refreshKey).Err(); err != nil { + slog.ErrorContext(ctx, "Error while removing refresh token from redis", slog.String("token", refreshStr), slog.String("error", err.Error())) + s.JSON(w, r, http.StatusUnauthorized, "Internal server error", "error") + return + } + + tokenStr := r.Header.Get("Authorization") + if tokenStr == "" { + s.JSON(w, r, http.StatusUnauthorized, "missing token", "error") + return + } + + tokenKey := "access_token:" + tokenStr + if err := s.Redis.Del(ctx, tokenKey).Err(); err != nil { + slog.ErrorContext(ctx, "Error while removing access token from redis", slog.String("token", tokenStr), slog.String("error", err.Error())) + s.JSON(w, r, http.StatusUnauthorized, "Internal server error", "error") + return + } +} +func (s *Server) AuthRefresh(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + cookie, err := r.Cookie("refresh_token") + if err != nil { + s.JSON(w, r, http.StatusUnauthorized, "Missing refresh token", "error") + return + } + refreshStr := cookie.Value + if refreshStr == "" { + s.JSON(w, r, http.StatusUnauthorized, "Empty refresh token", "error") + return + } + key := "refresh_token:" + refreshStr + if _, err := s.Redis.Get(ctx, key).Result(); err == redis.Nil { + s.JSON(w, r, http.StatusUnauthorized, "No active refresh token", "error") + return + } + + claimsValue := ctx.Value("user") + + claims, ok := claimsValue.(*Claims) + + if !ok { + slog.ErrorContext(ctx, "Error parsing claims", slog.Any("claims", claims)) + s.JSON(w, r, http.StatusInternalServerError, nil, "internal server error") + return + } + + userID := claims.ID + if userID == uuid.Nil { + s.JSON(w, r, http.StatusUnauthorized, "Missing user id in token", "error") + return + } + + user, err := storage.GetOne[models.User](ctx, s.DB, "users", func(sb *sqlbuilder.SelectBuilder) { + sb.Where(sb.Equal("id", userID)) + }) + if err != nil { + slog.ErrorContext(ctx, "user with that id not found", slog.Any("id", userID.String()), "error", err.Error()) + s.JSON(w, r, http.StatusUnauthorized, "Invalid user id in token", "error") + return + } + s.issueTokens(w, r, user) +} +func (s *Server) AuthRegister(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + var req api.RegisterRequest + + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + s.JSON(w, r, http.StatusBadRequest, "invalid request body", "error") + return + } + + passwordHash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) + if err != nil { + s.JSON(w, r, http.StatusInternalServerError, "error while hashing password", "error") + return + } + + now := time.Now() + + uuid := uuid.New() + + user := models.User{ + ID: uuid, + Slug: s.GenerateUserSlug(req.FullName, uuid), + CreatedAt: now, + UpdatedAt: now, + Email: string(req.Email), + PasswordHash: string(passwordHash), + FullName: req.FullName, + } + + if err := storage.Create[models.User](ctx, "users", user, s.DB); err != nil { + slog.ErrorContext(ctx, "Error while creating user", slog.String("error", err.Error())) + s.JSON(w, r, http.StatusInternalServerError, "Server error", "error") + return + } + s.issueTokens(w, r, &user) +} +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) {} + +func (s *Server) issueTokens(w http.ResponseWriter, r *http.Request, user *models.User) { + access, err := s.generateAccessToken(user, s.Config.RedisAccessTokenDur()) + if err != nil { + slog.ErrorContext(r.Context(), "generate access failed", slog.String("error", err.Error())) + return + } + refresh, err := s.generateRefreshToken() + if err != nil { + slog.ErrorContext(r.Context(), "generate refresh token failed", slog.String("error", err.Error())) + s.JSON(w, r, http.StatusInternalServerError, "Failure during generating tokens", "error") + } + + key := "access_token:" + access + refreshkey := "refresh_token:" + refresh + err = s.Redis.Set(r.Context(), key, "valid", s.Config.RedisAccessTokenDur()).Err() + if err != nil { + slog.ErrorContext(r.Context(), "Failed to set access token in redis", slog.String("error", err.Error())) + s.JSON(w, r, http.StatusInternalServerError, "Server error", "error") + return + } + + err = s.Redis.Set(r.Context(), refreshkey, "valid", s.Config.RedisRefreshTokenDur()).Err() + if err != nil { + slog.ErrorContext(r.Context(), "Failed to set refresh token in redis", slog.String("error", err.Error())) + s.JSON(w, r, http.StatusInternalServerError, "Server error", "error") + return + } + + http.SetCookie(w, &http.Cookie{ + Name: "refresh_token", + Value: refresh, + HttpOnly: true, + Secure: true, + SameSite: http.SameSiteStrictMode, + Path: "/", + MaxAge: 7 * 24 * 3600, + }) + + s.JSON(w, r, http.StatusOK, map[string]any{ + "access_token": access, + "expires_in": 86400, + }, "auth") +} + +func (s *Server) deleteRefreshCookie(w http.ResponseWriter) { + http.SetCookie(w, &http.Cookie{ + Name: "refresh_token", + Value: "", + Path: "/", + MaxAge: -1, + }) +} + +func (s *Server) generateAccessToken(user *models.User, duration time.Duration) (string, error) { + return s.generateJWT(user, duration) +} + +func (s *Server) generateRefreshToken() (string, error) { + b := make([]byte, 32) + _, err := rand.Read(b) + if err != nil { + return "", err + } + return hex.EncodeToString(b), nil +} + +func (s *Server) generateJWT(user *models.User, lifetime time.Duration) (string, error) { + if len(s.JwtKey) == 0 { + return "", errors.New("jwt key not set") + } + + tokenID := hex.EncodeToString([]byte(time.Now().String() + user.ID.String())) + + claims := Claims{ + ID: user.ID, + Email: user.Email, + Role: user.Role, + TokenID: tokenID, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(time.Now().Add(lifetime)), + IssuedAt: jwt.NewNumericDate(time.Now()), + Subject: user.ID.String(), + Issuer: s.Config.JwtOpt.Issuer, + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString(s.JwtKey) +} + +func (s *Server) GenerateUserSlug(username string, uuid uuid.UUID) string { + if username == "" { + username = "user" + } + + base := slug.Make(username) + + return base + uuid.String() } -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) {} diff --git a/internal/models/user.go b/internal/models/user.go index 49ebbf5..27adbf1 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -7,13 +7,14 @@ import ( ) 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"` + ID uuid.UUID `db:"id"` + Email string `db:"email"` + Slug string `db:"slug"` + PasswordHash string `db:"password_hash"` + FullName string `db:"full_name"` + AvatarURL string `db:"avatar_url"` + Role string `db:"role"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` + LastLoginAt *time.Time `db:"last_login_at"` } diff --git a/migrations/20260303130413_create_user.sql b/migrations/20260303130413_create_user.sql index 9b08c46..55c1f29 100644 --- a/migrations/20260303130413_create_user.sql +++ b/migrations/20260303130413_create_user.sql @@ -7,6 +7,7 @@ CREATE TABLE IF NOT EXISTS users ( password_hash VARCHAR(255) NOT NULL, full_name VARCHAR(150), avatar_url VARCHAR(512), + role VARCHAR(512), created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ, last_login_at TIMESTAMPTZ diff --git a/migrations/20260304163413_create_roles.sql b/migrations/20260304163413_create_roles.sql deleted file mode 100644 index d8133fe..0000000 --- a/migrations/20260304163413_create_roles.sql +++ /dev/null @@ -1,14 +0,0 @@ --- +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 diff --git a/migrations/20260304163423_create_users_roles.sql b/migrations/20260304163423_create_users_roles.sql deleted file mode 100644 index 08eab03..0000000 --- a/migrations/20260304163423_create_users_roles.sql +++ /dev/null @@ -1,13 +0,0 @@ --- +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 diff --git a/pkg/storage.go b/pkg/storage.go index fda27e7..742b5cb 100644 --- a/pkg/storage.go +++ b/pkg/storage.go @@ -18,7 +18,6 @@ type Querier interface { var ErrNotFound = errors.New("not found") -// GetAll функция для получения всех записей из базы данных func GetAll[T any](ctx context.Context, table string, db Querier, opts ...func(*sqlbuilder.SelectBuilder)) ([]T, error) { sb := sqlbuilder.NewStruct(new(T)).SelectFrom(table) @@ -55,7 +54,6 @@ func GetAll[T any](ctx context.Context, table string, db Querier, opts ...func(* return lists, nil } -// GetOne функция для получения одной записи из базы данных func GetOne[T any](ctx context.Context, db Querier, table string, opts ...func(*sqlbuilder.SelectBuilder)) (*T, error) { itemsStruct := sqlbuilder.NewStruct(new(T)).For(sqlbuilder.PostgreSQL) @@ -86,7 +84,6 @@ func GetOne[T any](ctx context.Context, db Querier, table string, opts ...func(* return &item, nil } -// Create функция для создания записи в базе данных func Create[T any](ctx context.Context, table string, item T, db Querier, opts ...func(*sqlbuilder.SelectBuilder)) error { structs := sqlbuilder.NewStruct(new(T)) @@ -111,7 +108,6 @@ func Create[T any](ctx context.Context, table string, item T, db Querier, opts . return nil } -// Update функция для обновления записи в базе данных func Update[T any](ctx context.Context, table string, item T, db Querier, opts ...func(*sqlbuilder.UpdateBuilder)) error { structs := sqlbuilder.NewStruct(new(T)) @@ -136,7 +132,6 @@ func Update[T any](ctx context.Context, table string, item T, db Querier, opts . return nil } -// Delete функция для удаления записи из базы данных func Delete[T any](ctx context.Context, table string, db Querier, opts ...func(*sqlbuilder.SelectBuilder)) error { structs := sqlbuilder.NewStruct(new(T))