add user management functionality with OTP verification and consent handling, DI introduced
This commit is contained in:
@ -137,9 +137,14 @@ components:
|
|||||||
type: boolean
|
type: boolean
|
||||||
description: Status of the verification
|
description: Status of the verification
|
||||||
example: true
|
example: true
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
description: Confirmation message
|
||||||
|
example: "OTP verified successfully"
|
||||||
required:
|
required:
|
||||||
- redirect_url
|
- redirect_url
|
||||||
- ok
|
- ok
|
||||||
|
- message
|
||||||
AcceptConsentRequest:
|
AcceptConsentRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -147,8 +152,14 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
description: The consent challenge to accept
|
description: The consent challenge to accept
|
||||||
example: "challenge123"
|
example: "challenge123"
|
||||||
|
phone_number:
|
||||||
|
type: string
|
||||||
|
description: Phone number associated with the consent
|
||||||
|
example: "+79999999999"
|
||||||
|
maxLength: 15
|
||||||
required:
|
required:
|
||||||
- consent_challenge
|
- consent_challenge
|
||||||
|
- phone_number
|
||||||
AcceptConsentResponse:
|
AcceptConsentResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
|
|||||||
@ -5,35 +5,11 @@ info:
|
|||||||
servers:
|
servers:
|
||||||
- url: https://api.example.com/v1
|
- url: https://api.example.com/v1
|
||||||
paths:
|
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}:
|
/users/{userId}:
|
||||||
get:
|
get:
|
||||||
|
tags:
|
||||||
|
- users
|
||||||
|
operationId: getUserById
|
||||||
summary: Get a user by ID
|
summary: Get a user by ID
|
||||||
parameters:
|
parameters:
|
||||||
- name: userId
|
- name: userId
|
||||||
@ -47,92 +23,65 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/User'
|
type: object
|
||||||
|
properties:
|
||||||
|
user:
|
||||||
|
$ref: '#/components/schemas/User'
|
||||||
|
required:
|
||||||
|
- user
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
'404':
|
'404':
|
||||||
description: User not found
|
description: User not found
|
||||||
put:
|
content:
|
||||||
summary: Update a user by ID
|
application/json:
|
||||||
parameters:
|
schema:
|
||||||
- name: userId
|
type: object
|
||||||
in: path
|
properties:
|
||||||
required: true
|
message:
|
||||||
schema:
|
type: string
|
||||||
type: string
|
example: "User not found"
|
||||||
|
required:
|
||||||
|
- message
|
||||||
|
/users:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- users
|
||||||
|
operationId: createUser
|
||||||
|
summary: "Create a new user with phone number"
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/UserUpdate'
|
$ref: '#/components/schemas/UserCreate'
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: User updated
|
description: User created successfully
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/User'
|
$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:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
User:
|
User:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
uuid:
|
||||||
type: string
|
type: string
|
||||||
example: "123"
|
example: "123"
|
||||||
username:
|
phone_number:
|
||||||
type: string
|
type: string
|
||||||
example: "johndoe"
|
example: "johndoe"
|
||||||
email:
|
|
||||||
type: string
|
|
||||||
format: email
|
|
||||||
example: "johndoe@example.com"
|
|
||||||
required:
|
required:
|
||||||
- id
|
- uuid
|
||||||
- username
|
- phone_number
|
||||||
- email
|
|
||||||
UserCreate:
|
UserCreate:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
username:
|
phone_number:
|
||||||
type: string
|
type: string
|
||||||
example: "johndoe"
|
|
||||||
email:
|
|
||||||
type: string
|
|
||||||
format: email
|
|
||||||
example: "johndoe@example.com"
|
|
||||||
password:
|
|
||||||
type: string
|
|
||||||
format: password
|
|
||||||
required:
|
required:
|
||||||
- username
|
- phone_number
|
||||||
- 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
|
|
||||||
|
|||||||
@ -1,28 +1,57 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
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/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/hydra_client"
|
||||||
"git.logidex.ru/fakz9/logidex-id/internal/redis"
|
"git.logidex.ru/fakz9/logidex-id/internal/redis"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
"strconv"
|
"go.uber.org/fx"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func NewFiberApp(cfg config.Config) *fiber.App {
|
||||||
config.Init()
|
|
||||||
err := redis.Init()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
hydra_client.InitClient()
|
|
||||||
|
|
||||||
app := fiber.New()
|
app := fiber.New()
|
||||||
api := app.Group("/api")
|
return app
|
||||||
authApi.RegisterApp(api)
|
}
|
||||||
|
|
||||||
err = app.Listen(":" + strconv.Itoa(config.Cfg.App.Port))
|
func StartFiberApp(lifecycle fx.Lifecycle, app *fiber.App, cfg config.Config) {
|
||||||
if err != nil {
|
lifecycle.Append(fx.Hook{
|
||||||
panic(err)
|
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
|
||||||
@ -7,4 +7,11 @@ redis:
|
|||||||
db: 0
|
db: 0
|
||||||
|
|
||||||
hydra:
|
hydra:
|
||||||
host: https://oauth2.logidex.ru/admin
|
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/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
github.com/jackc/pgx/v5 v5.7.5 // indirect
|
||||||
github.com/jackc/puddle/v2 v2.2.2 // 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/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/joho/godotenv v1.5.1 // indirect
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // 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/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // 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/oapi-codegen/v2 v2.5.0 // indirect
|
||||||
github.com/oapi-codegen/runtime v1.1.2 // indirect
|
github.com/oapi-codegen/runtime v1.1.2 // indirect
|
||||||
github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // 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/go-pgquery v0.0.0-20250409022910-10ac41983c07 // indirect
|
||||||
github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect
|
github.com/wasilibs/wazero-helpers v0.0.0-20240620070341-3dff1577cd52 // indirect
|
||||||
go.uber.org/atomic v1.11.0 // 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/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap v1.27.0 // indirect
|
go.uber.org/zap v1.27.0 // indirect
|
||||||
golang.org/x/crypto v0.40.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/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 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
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 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
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=
|
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/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.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
|
||||||
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
|
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 h1:iJvF8SdB/3/+eGOXEpsWkD8FQAHj6mqkb6Fnsoc8MFU=
|
||||||
github.com/oapi-codegen/oapi-codegen/v2 v2.5.0/go.mod h1:fwlMxUEMuQK5ih9aymrxKPQqNm2n8bdLk1ppjH+lr9w=
|
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=
|
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.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 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
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/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.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||||
go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
|
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 {
|
type AcceptConsentRequest struct {
|
||||||
// ConsentChallenge The consent challenge to accept
|
// ConsentChallenge The consent challenge to accept
|
||||||
ConsentChallenge string `json:"consent_challenge"`
|
ConsentChallenge string `json:"consent_challenge"`
|
||||||
|
|
||||||
|
// PhoneNumber Phone number associated with the consent
|
||||||
|
PhoneNumber string `json:"phone_number"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AcceptConsentResponse defines model for AcceptConsentResponse.
|
// AcceptConsentResponse defines model for AcceptConsentResponse.
|
||||||
@ -56,6 +59,9 @@ type VerifyOTPRequest struct {
|
|||||||
|
|
||||||
// VerifyOTPResponse defines model for VerifyOTPResponse.
|
// VerifyOTPResponse defines model for VerifyOTPResponse.
|
||||||
type VerifyOTPResponse struct {
|
type VerifyOTPResponse struct {
|
||||||
|
// Message Confirmation message
|
||||||
|
Message string `json:"message"`
|
||||||
|
|
||||||
// Ok Status of the verification
|
// Ok Status of the verification
|
||||||
Ok bool `json:"ok"`
|
Ok bool `json:"ok"`
|
||||||
|
|
||||||
|
|||||||
@ -2,113 +2,68 @@ package handler
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"git.logidex.ru/fakz9/logidex-id/internal/hydra_client"
|
"git.logidex.ru/fakz9/logidex-id/internal/api/auth/service"
|
||||||
"git.logidex.ru/fakz9/logidex-id/internal/redis"
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
hydraApi "github.com/ory/hydra-client-go"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AuthHandler struct{}
|
type AuthHandler struct {
|
||||||
|
service service.AuthService
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AuthHandler) PostAuthOtpRequest(ctx context.Context, request PostAuthOtpRequestRequestObject) (PostAuthOtpRequestResponseObject, error) {
|
func (h AuthHandler) PostAuthOtpRequest(ctx context.Context, request PostAuthOtpRequestRequestObject) (PostAuthOtpRequestResponseObject, error) {
|
||||||
redisClient := redis.GetClient()
|
err := h.service.OtpRequest(ctx, request.Body.PhoneNumber)
|
||||||
|
|
||||||
// TODO implement OTP request logic
|
|
||||||
|
|
||||||
err := redisClient.Do(ctx, redisClient.B().Set().Key("otp:"+request.Body.PhoneNumber).Value("123456").Build()).Error()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return PostAuthOtpRequest400JSONResponse{
|
return PostAuthOtpRequest400JSONResponse{
|
||||||
Message: "Failed to set OTP in Redis",
|
Message: err.Error(),
|
||||||
Ok: false,
|
Ok: false,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return PostAuthOtpRequest200JSONResponse{
|
return PostAuthOtpRequest200JSONResponse{
|
||||||
Message: "Код успешно отправлен",
|
Message: "OTP request successful",
|
||||||
Ok: true,
|
Ok: true,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a AuthHandler) PostAuthOtpVerify(ctx context.Context, request PostAuthOtpVerifyRequestObject) (PostAuthOtpVerifyResponseObject, error) {
|
func (h AuthHandler) PostAuthOtpVerify(ctx context.Context, request PostAuthOtpVerifyRequestObject) (PostAuthOtpVerifyResponseObject, error) {
|
||||||
redisClient := redis.GetClient()
|
redirectUrl, err := h.service.OtpVerify(ctx, request.Body.PhoneNumber, request.Body.Otp, request.Body.LoginChallenge)
|
||||||
hydraClient := hydra_client.GetClient()
|
|
||||||
|
|
||||||
sentOtp, err := redisClient.Do(ctx, redisClient.B().Get().Key("otp:"+request.Body.PhoneNumber).Build()).ToString()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return PostAuthOtpVerify400JSONResponse{
|
return PostAuthOtpVerify400JSONResponse{
|
||||||
RedirectUrl: "",
|
Message: err.Error(),
|
||||||
Ok: false,
|
Ok: false,
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
if sentOtp != request.Body.Otp {
|
|
||||||
return PostAuthOtpVerify400JSONResponse{
|
|
||||||
RedirectUrl: "",
|
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
|
}, nil
|
||||||
}
|
}
|
||||||
return PostAuthOtpVerify200JSONResponse{
|
return PostAuthOtpVerify200JSONResponse{
|
||||||
RedirectUrl: hydraResponse.RedirectTo,
|
Message: "OTP verification successful",
|
||||||
Ok: true,
|
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
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ StrictServerInterface = (*AuthHandler)(nil)
|
var _ StrictServerInterface = (*AuthHandler)(nil)
|
||||||
|
|
||||||
func NewAuthHandler() *AuthHandler {
|
func NewAuthHandler(service service.AuthService) *AuthHandler {
|
||||||
return &AuthHandler{}
|
return &AuthHandler{service: service}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterApp(router fiber.Router) {
|
func (h AuthHandler) RegisterRoutes(router fiber.Router) {
|
||||||
//authGroup := router.Group("/auth")
|
server := NewStrictHandler(h, nil)
|
||||||
server := NewStrictHandler(NewAuthHandler(), nil)
|
|
||||||
RegisterHandlers(router, server)
|
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/gofiber/fiber/v2"
|
||||||
"github.com/oapi-codegen/runtime"
|
"github.com/oapi-codegen/runtime"
|
||||||
openapi_types "github.com/oapi-codegen/runtime/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// User defines model for User.
|
// User defines model for User.
|
||||||
type User struct {
|
type User struct {
|
||||||
Email openapi_types.Email `json:"email"`
|
PhoneNumber string `json:"phone_number"`
|
||||||
Id string `json:"id"`
|
Uuid string `json:"uuid"`
|
||||||
Username string `json:"username"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserCreate defines model for UserCreate.
|
// UserCreate defines model for UserCreate.
|
||||||
type UserCreate struct {
|
type UserCreate struct {
|
||||||
Email openapi_types.Email `json:"email"`
|
PhoneNumber string `json:"phone_number"`
|
||||||
Password string `json:"password"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserUpdate defines model for UserUpdate.
|
// CreateUserJSONRequestBody defines body for CreateUser for application/json ContentType.
|
||||||
type UserUpdate struct {
|
type CreateUserJSONRequestBody = UserCreate
|
||||||
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
|
|
||||||
|
|
||||||
// ServerInterface represents all server handlers.
|
// ServerInterface represents all server handlers.
|
||||||
type ServerInterface interface {
|
type ServerInterface interface {
|
||||||
// Get all users
|
// Create a new user with phone number
|
||||||
// (GET /users)
|
|
||||||
GetUsers(c *fiber.Ctx) error
|
|
||||||
// Create a new user
|
|
||||||
// (POST /users)
|
// (POST /users)
|
||||||
PostUsers(c *fiber.Ctx) error
|
CreateUser(c *fiber.Ctx) error
|
||||||
// Delete a user by ID
|
|
||||||
// (DELETE /users/{userId})
|
|
||||||
DeleteUsersUserId(c *fiber.Ctx, userId string) error
|
|
||||||
// Get a user by ID
|
// Get a user by ID
|
||||||
// (GET /users/{userId})
|
// (GET /users/{userId})
|
||||||
GetUsersUserId(c *fiber.Ctx, userId string) error
|
GetUserById(c *fiber.Ctx, userId string) error
|
||||||
// Update a user by ID
|
|
||||||
// (PUT /users/{userId})
|
|
||||||
PutUsersUserId(c *fiber.Ctx, userId string) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerInterfaceWrapper converts contexts to parameters.
|
// ServerInterfaceWrapper converts contexts to parameters.
|
||||||
@ -64,20 +42,14 @@ type ServerInterfaceWrapper struct {
|
|||||||
|
|
||||||
type MiddlewareFunc fiber.Handler
|
type MiddlewareFunc fiber.Handler
|
||||||
|
|
||||||
// GetUsers operation middleware
|
// CreateUser operation middleware
|
||||||
func (siw *ServerInterfaceWrapper) GetUsers(c *fiber.Ctx) error {
|
func (siw *ServerInterfaceWrapper) CreateUser(c *fiber.Ctx) error {
|
||||||
|
|
||||||
return siw.Handler.GetUsers(c)
|
return siw.Handler.CreateUser(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostUsers operation middleware
|
// GetUserById operation middleware
|
||||||
func (siw *ServerInterfaceWrapper) PostUsers(c *fiber.Ctx) error {
|
func (siw *ServerInterfaceWrapper) GetUserById(c *fiber.Ctx) error {
|
||||||
|
|
||||||
return siw.Handler.PostUsers(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteUsersUserId operation middleware
|
|
||||||
func (siw *ServerInterfaceWrapper) DeleteUsersUserId(c *fiber.Ctx) error {
|
|
||||||
|
|
||||||
var err 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 fiber.NewError(fiber.StatusBadRequest, fmt.Errorf("Invalid format for parameter userId: %w", err).Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return siw.Handler.DeleteUsersUserId(c, userId)
|
return siw.Handler.GetUserById(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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FiberServerOptions provides options for the Fiber server.
|
// 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.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.Get(options.BaseURL+"/users/:userId", wrapper.GetUserById)
|
||||||
|
|
||||||
router.Delete(options.BaseURL+"/users/:userId", wrapper.DeleteUsersUserId)
|
|
||||||
|
|
||||||
router.Get(options.BaseURL+"/users/:userId", wrapper.GetUsersUserId)
|
|
||||||
|
|
||||||
router.Put(options.BaseURL+"/users/:userId", wrapper.PutUsersUserId)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUsersRequestObject struct {
|
type CreateUserRequestObject struct {
|
||||||
|
Body *CreateUserJSONRequestBody
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUsersResponseObject interface {
|
type CreateUserResponseObject interface {
|
||||||
VisitGetUsersResponse(ctx *fiber.Ctx) error
|
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.Response().Header.Set("Content-Type", "application/json")
|
||||||
ctx.Status(200)
|
ctx.Status(200)
|
||||||
|
|
||||||
return ctx.JSON(&response)
|
return ctx.JSON(&response)
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostUsersRequestObject struct {
|
type GetUserByIdRequestObject 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 {
|
|
||||||
UserId string `json:"userId"`
|
UserId string `json:"userId"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeleteUsersUserIdResponseObject interface {
|
type GetUserByIdResponseObject interface {
|
||||||
VisitDeleteUsersUserIdResponse(ctx *fiber.Ctx) error
|
VisitGetUserByIdResponse(ctx *fiber.Ctx) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeleteUsersUserId204Response struct {
|
type GetUserById200JSONResponse struct {
|
||||||
|
User User `json:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (response DeleteUsersUserId204Response) VisitDeleteUsersUserIdResponse(ctx *fiber.Ctx) error {
|
func (response GetUserById200JSONResponse) VisitGetUserByIdResponse(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 {
|
|
||||||
ctx.Response().Header.Set("Content-Type", "application/json")
|
ctx.Response().Header.Set("Content-Type", "application/json")
|
||||||
ctx.Status(200)
|
ctx.Status(200)
|
||||||
|
|
||||||
return ctx.JSON(&response)
|
return ctx.JSON(&response)
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUsersUserId404Response struct {
|
type GetUserById404JSONResponse struct {
|
||||||
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (response GetUsersUserId404Response) VisitGetUsersUserIdResponse(ctx *fiber.Ctx) error {
|
func (response GetUserById404JSONResponse) VisitGetUserByIdResponse(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 {
|
|
||||||
ctx.Response().Header.Set("Content-Type", "application/json")
|
ctx.Response().Header.Set("Content-Type", "application/json")
|
||||||
ctx.Status(200)
|
ctx.Status(404)
|
||||||
|
|
||||||
return ctx.JSON(&response)
|
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.
|
// StrictServerInterface represents all server handlers.
|
||||||
type StrictServerInterface interface {
|
type StrictServerInterface interface {
|
||||||
// Get all users
|
// Create a new user with phone number
|
||||||
// (GET /users)
|
|
||||||
GetUsers(ctx context.Context, request GetUsersRequestObject) (GetUsersResponseObject, error)
|
|
||||||
// Create a new user
|
|
||||||
// (POST /users)
|
// (POST /users)
|
||||||
PostUsers(ctx context.Context, request PostUsersRequestObject) (PostUsersResponseObject, error)
|
CreateUser(ctx context.Context, request CreateUserRequestObject) (CreateUserResponseObject, error)
|
||||||
// Delete a user by ID
|
|
||||||
// (DELETE /users/{userId})
|
|
||||||
DeleteUsersUserId(ctx context.Context, request DeleteUsersUserIdRequestObject) (DeleteUsersUserIdResponseObject, error)
|
|
||||||
// Get a user by ID
|
// Get a user by ID
|
||||||
// (GET /users/{userId})
|
// (GET /users/{userId})
|
||||||
GetUsersUserId(ctx context.Context, request GetUsersUserIdRequestObject) (GetUsersUserIdResponseObject, error)
|
GetUserById(ctx context.Context, request GetUserByIdRequestObject) (GetUserByIdResponseObject, error)
|
||||||
// Update a user by ID
|
|
||||||
// (PUT /users/{userId})
|
|
||||||
PutUsersUserId(ctx context.Context, request PutUsersUserIdRequestObject) (PutUsersUserIdResponseObject, error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type StrictHandlerFunc func(ctx *fiber.Ctx, args interface{}) (interface{}, error)
|
type StrictHandlerFunc func(ctx *fiber.Ctx, args interface{}) (interface{}, error)
|
||||||
@ -297,54 +161,29 @@ type strictHandler struct {
|
|||||||
middlewares []StrictMiddlewareFunc
|
middlewares []StrictMiddlewareFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUsers operation middleware
|
// CreateUser operation middleware
|
||||||
func (sh *strictHandler) GetUsers(ctx *fiber.Ctx) error {
|
func (sh *strictHandler) CreateUser(ctx *fiber.Ctx) error {
|
||||||
var request GetUsersRequestObject
|
var request CreateUserRequestObject
|
||||||
|
|
||||||
handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) {
|
var body CreateUserJSONRequestBody
|
||||||
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
|
|
||||||
if err := ctx.BodyParser(&body); err != nil {
|
if err := ctx.BodyParser(&body); err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
request.Body = &body
|
request.Body = &body
|
||||||
|
|
||||||
handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) {
|
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 {
|
for _, middleware := range sh.middlewares {
|
||||||
handler = middleware(handler, "PostUsers")
|
handler = middleware(handler, "CreateUser")
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := handler(ctx, request)
|
response, err := handler(ctx, request)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||||
} else if validResponse, ok := response.(PostUsersResponseObject); ok {
|
} else if validResponse, ok := response.(CreateUserResponseObject); ok {
|
||||||
if err := validResponse.VisitPostUsersResponse(ctx); err != nil {
|
if err := validResponse.VisitCreateUserResponse(ctx); err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
} else if response != nil {
|
} else if response != nil {
|
||||||
@ -353,85 +192,25 @@ func (sh *strictHandler) PostUsers(ctx *fiber.Ctx) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUsersUserId operation middleware
|
// GetUserById operation middleware
|
||||||
func (sh *strictHandler) DeleteUsersUserId(ctx *fiber.Ctx, userId string) error {
|
func (sh *strictHandler) GetUserById(ctx *fiber.Ctx, userId string) error {
|
||||||
var request DeleteUsersUserIdRequestObject
|
var request GetUserByIdRequestObject
|
||||||
|
|
||||||
request.UserId = userId
|
request.UserId = userId
|
||||||
|
|
||||||
handler := func(ctx *fiber.Ctx, request interface{}) (interface{}, error) {
|
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 {
|
for _, middleware := range sh.middlewares {
|
||||||
handler = middleware(handler, "DeleteUsersUserId")
|
handler = middleware(handler, "GetUserById")
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := handler(ctx, request)
|
response, err := handler(ctx, request)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||||
} else if validResponse, ok := response.(DeleteUsersUserIdResponseObject); ok {
|
} else if validResponse, ok := response.(GetUserByIdResponseObject); ok {
|
||||||
if err := validResponse.VisitDeleteUsersUserIdResponse(ctx); err != nil {
|
if err := validResponse.VisitGetUserByIdResponse(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 {
|
|
||||||
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
return fiber.NewError(fiber.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
} else if response != nil {
|
} else if response != nil {
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
package handler
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"git.logidex.ru/fakz9/logidex-id/internal/api/user/service"
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/jinzhu/copier"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserHandler struct {
|
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)
|
var _ StrictServerInterface = (*UserHandler)(nil)
|
||||||
|
|
||||||
func (u UserHandler) GetUsers(ctx context.Context, request GetUsersRequestObject) (GetUsersResponseObject, error) {
|
func NewUserHandler(
|
||||||
var response = make([]User, 0)
|
service service.UserService,
|
||||||
|
) *UserHandler {
|
||||||
return GetUsers200JSONResponse(response), nil
|
return &UserHandler{service: service}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u UserHandler) PostUsers(ctx context.Context, request PostUsersRequestObject) (PostUsersResponseObject, error) {
|
func (h UserHandler) RegisterRoutes(router fiber.Router) {
|
||||||
//TODO implement me
|
server := NewStrictHandler(h, nil)
|
||||||
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)
|
|
||||||
RegisterHandlers(router, server)
|
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
|
package service
|
||||||
|
|
||||||
import (
|
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 {
|
type UserService interface {
|
||||||
repo *repository.UserRepo
|
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 {
|
func (u userService) GetUserByUuid(ctx context.Context, uuid string) (*domain.User, error) {
|
||||||
return &UserService{repo: repo}
|
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
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"log"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
@ -20,11 +21,16 @@ type Config struct {
|
|||||||
Host string
|
Host string
|
||||||
Password string
|
Password string
|
||||||
}
|
}
|
||||||
|
DB struct {
|
||||||
|
Host string
|
||||||
|
Port int
|
||||||
|
User string
|
||||||
|
Password string
|
||||||
|
Dbname string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var Cfg *Config
|
func NewConfig() Config {
|
||||||
|
|
||||||
func Init() {
|
|
||||||
err := godotenv.Load()
|
err := godotenv.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error loading .env file")
|
log.Println("Error loading .env file")
|
||||||
@ -48,5 +54,5 @@ func Init() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Unable to decode config into struct, %v", err)
|
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)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
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 (
|
import (
|
||||||
"git.logidex.ru/fakz9/logidex-id/internal/config"
|
"git.logidex.ru/fakz9/logidex-id/internal/config"
|
||||||
hydraApi "github.com/ory/hydra-client-go"
|
hydraApi "github.com/ory/hydra-client-go"
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
func NewHydraClient(appConfig config.Config) *hydraApi.APIClient {
|
||||||
client *hydraApi.APIClient
|
|
||||||
initClient sync.Once
|
|
||||||
)
|
|
||||||
|
|
||||||
func InitClient() {
|
|
||||||
cfg := hydraApi.NewConfiguration()
|
cfg := hydraApi.NewConfiguration()
|
||||||
cfg.AddDefaultHeader("X-Secret", config.Cfg.Hydra.Password)
|
cfg.AddDefaultHeader("X-Secret", appConfig.Hydra.Password)
|
||||||
cfg.Servers = []hydraApi.ServerConfiguration{
|
cfg.Servers = []hydraApi.ServerConfiguration{
|
||||||
{
|
{
|
||||||
URL: config.Cfg.Hydra.Host,
|
URL: appConfig.Hydra.Host,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
client = hydraApi.NewAPIClient(cfg)
|
return hydraApi.NewAPIClient(cfg)
|
||||||
}
|
|
||||||
|
|
||||||
func GetClient() *hydraApi.APIClient {
|
|
||||||
initClient.Do(InitClient)
|
|
||||||
return client
|
|
||||||
}
|
}
|
||||||
|
|||||||
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
|
package redis
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"git.logidex.ru/fakz9/logidex-id/internal/config"
|
"git.logidex.ru/fakz9/logidex-id/internal/config"
|
||||||
"github.com/redis/rueidis"
|
"github.com/redis/rueidis"
|
||||||
"strconv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var client rueidis.Client
|
func NewRedisClient(cfg config.Config) rueidis.Client {
|
||||||
|
|
||||||
func Init() error {
|
|
||||||
var err error
|
var err error
|
||||||
client, err = rueidis.NewClient(rueidis.ClientOption{
|
client, err := rueidis.NewClient(rueidis.ClientOption{
|
||||||
// Set the address of your Redis server
|
// Set the address of your Redis server
|
||||||
InitAddress: []string{config.Cfg.Redis.Host + ":" + strconv.Itoa(config.Cfg.Redis.Port)},
|
InitAddress: []string{cfg.Redis.Host + ":" + strconv.Itoa(cfg.Redis.Port)},
|
||||||
Password: config.Cfg.Redis.Password,
|
Password: cfg.Redis.Password,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetClient() rueidis.Client {
|
|
||||||
return 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