Compare commits
3 Commits
6a9061a3de
...
588576b82f
| Author | SHA1 | Date | |
|---|---|---|---|
| 588576b82f | |||
| 07d3ea44ea | |||
| 5d80a68b44 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
||||
.idea
|
||||
combined.yaml
|
||||
@ -137,9 +137,14 @@ components:
|
||||
type: boolean
|
||||
description: Status of the verification
|
||||
example: true
|
||||
message:
|
||||
type: string
|
||||
description: Confirmation message
|
||||
example: "OTP verified successfully"
|
||||
required:
|
||||
- redirect_url
|
||||
- ok
|
||||
- message
|
||||
AcceptConsentRequest:
|
||||
type: object
|
||||
properties:
|
||||
@ -147,8 +152,14 @@ components:
|
||||
type: string
|
||||
description: The consent challenge to accept
|
||||
example: "challenge123"
|
||||
phone_number:
|
||||
type: string
|
||||
description: Phone number associated with the consent
|
||||
example: "+79999999999"
|
||||
maxLength: 15
|
||||
required:
|
||||
- consent_challenge
|
||||
- phone_number
|
||||
AcceptConsentResponse:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@ -5,35 +5,11 @@ info:
|
||||
servers:
|
||||
- url: https://api.example.com/v1
|
||||
paths:
|
||||
/users:
|
||||
get:
|
||||
summary: Get all users
|
||||
responses:
|
||||
'200':
|
||||
description: A list of users
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/User'
|
||||
post:
|
||||
summary: Create a new user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UserCreate'
|
||||
responses:
|
||||
'201':
|
||||
description: User created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
/users/{userId}:
|
||||
get:
|
||||
tags:
|
||||
- users
|
||||
operationId: getUserById
|
||||
summary: Get a user by ID
|
||||
parameters:
|
||||
- name: userId
|
||||
@ -47,92 +23,65 @@ paths:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
user:
|
||||
$ref: '#/components/schemas/User'
|
||||
required:
|
||||
- user
|
||||
|
||||
|
||||
|
||||
'404':
|
||||
description: User not found
|
||||
put:
|
||||
summary: Update a user by ID
|
||||
parameters:
|
||||
- name: userId
|
||||
in: path
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
message:
|
||||
type: string
|
||||
example: "User not found"
|
||||
required:
|
||||
- message
|
||||
/users:
|
||||
post:
|
||||
tags:
|
||||
- users
|
||||
operationId: createUser
|
||||
summary: "Create a new user with phone number"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UserUpdate'
|
||||
$ref: '#/components/schemas/UserCreate'
|
||||
responses:
|
||||
'200':
|
||||
description: User updated
|
||||
description: User created successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/User'
|
||||
'404':
|
||||
description: User not found
|
||||
delete:
|
||||
summary: Delete a user by ID
|
||||
parameters:
|
||||
- name: userId
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
'204':
|
||||
description: User deleted successfully
|
||||
'404':
|
||||
description: User not found
|
||||
|
||||
components:
|
||||
schemas:
|
||||
User:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
uuid:
|
||||
type: string
|
||||
example: "123"
|
||||
username:
|
||||
phone_number:
|
||||
type: string
|
||||
example: "johndoe"
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
example: "johndoe@example.com"
|
||||
required:
|
||||
- id
|
||||
- username
|
||||
- email
|
||||
- uuid
|
||||
- phone_number
|
||||
UserCreate:
|
||||
type: object
|
||||
properties:
|
||||
username:
|
||||
phone_number:
|
||||
type: string
|
||||
example: "johndoe"
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
example: "johndoe@example.com"
|
||||
password:
|
||||
type: string
|
||||
format: password
|
||||
|
||||
required:
|
||||
- username
|
||||
- email
|
||||
- password
|
||||
UserUpdate:
|
||||
type: object
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
example: "john_doe_updated"
|
||||
email:
|
||||
type: string
|
||||
format: email
|
||||
example: "johnupdated@example.com"
|
||||
required:
|
||||
- username
|
||||
- email
|
||||
- phone_number
|
||||
|
||||
@ -1,28 +1,57 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
authApi "git.logidex.ru/fakz9/logidex-id/internal/api/auth/handler"
|
||||
"context"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"git.logidex.ru/fakz9/logidex-id/internal/api/auth"
|
||||
"git.logidex.ru/fakz9/logidex-id/internal/api/user"
|
||||
"git.logidex.ru/fakz9/logidex-id/internal/config"
|
||||
"git.logidex.ru/fakz9/logidex-id/internal/db"
|
||||
"git.logidex.ru/fakz9/logidex-id/internal/hydra_client"
|
||||
"git.logidex.ru/fakz9/logidex-id/internal/redis"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"strconv"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
func main() {
|
||||
config.Init()
|
||||
err := redis.Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
hydra_client.InitClient()
|
||||
|
||||
func NewFiberApp(cfg config.Config) *fiber.App {
|
||||
app := fiber.New()
|
||||
api := app.Group("/api")
|
||||
authApi.RegisterApp(api)
|
||||
return app
|
||||
}
|
||||
|
||||
err = app.Listen(":" + strconv.Itoa(config.Cfg.App.Port))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
func StartFiberApp(lifecycle fx.Lifecycle, app *fiber.App, cfg config.Config) {
|
||||
lifecycle.Append(fx.Hook{
|
||||
OnStart: func(ctx context.Context) error {
|
||||
go func() {
|
||||
if err := app.Listen(":" + strconv.Itoa(cfg.App.Port)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
},
|
||||
OnStop: func(ctx context.Context) error {
|
||||
return app.Shutdown()
|
||||
},
|
||||
})
|
||||
}
|
||||
func NewFiberRouter(app *fiber.App) fiber.Router {
|
||||
return app.Group("/api")
|
||||
}
|
||||
|
||||
func main() {
|
||||
fx.New(
|
||||
fx.Provide(
|
||||
config.NewConfig,
|
||||
redis.NewRedisClient,
|
||||
hydra_client.NewHydraClient,
|
||||
NewFiberApp,
|
||||
NewFiberRouter,
|
||||
),
|
||||
db.Module,
|
||||
user.Module,
|
||||
auth.Module,
|
||||
fx.Invoke(StartFiberApp),
|
||||
).Run()
|
||||
}
|
||||
|
||||
1
combine.sh
Executable file
1
combine.sh
Executable file
@ -0,0 +1 @@
|
||||
swagger-cli bundle openapi.yaml --outfile combined.yaml --type yaml
|
||||
@ -8,3 +8,10 @@ redis:
|
||||
|
||||
hydra:
|
||||
host: https://oauth2.logidex.ru/admin
|
||||
|
||||
db:
|
||||
host: localhost
|
||||
port: 5432
|
||||
user: postgres
|
||||
password: postgres
|
||||
dbname: logidex-id
|
||||
|
||||
4
go.mod
4
go.mod
@ -27,6 +27,7 @@ require (
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/jinzhu/copier v0.4.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
@ -37,6 +38,7 @@ require (
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/nyaruka/phonenumbers v1.6.4 // indirect
|
||||
github.com/oapi-codegen/oapi-codegen/v2 v2.5.0 // indirect
|
||||
github.com/oapi-codegen/runtime v1.1.2 // indirect
|
||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect
|
||||
@ -74,6 +76,8 @@ require (
|
||||
github.com/wasilibs/go-pgquery v0.0.0-20250409022910-10ac41983c07 // indirect
|
||||
github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect
|
||||
go.uber.org/atomic v1.11.0 // indirect
|
||||
go.uber.org/dig v1.19.0 // indirect
|
||||
go.uber.org/fx v1.24.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.uber.org/zap v1.27.0 // indirect
|
||||
golang.org/x/crypto v0.40.0 // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@ -157,6 +157,8 @@ github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs=
|
||||
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
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/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
@ -187,6 +189,8 @@ github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdh
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
||||
github.com/nyaruka/phonenumbers v1.6.4 h1:GFAa844VqRKJvO7oboosM1q3gFVgYvyNe0O6CCbg33A=
|
||||
github.com/nyaruka/phonenumbers v1.6.4/go.mod h1:7gjs+Lchqm49adhAKB5cdcng5ZXgt6x7Jgvi0ZorUtU=
|
||||
github.com/oapi-codegen/oapi-codegen/v2 v2.5.0 h1:iJvF8SdB/3/+eGOXEpsWkD8FQAHj6mqkb6Fnsoc8MFU=
|
||||
github.com/oapi-codegen/oapi-codegen/v2 v2.5.0/go.mod h1:fwlMxUEMuQK5ih9aymrxKPQqNm2n8bdLk1ppjH+lr9w=
|
||||
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
|
||||
@ -296,6 +300,10 @@ go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
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/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
|
||||
go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
||||
go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=
|
||||
go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo=
|
||||
go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
||||
|
||||
51
internal/api/auth/domain/auth_domain.go
Normal file
51
internal/api/auth/domain/auth_domain.go
Normal 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
20
internal/api/auth/fx.go
Normal 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)
|
||||
}),
|
||||
)
|
||||
@ -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"`
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
}
|
||||
|
||||
45
internal/api/auth/repo/auth_repo.go
Normal file
45
internal/api/auth/repo/auth_repo.go
Normal 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}
|
||||
}
|
||||
167
internal/api/auth/service/auth_service.go
Normal file
167
internal/api/auth/service/auth_service.go
Normal 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,
|
||||
}
|
||||
}
|
||||
27
internal/api/user/domain/user_domain.go
Normal file
27
internal/api/user/domain/user_domain.go
Normal 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
20
internal/api/user/fx.go
Normal 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)
|
||||
}),
|
||||
)
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
//
|
||||
//}
|
||||
|
||||
70
internal/api/user/repo/user_repo.go
Normal file
70
internal/api/user/repo/user_repo.go
Normal 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}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package repository
|
||||
|
||||
type UserRepo interface {
|
||||
}
|
||||
@ -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}
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/spf13/viper"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
@ -20,11 +21,16 @@ type Config struct {
|
||||
Host string
|
||||
Password string
|
||||
}
|
||||
DB struct {
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
Dbname string
|
||||
}
|
||||
}
|
||||
|
||||
var Cfg *Config
|
||||
|
||||
func Init() {
|
||||
func NewConfig() Config {
|
||||
err := godotenv.Load()
|
||||
if err != nil {
|
||||
log.Println("Error loading .env file")
|
||||
@ -48,5 +54,5 @@ func Init() {
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to decode config into struct, %v", err)
|
||||
}
|
||||
Cfg = &config
|
||||
return config
|
||||
}
|
||||
|
||||
24
internal/db/database.go
Normal file
24
internal/db/database.go
Normal file
@ -0,0 +1,24 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"git.logidex.ru/fakz9/logidex-id/internal/config"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
func NewDatabasePool(cfg config.Config) *pgxpool.Pool {
|
||||
ctx := context.Background()
|
||||
connUrl := "postgresql://" + cfg.DB.User + ":" + cfg.DB.Password + "@" + cfg.DB.Host + ":" + strconv.Itoa(cfg.DB.Port) + "/" + cfg.DB.Dbname
|
||||
|
||||
pool, err := pgxpool.New(ctx, connUrl)
|
||||
if err != nil {
|
||||
panic("Failed to connect to database: " + err.Error())
|
||||
}
|
||||
err = pool.Ping(ctx)
|
||||
if err != nil {
|
||||
panic("Failed to ping database: " + err.Error())
|
||||
}
|
||||
return pool
|
||||
}
|
||||
15
internal/db/fx.go
Normal file
15
internal/db/fx.go
Normal file
@ -0,0 +1,15 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
sqlcdb "git.logidex.ru/fakz9/logidex-id/internal/db/generated"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
var Module = fx.Options(
|
||||
fx.Provide(
|
||||
fx.Annotate(
|
||||
NewDatabasePool,
|
||||
fx.As(new(sqlcdb.DBTX)),
|
||||
),
|
||||
),
|
||||
)
|
||||
32
internal/db/generated/db.go
Normal file
32
internal/db/generated/db.go
Normal file
@ -0,0 +1,32 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
)
|
||||
|
||||
type DBTX interface {
|
||||
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
||||
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
||||
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
||||
}
|
||||
|
||||
func New(db DBTX) *Queries {
|
||||
return &Queries{db: db}
|
||||
}
|
||||
|
||||
type Queries struct {
|
||||
db DBTX
|
||||
}
|
||||
|
||||
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
|
||||
return &Queries{
|
||||
db: tx,
|
||||
}
|
||||
}
|
||||
19
internal/db/generated/models.go
Normal file
19
internal/db/generated/models.go
Normal file
@ -0,0 +1,19 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Uuid uuid.UUID `json:"uuid"`
|
||||
PhoneNumber string `json:"phone_number"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Verified bool `json:"verified"`
|
||||
VerifiedAt *time.Time `json:"verified_at"`
|
||||
}
|
||||
20
internal/db/generated/querier.go
Normal file
20
internal/db/generated/querier.go
Normal file
@ -0,0 +1,20 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Querier interface {
|
||||
CreateUser(ctx context.Context, phoneNumber string) (User, error)
|
||||
GetByPhoneNumber(ctx context.Context, phoneNumber string) (User, error)
|
||||
GetUserByUUID(ctx context.Context, argUuid uuid.UUID) (User, error)
|
||||
UpdateUserVerified(ctx context.Context, argUuid uuid.UUID) (User, error)
|
||||
}
|
||||
|
||||
var _ Querier = (*Queries)(nil)
|
||||
92
internal/db/generated/users.sql.go
Normal file
92
internal/db/generated/users.sql.go
Normal file
@ -0,0 +1,92 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.28.0
|
||||
// source: users.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const createUser = `-- name: CreateUser :one
|
||||
INSERT INTO users (phone_number)
|
||||
VALUES ($1)
|
||||
RETURNING uuid, phone_number, created_at, verified, verified_at
|
||||
`
|
||||
|
||||
func (q *Queries) CreateUser(ctx context.Context, phoneNumber string) (User, error) {
|
||||
row := q.db.QueryRow(ctx, createUser, phoneNumber)
|
||||
var i User
|
||||
err := row.Scan(
|
||||
&i.Uuid,
|
||||
&i.PhoneNumber,
|
||||
&i.CreatedAt,
|
||||
&i.Verified,
|
||||
&i.VerifiedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getByPhoneNumber = `-- name: GetByPhoneNumber :one
|
||||
SELECT uuid, phone_number, created_at, verified, verified_at
|
||||
FROM users
|
||||
WHERE phone_number = $1
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetByPhoneNumber(ctx context.Context, phoneNumber string) (User, error) {
|
||||
row := q.db.QueryRow(ctx, getByPhoneNumber, phoneNumber)
|
||||
var i User
|
||||
err := row.Scan(
|
||||
&i.Uuid,
|
||||
&i.PhoneNumber,
|
||||
&i.CreatedAt,
|
||||
&i.Verified,
|
||||
&i.VerifiedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getUserByUUID = `-- name: GetUserByUUID :one
|
||||
SELECT uuid, phone_number, created_at, verified, verified_at
|
||||
FROM users
|
||||
WHERE uuid = $1
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetUserByUUID(ctx context.Context, argUuid uuid.UUID) (User, error) {
|
||||
row := q.db.QueryRow(ctx, getUserByUUID, argUuid)
|
||||
var i User
|
||||
err := row.Scan(
|
||||
&i.Uuid,
|
||||
&i.PhoneNumber,
|
||||
&i.CreatedAt,
|
||||
&i.Verified,
|
||||
&i.VerifiedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateUserVerified = `-- name: UpdateUserVerified :one
|
||||
UPDATE users
|
||||
SET verified = TRUE,
|
||||
verified_at = CURRENT_TIMESTAMP
|
||||
WHERE uuid = $1
|
||||
RETURNING uuid, phone_number, created_at, verified, verified_at
|
||||
`
|
||||
|
||||
func (q *Queries) UpdateUserVerified(ctx context.Context, argUuid uuid.UUID) (User, error) {
|
||||
row := q.db.QueryRow(ctx, updateUserVerified, argUuid)
|
||||
var i User
|
||||
err := row.Scan(
|
||||
&i.Uuid,
|
||||
&i.PhoneNumber,
|
||||
&i.CreatedAt,
|
||||
&i.Verified,
|
||||
&i.VerifiedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
9
internal/db/migrations/001_init.sql
Normal file
9
internal/db/migrations/001_init.sql
Normal file
@ -0,0 +1,9 @@
|
||||
CREATE TABLE IF NOT EXISTS users
|
||||
(
|
||||
uuid UUID PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
||||
phone_number VARCHAR(20) NOT NULL CHECK (phone_number ~ '^\+[0-9]{10,15}$'),
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||||
verified BOOLEAN DEFAULT FALSE NOT NULL,
|
||||
verified_at TIMESTAMP NULL
|
||||
|
||||
);
|
||||
23
internal/db/queries/users.sql
Normal file
23
internal/db/queries/users.sql
Normal file
@ -0,0 +1,23 @@
|
||||
-- name: GetByPhoneNumber :one
|
||||
SELECT *
|
||||
FROM users
|
||||
WHERE phone_number = $1
|
||||
LIMIT 1;
|
||||
|
||||
-- name: GetUserByUUID :one
|
||||
SELECT *
|
||||
FROM users
|
||||
WHERE uuid = $1
|
||||
LIMIT 1;
|
||||
|
||||
-- name: CreateUser :one
|
||||
INSERT INTO users (phone_number)
|
||||
VALUES ($1)
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpdateUserVerified :one
|
||||
UPDATE users
|
||||
SET verified = TRUE,
|
||||
verified_at = CURRENT_TIMESTAMP
|
||||
WHERE uuid = $1
|
||||
RETURNING *;
|
||||
@ -3,26 +3,15 @@ package hydra_client
|
||||
import (
|
||||
"git.logidex.ru/fakz9/logidex-id/internal/config"
|
||||
hydraApi "github.com/ory/hydra-client-go"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
client *hydraApi.APIClient
|
||||
initClient sync.Once
|
||||
)
|
||||
|
||||
func InitClient() {
|
||||
func NewHydraClient(appConfig config.Config) *hydraApi.APIClient {
|
||||
cfg := hydraApi.NewConfiguration()
|
||||
cfg.AddDefaultHeader("X-Secret", config.Cfg.Hydra.Password)
|
||||
cfg.AddDefaultHeader("X-Secret", appConfig.Hydra.Password)
|
||||
cfg.Servers = []hydraApi.ServerConfiguration{
|
||||
{
|
||||
URL: config.Cfg.Hydra.Host,
|
||||
URL: appConfig.Hydra.Host,
|
||||
},
|
||||
}
|
||||
client = hydraApi.NewAPIClient(cfg)
|
||||
}
|
||||
|
||||
func GetClient() *hydraApi.APIClient {
|
||||
initClient.Do(InitClient)
|
||||
return client
|
||||
return hydraApi.NewAPIClient(cfg)
|
||||
}
|
||||
|
||||
19
internal/phoneutil/phoneutil.go
Normal file
19
internal/phoneutil/phoneutil.go
Normal file
@ -0,0 +1,19 @@
|
||||
package phoneutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/nyaruka/phonenumbers"
|
||||
)
|
||||
|
||||
func ParseAndFormatPhoneNumber(phoneNumber string) (string, error) {
|
||||
parsedNumber, err := phonenumbers.Parse(phoneNumber, "RU")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
result := phonenumbers.Format(parsedNumber, phonenumbers.E164)
|
||||
if result == "" {
|
||||
return "", errors.New("failed to format phone number")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@ -1,26 +1,21 @@
|
||||
package redis
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.logidex.ru/fakz9/logidex-id/internal/config"
|
||||
"github.com/redis/rueidis"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
var client rueidis.Client
|
||||
|
||||
func Init() error {
|
||||
func NewRedisClient(cfg config.Config) rueidis.Client {
|
||||
var err error
|
||||
client, err = rueidis.NewClient(rueidis.ClientOption{
|
||||
client, err := rueidis.NewClient(rueidis.ClientOption{
|
||||
// Set the address of your Redis server
|
||||
InitAddress: []string{config.Cfg.Redis.Host + ":" + strconv.Itoa(config.Cfg.Redis.Port)},
|
||||
Password: config.Cfg.Redis.Password,
|
||||
InitAddress: []string{cfg.Redis.Host + ":" + strconv.Itoa(cfg.Redis.Port)},
|
||||
Password: cfg.Redis.Password,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetClient() rueidis.Client {
|
||||
return client
|
||||
}
|
||||
|
||||
35
sqlc.yaml
Normal file
35
sqlc.yaml
Normal file
@ -0,0 +1,35 @@
|
||||
version: "2"
|
||||
sql:
|
||||
- engine: postgresql
|
||||
schema: "internal/db/migrations"
|
||||
queries: "internal/db/queries"
|
||||
gen:
|
||||
go:
|
||||
package: "db"
|
||||
out: "internal/db/generated"
|
||||
sql_package: "pgx/v5"
|
||||
emit_interface: true
|
||||
emit_json_tags: true
|
||||
emit_pointers_for_null_types: true
|
||||
overrides:
|
||||
# Timestamp
|
||||
- db_type: "pg_catalog.timestamp"
|
||||
nullable: true
|
||||
go_type:
|
||||
import: "time"
|
||||
type: "Time"
|
||||
pointer: true
|
||||
- db_type: "pg_catalog.timestamp"
|
||||
go_type: "time.Time"
|
||||
# UUID
|
||||
- db_type: "uuid"
|
||||
go_type:
|
||||
import: "github.com/google/uuid"
|
||||
type: "UUID"
|
||||
- db_type: "uuid"
|
||||
nullable: true
|
||||
go_type:
|
||||
import: "github.com/google/uuid"
|
||||
type: "UUID"
|
||||
pointer: true
|
||||
|
||||
Reference in New Issue
Block a user