add user management functionality with OTP verification and consent handling, DI introduced

This commit is contained in:
2025-08-10 10:38:49 +03:00
parent 6a9061a3de
commit 5d80a68b44
30 changed files with 828 additions and 528 deletions

View File

@ -0,0 +1,51 @@
package domain
import "context"
type AuthRepository interface {
SaveOtpRequest(ctx context.Context, uuid string, code string) error
GetOtpRequest(ctx context.Context, uuid string) (*string, error)
}
type ErrOtpNotFound struct {
Uuid string
}
func (e ErrOtpNotFound) Error() string {
return "OTP request not found for UUID: " + e.Uuid
}
type ErrUserNotFound struct {
PhoneNumber string
}
func (e ErrUserNotFound) Error() string {
return "User not found with phone number: " + e.PhoneNumber
}
type ErrOtpInvalid struct {
Code string
Uuid string
}
func (e ErrOtpInvalid) Error() string {
return "Invalid OTP code: " + e.Code + " for UUID: " + e.Uuid
}
type ErrInvalidHydraAccept struct {
Message string
Uuid string
}
func (e ErrInvalidHydraAccept) Error() string {
return "Invalid Hydra accept request: " + e.Message + " for UUID: " + e.Uuid
}
type ErrInvalidPhoneNumber struct {
PhoneNumber string
Err error
}
func (e ErrInvalidPhoneNumber) Error() string {
return "Invalid phone number: " + e.PhoneNumber + ", error: " + e.Err.Error()
}

20
internal/api/auth/fx.go Normal file
View File

@ -0,0 +1,20 @@
package auth
import (
"git.logidex.ru/fakz9/logidex-id/internal/api/auth/handler"
"git.logidex.ru/fakz9/logidex-id/internal/api/auth/repo"
"git.logidex.ru/fakz9/logidex-id/internal/api/auth/service"
"github.com/gofiber/fiber/v2"
"go.uber.org/fx"
)
var Module = fx.Options(
fx.Provide(
repo.NewAuthRepo,
service.NewAuthService,
handler.NewAuthHandler,
),
fx.Invoke(func(handler *handler.AuthHandler, router fiber.Router) {
handler.RegisterRoutes(router)
}),
)

View File

@ -14,6 +14,9 @@ import (
type AcceptConsentRequest struct {
// ConsentChallenge The consent challenge to accept
ConsentChallenge string `json:"consent_challenge"`
// PhoneNumber Phone number associated with the consent
PhoneNumber string `json:"phone_number"`
}
// AcceptConsentResponse defines model for AcceptConsentResponse.
@ -56,6 +59,9 @@ type VerifyOTPRequest struct {
// VerifyOTPResponse defines model for VerifyOTPResponse.
type VerifyOTPResponse struct {
// Message Confirmation message
Message string `json:"message"`
// Ok Status of the verification
Ok bool `json:"ok"`

View File

@ -2,113 +2,68 @@ package handler
import (
"context"
"fmt"
"git.logidex.ru/fakz9/logidex-id/internal/hydra_client"
"git.logidex.ru/fakz9/logidex-id/internal/redis"
"git.logidex.ru/fakz9/logidex-id/internal/api/auth/service"
"github.com/gofiber/fiber/v2"
hydraApi "github.com/ory/hydra-client-go"
)
type AuthHandler struct{}
func (a AuthHandler) PostAuthConsentAccept(ctx context.Context, request PostAuthConsentAcceptRequestObject) (PostAuthConsentAcceptResponseObject, error) {
hydraClient := hydra_client.GetClient()
hydraRequest := hydraApi.AcceptConsentRequest{}
hydraRequest.SetGrantScope([]string{"openid"})
hydraRequest.SetRemember(true)
hydraRequest.SetRememberFor(3600) // 1 hour
hydraResponse, r, err := hydraClient.AdminApi.
AcceptConsentRequest(ctx).
ConsentChallenge(request.Body.ConsentChallenge).
AcceptConsentRequest(hydraRequest).
Execute()
if err != nil {
return PostAuthConsentAccept400JSONResponse{
RedirectUrl: "",
Ok: false,
Message: "Failed to accept consent request",
}, nil
}
fmt.Println(r)
return PostAuthConsentAccept200JSONResponse{
RedirectUrl: hydraResponse.RedirectTo,
Ok: true,
Message: "Успешно",
}, nil
type AuthHandler struct {
service service.AuthService
}
func (a AuthHandler) PostAuthOtpRequest(ctx context.Context, request PostAuthOtpRequestRequestObject) (PostAuthOtpRequestResponseObject, error) {
redisClient := redis.GetClient()
// TODO implement OTP request logic
err := redisClient.Do(ctx, redisClient.B().Set().Key("otp:"+request.Body.PhoneNumber).Value("123456").Build()).Error()
func (h AuthHandler) PostAuthOtpRequest(ctx context.Context, request PostAuthOtpRequestRequestObject) (PostAuthOtpRequestResponseObject, error) {
err := h.service.OtpRequest(ctx, request.Body.PhoneNumber)
if err != nil {
return PostAuthOtpRequest400JSONResponse{
Message: "Failed to set OTP in Redis",
Message: err.Error(),
Ok: false,
}, nil
}
return PostAuthOtpRequest200JSONResponse{
Message: "Код успешно отправлен",
Message: "OTP request successful",
Ok: true,
}, nil
}
func (a AuthHandler) PostAuthOtpVerify(ctx context.Context, request PostAuthOtpVerifyRequestObject) (PostAuthOtpVerifyResponseObject, error) {
redisClient := redis.GetClient()
hydraClient := hydra_client.GetClient()
sentOtp, err := redisClient.Do(ctx, redisClient.B().Get().Key("otp:"+request.Body.PhoneNumber).Build()).ToString()
func (h AuthHandler) PostAuthOtpVerify(ctx context.Context, request PostAuthOtpVerifyRequestObject) (PostAuthOtpVerifyResponseObject, error) {
redirectUrl, err := h.service.OtpVerify(ctx, request.Body.PhoneNumber, request.Body.Otp, request.Body.LoginChallenge)
if err != nil {
return PostAuthOtpVerify400JSONResponse{
RedirectUrl: "",
Message: err.Error(),
Ok: false,
}, nil
}
if sentOtp != request.Body.Otp {
return PostAuthOtpVerify400JSONResponse{
RedirectUrl: "",
Ok: false,
}, nil
}
hydraRequest := hydraApi.AcceptLoginRequest{}
// TODO read user from database by phone number
hydraRequest.SetSubject("some-user-id") // Replace with actual user ID
hydraRequest.SetRemember(true)
hydraRequest.SetRememberFor(3600) // 1 hour
hydraResponse, r, err := hydraClient.AdminApi.
AcceptLoginRequest(ctx).
LoginChallenge(request.Body.LoginChallenge).
AcceptLoginRequest(hydraRequest).
Execute()
fmt.Println(r)
if err != nil {
return PostAuthOtpVerify400JSONResponse{
RedirectUrl: "",
Ok: false,
}, nil
}
return PostAuthOtpVerify200JSONResponse{
RedirectUrl: hydraResponse.RedirectTo,
Message: "OTP verification successful",
Ok: true,
RedirectUrl: redirectUrl,
}, nil
}
func (h AuthHandler) PostAuthConsentAccept(ctx context.Context, request PostAuthConsentAcceptRequestObject) (PostAuthConsentAcceptResponseObject, error) {
redirectUrl, err := h.service.AcceptConsent(ctx, request.Body.PhoneNumber, request.Body.ConsentChallenge)
if err != nil {
return PostAuthConsentAccept400JSONResponse{
Message: err.Error(),
Ok: false,
RedirectUrl: "",
}, nil
}
return PostAuthConsentAccept200JSONResponse{
Message: "Consent accepted successfully",
Ok: true,
RedirectUrl: redirectUrl,
}, nil
}
var _ StrictServerInterface = (*AuthHandler)(nil)
func NewAuthHandler() *AuthHandler {
return &AuthHandler{}
func NewAuthHandler(service service.AuthService) *AuthHandler {
return &AuthHandler{service: service}
}
func RegisterApp(router fiber.Router) {
//authGroup := router.Group("/auth")
server := NewStrictHandler(NewAuthHandler(), nil)
func (h AuthHandler) RegisterRoutes(router fiber.Router) {
server := NewStrictHandler(h, nil)
RegisterHandlers(router, server)
}

View File

@ -0,0 +1,45 @@
package repo
import (
"context"
"time"
"git.logidex.ru/fakz9/logidex-id/internal/api/auth/domain"
"github.com/redis/rueidis"
)
type authRepo struct {
redisClient rueidis.Client
}
func (a authRepo) GetOtpRequest(ctx context.Context, uuid string) (*string, error) {
redisClient := a.redisClient
resp, err := redisClient.Do(ctx, redisClient.B().Get().Key("otp:"+uuid).Build()).ToString()
if err != nil {
return nil, err
}
if resp == "" {
// create error
return nil, &domain.ErrOtpNotFound{Uuid: uuid}
}
return &resp, nil
}
func (a authRepo) SaveOtpRequest(ctx context.Context, uuid string, code string) error {
redisClient := a.redisClient
err := redisClient.Do(ctx, redisClient.B().Set().Key("otp:"+uuid).Value(code).Ex(120*time.Second).Build()).Error()
if err != nil {
return err
}
return nil
}
func NewAuthRepo(redisClient rueidis.Client) domain.AuthRepository {
return &authRepo{redisClient: redisClient}
}

View File

@ -0,0 +1,167 @@
package service
import (
"context"
"strconv"
"git.logidex.ru/fakz9/logidex-id/internal/api/auth/domain"
userDomain "git.logidex.ru/fakz9/logidex-id/internal/api/user/domain"
"git.logidex.ru/fakz9/logidex-id/internal/phoneutil"
hydraApi "github.com/ory/hydra-client-go"
)
type AuthService interface {
OtpRequest(ctx context.Context, phoneNumber string) error
OtpVerify(ctx context.Context, phoneNumber, code string, loggingChallenge string) (string, error)
AcceptConsent(ctx context.Context, phoneNumber string, challenge string) (string, error)
}
type authService struct {
repo domain.AuthRepository
userRepo userDomain.UserRepository
hydraClient *hydraApi.APIClient
}
func (a authService) AcceptConsent(ctx context.Context, phoneNumber string, challenge string) (string, error) {
phoneNumber, err := phoneutil.ParseAndFormatPhoneNumber(phoneNumber)
if err != nil {
return "", domain.ErrInvalidPhoneNumber{
PhoneNumber: phoneNumber,
Err: err,
}
}
user, err := a.userRepo.GetUserByPhoneNumber(ctx, phoneNumber)
if err != nil {
return "", err
}
if user == nil {
return "", domain.ErrUserNotFound{PhoneNumber: phoneNumber}
}
request := hydraApi.AcceptConsentRequest{}
request.SetGrantScope([]string{"openid"})
request.SetRemember(true)
request.SetRememberFor(3600)
rsp, rawRsp, err := a.hydraClient.AdminApi.
AcceptConsentRequest(ctx).
ConsentChallenge(challenge).
AcceptConsentRequest(request).
Execute()
if err != nil {
return "", err
}
if rawRsp.StatusCode != 200 {
return "", domain.ErrInvalidHydraAccept{
Message: "Hydra response is nil: " + strconv.Itoa(rawRsp.StatusCode),
Uuid: "",
}
}
redirectTo, ok := rsp.GetRedirectToOk()
if !ok || redirectTo == nil {
return "", domain.ErrInvalidHydraAccept{
Message: "Hydra redirectTo is nil",
Uuid: "",
}
}
// TODO: Verify user in the database
_, err = a.userRepo.VerifyUser(ctx, user.Uuid.String())
if err != nil {
return "", err
}
return *redirectTo, nil
}
func (a authService) OtpRequest(ctx context.Context, phoneNumber string) error {
phoneNumber, err := phoneutil.ParseAndFormatPhoneNumber(phoneNumber)
if err != nil {
return domain.ErrInvalidPhoneNumber{
PhoneNumber: phoneNumber,
Err: err,
}
}
user, err := a.userRepo.GetUserByPhoneNumber(ctx, phoneNumber)
//if err != nil {
// return err
//}
if user == nil {
// Create a new user if it does not exist
user, err = a.userRepo.CreateUser(ctx, phoneNumber)
if err != nil {
return err
}
}
code := "123456"
err = a.repo.SaveOtpRequest(ctx, user.Uuid.String(), code)
if err != nil {
return err
}
// TODO implement sending OTP code via SMS
return nil
}
func (a authService) OtpVerify(ctx context.Context, phoneNumber string, code string, loggingChallenge string) (string, error) {
phoneNumber, err := phoneutil.ParseAndFormatPhoneNumber(phoneNumber)
if err != nil {
return "", domain.ErrInvalidPhoneNumber{
PhoneNumber: phoneNumber,
Err: err,
}
}
user, err := a.userRepo.GetUserByPhoneNumber(ctx, phoneNumber)
if err != nil {
return "", err
}
if user == nil {
return "", domain.ErrUserNotFound{PhoneNumber: phoneNumber}
}
otp, err := a.repo.GetOtpRequest(ctx, user.Uuid.String())
if err != nil {
return "", err
}
if otp == nil {
return "", domain.ErrOtpNotFound{Uuid: user.Uuid.String()}
}
if *otp != code {
return "", domain.ErrOtpInvalid{Uuid: user.Uuid.String(), Code: code}
}
request := hydraApi.AcceptLoginRequest{}
request.SetSubject(user.Uuid.String())
request.SetRemember(true)
request.SetRememberFor(3600) // 1 hour
rsp, rawRsp, err := a.hydraClient.AdminApi.
AcceptLoginRequest(ctx).
LoginChallenge(loggingChallenge).
AcceptLoginRequest(request).
Execute()
if err != nil {
return "", err
}
if rawRsp.StatusCode != 200 {
return "", domain.ErrInvalidHydraAccept{
Message: "Hydra response is nil: " + strconv.Itoa(rawRsp.StatusCode),
Uuid: user.Uuid.String(),
}
}
redirectTo, ok := rsp.GetRedirectToOk()
if !ok || redirectTo == nil {
return "", domain.ErrInvalidHydraAccept{
Message: "Hydra redirectTo is nil",
Uuid: user.Uuid.String(),
}
}
return *redirectTo, nil
}
func NewAuthService(repo domain.AuthRepository, userRepo userDomain.UserRepository, hydraClient *hydraApi.APIClient) AuthService {
return &authService{
repo: repo,
userRepo: userRepo,
hydraClient: hydraClient,
}
}

View File

@ -0,0 +1,27 @@
package domain
import (
"context"
db "git.logidex.ru/fakz9/logidex-id/internal/db/generated"
)
type User struct {
Uuid string `json:"uuid"`
PhoneNumber string `json:"phone_number"`
}
type UserRepository interface {
GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*db.User, error)
GetUserByUuid(ctx context.Context, uuid string) (*db.User, error)
CreateUser(ctx context.Context, phoneNumber string) (*db.User, error)
VerifyUser(ctx context.Context, uuid string) (*db.User, error)
}
type ErrUserNotFound struct {
PhoneNumber string
}
func (e ErrUserNotFound) Error() string {
return "User not found with phone number: " + e.PhoneNumber
}

20
internal/api/user/fx.go Normal file
View File

@ -0,0 +1,20 @@
package user
import (
"git.logidex.ru/fakz9/logidex-id/internal/api/user/handler"
"git.logidex.ru/fakz9/logidex-id/internal/api/user/repo"
"git.logidex.ru/fakz9/logidex-id/internal/api/user/service"
"github.com/gofiber/fiber/v2"
"go.uber.org/fx"
)
var Module = fx.Options(
fx.Provide(
repo.NewUserRepo,
service.NewUserService,
handler.NewUserHandler,
),
fx.Invoke(func(handler *handler.UserHandler, router fiber.Router) {
handler.RegisterRoutes(router)
}),
)

View File

@ -9,52 +9,30 @@ import (
"github.com/gofiber/fiber/v2"
"github.com/oapi-codegen/runtime"
openapi_types "github.com/oapi-codegen/runtime/types"
)
// User defines model for User.
type User struct {
Email openapi_types.Email `json:"email"`
Id string `json:"id"`
Username string `json:"username"`
PhoneNumber string `json:"phone_number"`
Uuid string `json:"uuid"`
}
// UserCreate defines model for UserCreate.
type UserCreate struct {
Email openapi_types.Email `json:"email"`
Password string `json:"password"`
Username string `json:"username"`
PhoneNumber string `json:"phone_number"`
}
// UserUpdate defines model for UserUpdate.
type UserUpdate struct {
Email openapi_types.Email `json:"email"`
Username string `json:"username"`
}
// PostUsersJSONRequestBody defines body for PostUsers for application/json ContentType.
type PostUsersJSONRequestBody = UserCreate
// PutUsersUserIdJSONRequestBody defines body for PutUsersUserId for application/json ContentType.
type PutUsersUserIdJSONRequestBody = UserUpdate
// CreateUserJSONRequestBody defines body for CreateUser for application/json ContentType.
type CreateUserJSONRequestBody = UserCreate
// ServerInterface represents all server handlers.
type ServerInterface interface {
// Get all users
// (GET /users)
GetUsers(c *fiber.Ctx) error
// Create a new user
// Create a new user with phone number
// (POST /users)
PostUsers(c *fiber.Ctx) error
// Delete a user by ID
// (DELETE /users/{userId})
DeleteUsersUserId(c *fiber.Ctx, userId string) error
CreateUser(c *fiber.Ctx) error
// Get a user by ID
// (GET /users/{userId})
GetUsersUserId(c *fiber.Ctx, userId string) error
// Update a user by ID
// (PUT /users/{userId})
PutUsersUserId(c *fiber.Ctx, userId string) error
GetUserById(c *fiber.Ctx, userId string) error
}
// ServerInterfaceWrapper converts contexts to parameters.
@ -64,20 +42,14 @@ type ServerInterfaceWrapper struct {
type MiddlewareFunc fiber.Handler
// GetUsers operation middleware
func (siw *ServerInterfaceWrapper) GetUsers(c *fiber.Ctx) error {
// CreateUser operation middleware
func (siw *ServerInterfaceWrapper) CreateUser(c *fiber.Ctx) error {
return siw.Handler.GetUsers(c)
return siw.Handler.CreateUser(c)
}
// PostUsers operation middleware
func (siw *ServerInterfaceWrapper) PostUsers(c *fiber.Ctx) error {
return siw.Handler.PostUsers(c)
}
// DeleteUsersUserId operation middleware
func (siw *ServerInterfaceWrapper) DeleteUsersUserId(c *fiber.Ctx) error {
// GetUserById operation middleware
func (siw *ServerInterfaceWrapper) GetUserById(c *fiber.Ctx) error {
var err error
@ -89,39 +61,7 @@ func (siw *ServerInterfaceWrapper) DeleteUsersUserId(c *fiber.Ctx) error {
return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter userId: %w", err).Error())
}
return siw.Handler.DeleteUsersUserId(c, userId)
}
// GetUsersUserId operation middleware
func (siw *ServerInterfaceWrapper) GetUsersUserId(c *fiber.Ctx) error {
var err error
// ------------- Path parameter "userId" -------------
var userId string
err = runtime.BindStyledParameterWithOptions("simple", "userId", c.Params("userId"), &userId, runtime.BindStyledParameterOptions{Explode: false, Required: true})
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter userId: %w", err).Error())
}
return siw.Handler.GetUsersUserId(c, userId)
}
// PutUsersUserId operation middleware
func (siw *ServerInterfaceWrapper) PutUsersUserId(c *fiber.Ctx) error {
var err error
// ------------- Path parameter "userId" -------------
var userId string
err = runtime.BindStyledParameterWithOptions("simple", "userId", c.Params("userId"), &userId, runtime.BindStyledParameterOptions{Explode: false, Required: true})
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter userId: %w", err).Error())
}
return siw.Handler.PutUsersUserId(c, userId)
return siw.Handler.GetUserById(c, userId)
}
// FiberServerOptions provides options for the Fiber server.
@ -145,143 +85,67 @@ func RegisterHandlersWithOptions(router fiber.Router, si ServerInterface, option
router.Use(fiber.Handler(m))
}
router.Get(options.BaseURL+"/users", wrapper.GetUsers)
router.Post(options.BaseURL+"/users", wrapper.CreateUser)
router.Post(options.BaseURL+"/users", wrapper.PostUsers)
router.Delete(options.BaseURL+"/users/:userId", wrapper.DeleteUsersUserId)
router.Get(options.BaseURL+"/users/:userId", wrapper.GetUsersUserId)
router.Put(options.BaseURL+"/users/:userId", wrapper.PutUsersUserId)
router.Get(options.BaseURL+"/users/:userId", wrapper.GetUserById)
}
type GetUsersRequestObject struct {
type CreateUserRequestObject struct {
Body *CreateUserJSONRequestBody
}
type GetUsersResponseObject interface {
VisitGetUsersResponse(ctx *fiber.Ctx) error
type CreateUserResponseObject interface {
VisitCreateUserResponse(ctx *fiber.Ctx) error
}
type GetUsers200JSONResponse []User
type CreateUser200JSONResponse User
func (response GetUsers200JSONResponse) VisitGetUsersResponse(ctx *fiber.Ctx) error {
func (response CreateUser200JSONResponse) VisitCreateUserResponse(ctx *fiber.Ctx) error {
ctx.Response().Header.Set("Content-Type", "application/json")
ctx.Status(200)
return ctx.JSON(&response)
}
type PostUsersRequestObject struct {
Body *PostUsersJSONRequestBody
}
type PostUsersResponseObject interface {
VisitPostUsersResponse(ctx *fiber.Ctx) error
}
type PostUsers201JSONResponse User
func (response PostUsers201JSONResponse) VisitPostUsersResponse(ctx *fiber.Ctx) error {
ctx.Response().Header.Set("Content-Type", "application/json")
ctx.Status(201)
return ctx.JSON(&response)
}
type DeleteUsersUserIdRequestObject struct {
type GetUserByIdRequestObject struct {
UserId string `json:"userId"`
}
type DeleteUsersUserIdResponseObject interface {
VisitDeleteUsersUserIdResponse(ctx *fiber.Ctx) error
type GetUserByIdResponseObject interface {
VisitGetUserByIdResponse(ctx *fiber.Ctx) error
}
type DeleteUsersUserId204Response struct {
type GetUserById200JSONResponse struct {
User User `json:"user"`
}
func (response DeleteUsersUserId204Response) VisitDeleteUsersUserIdResponse(ctx *fiber.Ctx) error {
ctx.Status(204)
return nil
}
type DeleteUsersUserId404Response struct {
}
func (response DeleteUsersUserId404Response) VisitDeleteUsersUserIdResponse(ctx *fiber.Ctx) error {
ctx.Status(404)
return nil
}
type GetUsersUserIdRequestObject struct {
UserId string `json:"userId"`
}
type GetUsersUserIdResponseObject interface {
VisitGetUsersUserIdResponse(ctx *fiber.Ctx) error
}
type GetUsersUserId200JSONResponse User
func (response GetUsersUserId200JSONResponse) VisitGetUsersUserIdResponse(ctx *fiber.Ctx) error {
func (response GetUserById200JSONResponse) VisitGetUserByIdResponse(ctx *fiber.Ctx) error {
ctx.Response().Header.Set("Content-Type", "application/json")
ctx.Status(200)
return ctx.JSON(&response)
}
type GetUsersUserId404Response struct {
type GetUserById404JSONResponse struct {
Message string `json:"message"`
}
func (response GetUsersUserId404Response) VisitGetUsersUserIdResponse(ctx *fiber.Ctx) error {
ctx.Status(404)
return nil
}
type PutUsersUserIdRequestObject struct {
UserId string `json:"userId"`
Body *PutUsersUserIdJSONRequestBody
}
type PutUsersUserIdResponseObject interface {
VisitPutUsersUserIdResponse(ctx *fiber.Ctx) error
}
type PutUsersUserId200JSONResponse User
func (response PutUsersUserId200JSONResponse) VisitPutUsersUserIdResponse(ctx *fiber.Ctx) error {
func (response GetUserById404JSONResponse) VisitGetUserByIdResponse(ctx *fiber.Ctx) error {
ctx.Response().Header.Set("Content-Type", "application/json")
ctx.Status(200)
ctx.Status(404)
return ctx.JSON(&response)
}
type PutUsersUserId404Response struct {
}
func (response PutUsersUserId404Response) VisitPutUsersUserIdResponse(ctx *fiber.Ctx) error {
ctx.Status(404)
return nil
}
// StrictServerInterface represents all server handlers.
type StrictServerInterface interface {
// Get all users
// (GET /users)
GetUsers(ctx context.Context, request GetUsersRequestObject) (GetUsersResponseObject, error)
// Create a new user
// Create a new user with phone number
// (POST /users)
PostUsers(ctx context.Context, request PostUsersRequestObject) (PostUsersResponseObject, error)
// Delete a user by ID
// (DELETE /users/{userId})
DeleteUsersUserId(ctx context.Context, request DeleteUsersUserIdRequestObject) (DeleteUsersUserIdResponseObject, error)
CreateUser(ctx context.Context, request CreateUserRequestObject) (CreateUserResponseObject, error)
// Get a user by ID
// (GET /users/{userId})
GetUsersUserId(ctx context.Context, request GetUsersUserIdRequestObject) (GetUsersUserIdResponseObject, error)
// Update a user by ID
// (PUT /users/{userId})
PutUsersUserId(ctx context.Context, request PutUsersUserIdRequestObject) (PutUsersUserIdResponseObject, error)
GetUserById(ctx context.Context, request GetUserByIdRequestObject) (GetUserByIdResponseObject, error)
}
type StrictHandlerFunc func(ctx *fiber.Ctx, args interface{}) (interface{}, error)
@ -297,54 +161,29 @@ type strictHandler struct {
middlewares []StrictMiddlewareFunc
}
// GetUsers operation middleware
func (sh *strictHandler) GetUsers(ctx *fiber.Ctx) error {
var request GetUsersRequestObject
// CreateUser operation middleware
func (sh *strictHandler) CreateUser(ctx *fiber.Ctx) error {
var request CreateUserRequestObject
handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) {
return sh.ssi.GetUsers(ctx.UserContext(), request.(GetUsersRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "GetUsers")
}
response, err := handler(ctx, request)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else if validResponse, ok := response.(GetUsersResponseObject); ok {
if err := validResponse.VisitGetUsersResponse(ctx); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
} else if response != nil {
return fmt.Errorf("unexpected response type: %T", response)
}
return nil
}
// PostUsers operation middleware
func (sh *strictHandler) PostUsers(ctx *fiber.Ctx) error {
var request PostUsersRequestObject
var body PostUsersJSONRequestBody
var body CreateUserJSONRequestBody
if err := ctx.BodyParser(&body); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
request.Body = &body
handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) {
return sh.ssi.PostUsers(ctx.UserContext(), request.(PostUsersRequestObject))
return sh.ssi.CreateUser(ctx.UserContext(), request.(CreateUserRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "PostUsers")
handler = middleware(handler, "CreateUser")
}
response, err := handler(ctx, request)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else if validResponse, ok := response.(PostUsersResponseObject); ok {
if err := validResponse.VisitPostUsersResponse(ctx); err != nil {
} else if validResponse, ok := response.(CreateUserResponseObject); ok {
if err := validResponse.VisitCreateUserResponse(ctx); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
} else if response != nil {
@ -353,85 +192,25 @@ func (sh *strictHandler) PostUsers(ctx *fiber.Ctx) error {
return nil
}
// DeleteUsersUserId operation middleware
func (sh *strictHandler) DeleteUsersUserId(ctx *fiber.Ctx, userId string) error {
var request DeleteUsersUserIdRequestObject
// GetUserById operation middleware
func (sh *strictHandler) GetUserById(ctx *fiber.Ctx, userId string) error {
var request GetUserByIdRequestObject
request.UserId = userId
handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) {
return sh.ssi.DeleteUsersUserId(ctx.UserContext(), request.(DeleteUsersUserIdRequestObject))
return sh.ssi.GetUserById(ctx.UserContext(), request.(GetUserByIdRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "DeleteUsersUserId")
handler = middleware(handler, "GetUserById")
}
response, err := handler(ctx, request)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else if validResponse, ok := response.(DeleteUsersUserIdResponseObject); ok {
if err := validResponse.VisitDeleteUsersUserIdResponse(ctx); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
} else if response != nil {
return fmt.Errorf("unexpected response type: %T", response)
}
return nil
}
// GetUsersUserId operation middleware
func (sh *strictHandler) GetUsersUserId(ctx *fiber.Ctx, userId string) error {
var request GetUsersUserIdRequestObject
request.UserId = userId
handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) {
return sh.ssi.GetUsersUserId(ctx.UserContext(), request.(GetUsersUserIdRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "GetUsersUserId")
}
response, err := handler(ctx, request)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else if validResponse, ok := response.(GetUsersUserIdResponseObject); ok {
if err := validResponse.VisitGetUsersUserIdResponse(ctx); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
} else if response != nil {
return fmt.Errorf("unexpected response type: %T", response)
}
return nil
}
// PutUsersUserId operation middleware
func (sh *strictHandler) PutUsersUserId(ctx *fiber.Ctx, userId string) error {
var request PutUsersUserIdRequestObject
request.UserId = userId
var body PutUsersUserIdJSONRequestBody
if err := ctx.BodyParser(&body); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
request.Body = &body
handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) {
return sh.ssi.PutUsersUserId(ctx.UserContext(), request.(PutUsersUserIdRequestObject))
}
for _, middleware := range sh.middlewares {
handler = middleware(handler, "PutUsersUserId")
}
response, err := handler(ctx, request)
if err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
} else if validResponse, ok := response.(PutUsersUserIdResponseObject); ok {
if err := validResponse.VisitPutUsersUserIdResponse(ctx); err != nil {
} else if validResponse, ok := response.(GetUserByIdResponseObject); ok {
if err := validResponse.VisitGetUserByIdResponse(ctx); err != nil {
return fiber.NewError(fiber.StatusBadRequest, err.Error())
}
} else if response != nil {

View File

@ -1,3 +1,3 @@
package handler
//go:generate go tool oapi-codegen -config ../../../../api/user/cfg.yaml ../../../../api/user/user.yaml
//go:generate go tool oapi-codegen -config ../../../../api/user/cfg.yaml ../../../../api/user/api.yaml

View File

@ -2,46 +2,52 @@ package handler
import (
"context"
"git.logidex.ru/fakz9/logidex-id/internal/api/user/service"
"github.com/gofiber/fiber/v2"
"github.com/jinzhu/copier"
)
type UserHandler struct {
service service.UserService
}
func (h UserHandler) CreateUser(ctx context.Context, request CreateUserRequestObject) (CreateUserResponseObject, error) {
//TODO implement me
panic("implement me")
}
func (h UserHandler) GetUserById(ctx context.Context, request GetUserByIdRequestObject) (GetUserByIdResponseObject, error) {
user, err := h.service.GetUserByUuid(ctx, request.UserId)
if err != nil {
return GetUserById404JSONResponse{
Message: err.Error(),
}, nil
}
var responseUser User
err = copier.Copy(responseUser, user)
if err != nil {
return GetUserById404JSONResponse{Message: err.Error()}, nil
}
return GetUserById200JSONResponse{User: responseUser}, nil
}
var _ StrictServerInterface = (*UserHandler)(nil)
func (u UserHandler) GetUsers(ctx context.Context, request GetUsersRequestObject) (GetUsersResponseObject, error) {
var response = make([]User, 0)
return GetUsers200JSONResponse(response), nil
func NewUserHandler(
service service.UserService,
) *UserHandler {
return &UserHandler{service: service}
}
func (u UserHandler) PostUsers(ctx context.Context, request PostUsersRequestObject) (PostUsersResponseObject, error) {
//TODO implement me
panic("implement me")
}
func (u UserHandler) DeleteUsersUserId(ctx context.Context, request DeleteUsersUserIdRequestObject) (DeleteUsersUserIdResponseObject, error) {
//TODO implement me
panic("implement me")
}
func (u UserHandler) GetUsersUserId(ctx context.Context, request GetUsersUserIdRequestObject) (GetUsersUserIdResponseObject, error) {
//TODO implement me
panic("implement me")
}
func (u UserHandler) PutUsersUserId(ctx context.Context, request PutUsersUserIdRequestObject) (PutUsersUserIdResponseObject, error) {
//TODO implement me
panic("implement me")
}
func NewUserHandler() *UserHandler {
return &UserHandler{}
}
func RegisterApp(router fiber.Router) {
server := NewStrictHandler(NewUserHandler(), nil)
func (h UserHandler) RegisterRoutes(router fiber.Router) {
server := NewStrictHandler(h, nil)
RegisterHandlers(router, server)
}
//func RegisterUserHandler(router fiber.Router) {
// server := NewStrictHandler(NewUserHandler(), nil)
// RegisterHandlers(router, server)
//
//}

View File

@ -0,0 +1,70 @@
package repo
import (
"context"
"git.logidex.ru/fakz9/logidex-id/internal/api/user/domain"
db "git.logidex.ru/fakz9/logidex-id/internal/db/generated"
"github.com/google/uuid"
)
type userRepo struct {
db db.DBTX
}
func (u userRepo) VerifyUser(ctx context.Context, requestUuid string) (*db.User, error) {
//TODO implement me
queries := db.New(u.db)
uuidParsed, err := uuid.Parse(requestUuid)
if err != nil {
return nil, err
}
dbUser, err := queries.UpdateUserVerified(ctx, uuidParsed)
if err != nil {
return nil, err
}
return &dbUser, nil
}
func (u userRepo) GetUserByUuid(ctx context.Context, requestUuid string) (*db.User, error) {
queries := db.New(u.db)
uuidParsed, err := uuid.Parse(requestUuid)
if err != nil {
return nil, err
}
dbUser, err := queries.GetUserByUUID(ctx, uuidParsed)
if err != nil {
return nil, err
}
return &dbUser, nil
}
func userFromDbToDomain(dbUser db.User) *domain.User {
return &domain.User{
PhoneNumber: dbUser.PhoneNumber,
Uuid: dbUser.Uuid.String(),
}
}
func (u userRepo) CreateUser(ctx context.Context, phoneNumber string) (*db.User, error) {
queries := db.New(u.db)
user, err := queries.CreateUser(ctx, phoneNumber)
if err != nil {
return nil, err
}
return &user, nil
}
func (u userRepo) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*db.User, error) {
queries := db.New(u.db)
dbUser, err := queries.GetByPhoneNumber(ctx, phoneNumber)
if err != nil {
return nil, err
}
return &dbUser, nil
}
func NewUserRepo(db db.DBTX) domain.UserRepository {
return &userRepo{db: db}
}

View File

@ -1,4 +0,0 @@
package repository
type UserRepo interface {
}

View File

@ -1,13 +1,47 @@
package service
import (
"git.logidex.ru/fakz9/logidex-id/internal/api/user/repository"
"context"
"git.logidex.ru/fakz9/logidex-id/internal/api/user/domain"
)
type UserService struct {
repo *repository.UserRepo
type UserService interface {
GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*domain.User, error)
GetUserByUuid(ctx context.Context, phoneNumber string) (*domain.User, error)
}
type userService struct {
repo domain.UserRepository
}
func NewUserService(repo *repository.UserRepo) *UserService {
return &UserService{repo: repo}
func (u userService) GetUserByUuid(ctx context.Context, uuid string) (*domain.User, error) {
dbUser, err := u.repo.GetUserByUuid(ctx, uuid)
if err != nil {
return nil, err
}
if dbUser == nil {
return nil, domain.ErrUserNotFound{PhoneNumber: uuid}
}
return &domain.User{
Uuid: dbUser.Uuid.String(),
PhoneNumber: dbUser.PhoneNumber,
}, nil
}
func (u userService) GetUserByPhoneNumber(ctx context.Context, phoneNumber string) (*domain.User, error) {
dbUser, err := u.repo.GetUserByPhoneNumber(ctx, phoneNumber)
if err != nil {
return nil, err
}
if dbUser == nil {
return nil, domain.ErrUserNotFound{PhoneNumber: phoneNumber}
}
return &domain.User{
Uuid: dbUser.Uuid.String(),
PhoneNumber: dbUser.PhoneNumber,
}, nil
}
func NewUserService(repo domain.UserRepository) UserService {
return &userService{repo: repo}
}