Reorganaze modules, add auth processing.

This commit is contained in:
2026-03-13 00:07:23 +03:00
parent 95b1b867db
commit 970e64745b
14 changed files with 220 additions and 97 deletions

View File

@@ -12,6 +12,7 @@ import (
"payouts/internal/log" "payouts/internal/log"
"payouts/internal/service/cache" "payouts/internal/service/cache"
"payouts/internal/service/database" "payouts/internal/service/database"
"payouts/internal/service/yookassa"
) )
func main() { func main() {
@@ -21,6 +22,7 @@ func main() {
cache.Module, cache.Module,
config.Module, config.Module,
database.Module, database.Module,
yookassa.Module,
log.Module, log.Module,
fx.WithLogger(func() fxevent.Logger { fx.WithLogger(func() fxevent.Logger {

View File

@@ -38,6 +38,8 @@ Cache.TTL = 24h
# Yookassa related props # Yookassa related props
# Base API Url # Base API Url
YooKassa.BaseUrl = https://api.yookassa.ru/v3 YooKassa.BaseUrl = https://api.yookassa.ru/v3
YooKassa.Timeout = 30s
YooKassa.Test = false
# Base API key/secret # Base API key/secret
YooKassa.ApiBaseKey = YooKassa.ApiBaseKey =
YooKassa.ApiBaseSecret = YooKassa.ApiBaseSecret =

View File

@@ -11,7 +11,7 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/fx" "go.uber.org/fx"
"payouts/internal/api/payment" "payouts/internal/api/payout"
"payouts/internal/api/user" "payouts/internal/api/user"
"payouts/internal/api/version" "payouts/internal/api/version"
appConfig "payouts/internal/config" appConfig "payouts/internal/config"
@@ -21,7 +21,7 @@ import (
// Module is a fx module // Module is a fx module
var Module = fx.Options( var Module = fx.Options(
user.Module, user.Module,
payment.Module, payout.Module,
version.Module, version.Module,
monitoring.Module, monitoring.Module,
@@ -37,9 +37,9 @@ type Params struct {
AppConfig *appConfig.App AppConfig *appConfig.App
PaymentHandler payment.Handler PayoutHandler payout.Handler
UserHandler user.Handler UserHandler user.Handler
Version version.Handler Version version.Handler
Metrics monitoring.Metrics Metrics monitoring.Metrics
} }
@@ -66,9 +66,9 @@ func RegisterRoutes(p Params, lc fx.Lifecycle) {
userRouter.HandleFunc(user.RegisterRoute, p.UserHandler.UserRegister).Methods(http.MethodPost) userRouter.HandleFunc(user.RegisterRoute, p.UserHandler.UserRegister).Methods(http.MethodPost)
userRouter.HandleFunc(user.LoginRoute, p.UserHandler.UserLogin).Methods(http.MethodPost) userRouter.HandleFunc(user.LoginRoute, p.UserHandler.UserLogin).Methods(http.MethodPost)
paymentRouter := apiRouter.PathPrefix(payment.BaseRoute).Subrouter() payoutRouter := apiRouter.PathPrefix(payout.BaseRoute).Subrouter()
paymentRouter.HandleFunc(payment.CreateRoute, p.PaymentHandler.PaymentCreate).Methods(http.MethodPost) payoutRouter.HandleFunc(payout.CreateRoute, p.PayoutHandler.PayoutCreate).Methods(http.MethodPost)
paymentRouter.HandleFunc(payment.CallbackRoute, p.PaymentHandler.PaymentCallback).Methods(http.MethodPost) payoutRouter.HandleFunc(payout.CallbackRoute, p.PayoutHandler.PayoutCallback).Methods(http.MethodPost)
// collect api metrics // collect api metrics
apiRouter.Use(p.Metrics.GetMiddleware()) apiRouter.Use(p.Metrics.GetMiddleware())

View File

@@ -1,17 +0,0 @@
package payment
import (
"net/http"
"go.uber.org/fx"
)
var Module = fx.Options(
fx.Provide(NewPaymentHandler),
)
type Handler interface {
GetSbpBanks(http.ResponseWriter, *http.Request)
PaymentCreate(http.ResponseWriter, *http.Request)
PaymentCallback(http.ResponseWriter, *http.Request)
}

View File

@@ -1,54 +0,0 @@
package payment
import (
"net/http"
"go.uber.org/fx"
"payouts/internal/config"
"payouts/internal/service/cache"
"payouts/internal/service/database"
)
const (
BaseRoute = "/payment"
CreateRoute = "/create"
CallbackRoute = "/callback"
BanksRoute = "/sbp/banks"
)
type paymentHandler struct {
dbService database.Service
cacheService cache.Service
}
// Params represents the module input params
type Params struct {
fx.In
AppConfig *config.App
DbService database.Service
CacheService cache.Service
}
func NewPaymentHandler(p Params) (Handler, error) {
return &paymentHandler{
dbService: p.DbService,
cacheService: p.CacheService,
}, nil
}
// GetSbpBanks implements [Handler].
func (p *paymentHandler) GetSbpBanks(http.ResponseWriter, *http.Request) {
panic("unimplemented")
}
// PaymentCreate implements [Handler].
func (p *paymentHandler) PaymentCreate(http.ResponseWriter, *http.Request) {
panic("unimplemented")
}
// PaymentCallback implements [Handler].
func (p *paymentHandler) PaymentCallback(http.ResponseWriter, *http.Request) {
panic("unimplemented")
}

View File

@@ -0,0 +1,17 @@
package payout
import (
"net/http"
"go.uber.org/fx"
)
var Module = fx.Options(
fx.Provide(NewPayoutHandler),
)
type Handler interface {
GetSbpBanks(http.ResponseWriter, *http.Request)
PayoutCreate(http.ResponseWriter, *http.Request)
PayoutCallback(http.ResponseWriter, *http.Request)
}

View File

@@ -0,0 +1,91 @@
package payout
import (
"errors"
"net/http"
"regexp"
"go.uber.org/fx"
"payouts/internal/config"
"payouts/internal/service/cache"
"payouts/internal/service/database"
"payouts/internal/service/database/orm"
"payouts/internal/service/yookassa"
)
const (
BaseRoute = "/payment"
CreateRoute = "/create"
CallbackRoute = "/callback"
BanksRoute = "/sbp/banks"
)
var authHeaderRe = regexp.MustCompile(`^Bearer\s+(.*)$`)
type payoutHandler struct {
dbService database.Service
cacheService cache.Service
yooKassa yookassa.Service
}
// Params represents the module input params
type Params struct {
fx.In
AppConfig *config.App
DbService database.Service
YooKassa yookassa.Service
CacheService cache.Service
}
func NewPayoutHandler(p Params) (Handler, error) {
return &payoutHandler{
dbService: p.DbService,
cacheService: p.CacheService,
yooKassa: p.YooKassa,
}, nil
}
func (p *payoutHandler) getSession(r *http.Request) (*orm.User, error) {
authHeaderValue := r.Header.Get("Authorization")
if len(authHeaderValue) == 0 {
return nil, errors.New("no valid auth header")
}
matches := authHeaderRe.FindStringSubmatch(authHeaderValue)
if matches == nil {
return nil, errors.New("no valid auth header")
}
sessionId := matches[1]
userSession, err := p.cacheService.GetSession(sessionId)
if err != nil {
return nil, errors.New("session not found")
}
return &userSession, nil
}
// GetSbpBanks implements [Handler].
func (p *payoutHandler) GetSbpBanks(w http.ResponseWriter, r *http.Request) {
panic("unimplemented")
}
// PaymentCreate implements [Handler].
func (p *payoutHandler) PayoutCreate(w http.ResponseWriter, r *http.Request) {
errResponse := func(message string, err error, status int) {
http.Error(w, errors.Join(errors.New(message), err).Error(), status)
}
_, err := p.getSession(r)
if err != nil {
errResponse("unautiorized", err, http.StatusUnauthorized)
}
panic("unimplemented")
}
// PaymentCallback implements [Handler].
func (p *payoutHandler) PayoutCallback(http.ResponseWriter, *http.Request) {
}

46
internal/models/payout.go Normal file
View File

@@ -0,0 +1,46 @@
package models
import (
"fmt"
"strings"
)
type PayoutType int64
const (
SBP PayoutType = iota
YooMoney
)
func (r PayoutType) String() string {
switch r {
case SBP:
return "spb"
case YooMoney:
return "yoo_money"
}
return "unknown"
}
func (r PayoutType) MarshalText() (text []byte, err error) {
return []byte(r.String()), nil
}
func (r *PayoutType) UnmarshalText(text []byte) (err error) {
s := strings.ToLower(string(text))
switch s {
case "spb":
*r = SBP
case "yoo_money":
*r = YooMoney
default:
err = fmt.Errorf("invalid payment type: %s", s)
}
return err
}
type PayoutReq struct {
PayoutType PayoutType `json:"payout_type"`
AccountNumber string `json:"account_number"`
Amount float32 `json:"amount"`
}

View File

@@ -17,6 +17,7 @@ import (
type dbService struct { type dbService struct {
dbType string dbType string
db *gorm.DB db *gorm.DB
ctx context.Context
} }
func NewDatabaseService(conf config.Database) (Service, error) { func NewDatabaseService(conf config.Database) (Service, error) {
@@ -51,7 +52,9 @@ func NewDatabaseService(conf config.Database) (Service, error) {
db.DB() db.DB()
db.AutoMigrate(&orm.User{}) db.AutoMigrate(&orm.User{})
} }
result := &dbService{} result := &dbService{
ctx: context.Background(),
}
result.dbType = conf.Type result.dbType = conf.Type
result.db = db result.db = db
@@ -59,9 +62,9 @@ func NewDatabaseService(conf config.Database) (Service, error) {
} }
func getParams(options ...Optional) *params { func (d *dbService) getParams(options ...Optional) *params {
params := &params{ params := &params{
ctx: context.Background(), ctx: d.ctx,
} }
for _, opt := range options { for _, opt := range options {
opt(params) opt(params)
@@ -71,14 +74,14 @@ func getParams(options ...Optional) *params {
// AddUser implements [Service]. // AddUser implements [Service].
func (d *dbService) CreateUser(userModel orm.User, opts ...Optional) error { func (d *dbService) CreateUser(userModel orm.User, opts ...Optional) error {
p := getParams(opts...) p := d.getParams(opts...)
return gorm.G[orm.User](d.db).Create(p.ctx, &userModel) return gorm.G[orm.User](d.db).Create(p.ctx, &userModel)
} }
// GetUser implements [Service]. // GetUser implements [Service].
func (d *dbService) GetUser(userModel orm.User, opts ...Optional) (orm.User, error) { func (d *dbService) GetUser(userModel orm.User, opts ...Optional) (orm.User, error) {
p := getParams(opts...) p := d.getParams(opts...)
userResp, err := gorm.G[orm.User](d.db).Where(&userModel).First(p.ctx) userResp, err := gorm.G[orm.User](d.db).Where(&userModel).First(p.ctx)
return userResp, err return userResp, err

View File

@@ -0,0 +1 @@
package orm

View File

@@ -1,7 +1,11 @@
package config package config
import "time"
type YooKassa struct { type YooKassa struct {
BaseUrl string BaseUrl string
Timeout time.Duration
Test bool
ApiBaseKey string ApiBaseKey string
ApiBaseSecret string ApiBaseSecret string

View File

@@ -6,6 +6,8 @@ import (
"go.uber.org/fx" "go.uber.org/fx"
"payouts/internal/config" "payouts/internal/config"
"payouts/internal/models"
"payouts/internal/service/database/orm"
) )
var Module = fx.Options( var Module = fx.Options(
@@ -24,6 +26,7 @@ func WithContext(ctx context.Context) Optional {
} }
type Service interface { type Service interface {
CreatePayout(models.PayoutReq, *orm.User, ...Optional)
} }
type Param struct { type Param struct {

View File

@@ -46,7 +46,7 @@ paths:
application/json: application/json:
schema: schema:
type: "object" type: "object"
title: "CreatePaymentRequest" title: "CreatePayoutRequest"
properties: properties:
amount: amount:
allOf: allOf:

View File

@@ -2,6 +2,9 @@ package yookassa
import ( import (
"context" "context"
"net/http"
"payouts/internal/models"
"payouts/internal/service/database/orm"
"payouts/internal/service/yookassa/config" "payouts/internal/service/yookassa/config"
"payouts/internal/service/yookassa/gen" "payouts/internal/service/yookassa/gen"
@@ -11,6 +14,36 @@ import (
type yookassaService struct { type yookassaService struct {
conf config.YooKassa conf config.YooKassa
payClient *gen.Client payClient *gen.Client
ctx context.Context
}
func NewYookassaService(conf config.YooKassa) (Service, error) {
svc := &yookassaService{
conf: conf,
ctx: context.Background(),
}
payClient, err := gen.NewClient(conf.BaseUrl, svc, gen.WithClient(&http.Client{
Timeout: conf.Timeout,
}))
if err != nil {
return nil, err
}
svc.payClient = payClient
// payClient.PaymentsPost()
return svc, nil
}
func (y *yookassaService) getParams(options ...Optional) *params {
params := &params{
ctx: y.ctx,
}
for _, opt := range options {
opt(params)
}
return params
} }
// BasicAuth implements [gen.SecuritySource]. // BasicAuth implements [gen.SecuritySource].
@@ -26,17 +59,9 @@ func (y *yookassaService) OAuth2(ctx context.Context, operationName gen.Operatio
return gen.OAuth2{}, ogenerrors.ErrSkipClientSecurity return gen.OAuth2{}, ogenerrors.ErrSkipClientSecurity
} }
func NewYookassaService(conf config.YooKassa) (Service, error) { // CreatePayout implements [Service].
func (y *yookassaService) CreatePayout(req models.PayoutReq, userSession *orm.User, opts ...Optional) {
params := y.getParams(opts...)
svc := &yookassaService{ y.payClient.PayoutsPost(params.ctx, &gen.PayoutRequest{}, gen.PayoutsPostParams{})
conf: conf,
}
payClient, err := gen.NewClient(conf.BaseUrl, svc)
if err != nil {
return nil, err
}
svc.payClient = payClient
// payClient.PaymentsPost()
return svc, nil
} }