mirror of
https://github.com/emo2007/block-accounting.git
synced 2025-04-04 13:46:27 +00:00
init
This commit is contained in:
parent
ebf2edafff
commit
42c577f80a
13
backend/cmd/commands/commands.go
Normal file
13
backend/cmd/commands/commands.go
Normal file
@ -0,0 +1,13 @@
|
||||
package commands
|
||||
|
||||
import "github.com/urfave/cli/v2"
|
||||
|
||||
var commandPool []*cli.Command
|
||||
|
||||
func Register(c *cli.Command) {
|
||||
commandPool = append(commandPool, c)
|
||||
}
|
||||
|
||||
func Commands() []*cli.Command {
|
||||
return commandPool
|
||||
}
|
55
backend/cmd/main.go
Normal file
55
backend/cmd/main.go
Normal file
@ -0,0 +1,55 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/emochka2007/block-accounting/cmd/commands"
|
||||
|
||||
cli "github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := &cli.App{
|
||||
Name: "biocom-ioannes",
|
||||
Version: "0.0.1a",
|
||||
Commands: commands.Commands(),
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "log-level",
|
||||
Value: "debug",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "address",
|
||||
Value: "localhost:8080",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "db-host",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "db-user",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "db-password",
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
|
||||
// todo build config
|
||||
|
||||
// todo build service
|
||||
|
||||
// todo run service
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
19
backend/go.mod
Normal file
19
backend/go.mod
Normal file
@ -0,0 +1,19 @@
|
||||
module github.com/emochka2007/block-accounting
|
||||
|
||||
go 1.22.2
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.16.0
|
||||
github.com/go-chi/chi/v5 v5.0.12
|
||||
github.com/urfave/cli/v2 v2.27.2
|
||||
golang.org/x/sync v0.7.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
)
|
23
backend/go.sum
Normal file
23
backend/go.sum
Normal file
@ -0,0 +1,23 @@
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||
github.com/go-chi/chi/v5 v5.0.12 h1:9euLV5sTrTNTRUU9POmDUvfxyj6LAABLUcEWO+JJb4s=
|
||||
github.com/go-chi/chi/v5 v5.0.12/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
|
||||
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
|
||||
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
1
backend/internal/factory/service.go
Normal file
1
backend/internal/factory/service.go
Normal file
@ -0,0 +1 @@
|
||||
package factory
|
29
backend/internal/interface/controllers/ping.go
Normal file
29
backend/internal/interface/controllers/ping.go
Normal file
@ -0,0 +1,29 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type PingController interface {
|
||||
HandlePing(ctx context.Context, req *http.Request, w http.ResponseWriter) error
|
||||
}
|
||||
|
||||
type pingController struct {
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
func NewPingController(
|
||||
log *slog.Logger,
|
||||
) PingController {
|
||||
return &pingController{
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *pingController) HandlePing(ctx context.Context, req *http.Request, w http.ResponseWriter) error {
|
||||
_, err := w.Write([]byte("pong"))
|
||||
|
||||
return err
|
||||
}
|
13
backend/internal/interface/controllers/root.go
Normal file
13
backend/internal/interface/controllers/root.go
Normal file
@ -0,0 +1,13 @@
|
||||
package controllers
|
||||
|
||||
type RootController struct {
|
||||
Ping PingController
|
||||
}
|
||||
|
||||
func NewRootController(
|
||||
ping PingController,
|
||||
) *RootController {
|
||||
return &RootController{
|
||||
Ping: ping,
|
||||
}
|
||||
}
|
23
backend/internal/interface/rest/errors.go
Normal file
23
backend/internal/interface/rest/errors.go
Normal file
@ -0,0 +1,23 @@
|
||||
package rest
|
||||
|
||||
import "net/http"
|
||||
|
||||
type apiError struct {
|
||||
Code int `json:"code"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
func buildApiError(code int, message string) apiError {
|
||||
return apiError{
|
||||
Code: code,
|
||||
Error: message,
|
||||
}
|
||||
}
|
||||
|
||||
func mapError(_ error) apiError {
|
||||
// todo map typed errors
|
||||
switch {
|
||||
default:
|
||||
return buildApiError(http.StatusInternalServerError, "Internal Server Error")
|
||||
}
|
||||
}
|
108
backend/internal/interface/rest/server.go
Normal file
108
backend/internal/interface/rest/server.go
Normal file
@ -0,0 +1,108 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/emochka2007/block-accounting/internal/interface/controllers"
|
||||
"github.com/emochka2007/block-accounting/internal/logger"
|
||||
"github.com/go-chi/chi/v5"
|
||||
mw "github.com/go-chi/chi/v5/middleware"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
*chi.Mux
|
||||
|
||||
ctx context.Context
|
||||
|
||||
log *slog.Logger
|
||||
addr string
|
||||
tls bool
|
||||
controllers *controllers.RootController
|
||||
|
||||
closeMu sync.RWMutex
|
||||
closed bool
|
||||
}
|
||||
|
||||
func NewServer(
|
||||
log *slog.Logger,
|
||||
addr string,
|
||||
controllers *controllers.RootController,
|
||||
) *Server {
|
||||
s := &Server{
|
||||
log: log,
|
||||
addr: addr,
|
||||
controllers: controllers,
|
||||
}
|
||||
|
||||
s.buildRouter()
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Server) Serve(ctx context.Context) error {
|
||||
s.ctx = ctx
|
||||
|
||||
if s.tls {
|
||||
return http.ListenAndServeTLS(s.addr, "/todo", "/todo", s)
|
||||
}
|
||||
|
||||
return http.ListenAndServe(s.addr, s)
|
||||
}
|
||||
|
||||
func (s *Server) Close() {
|
||||
s.closeMu.Lock()
|
||||
defer s.closeMu.Unlock()
|
||||
|
||||
s.closed = true
|
||||
}
|
||||
|
||||
func (s *Server) buildRouter() {
|
||||
s.Mux = chi.NewRouter()
|
||||
|
||||
s.With(mw.Recoverer)
|
||||
s.With(mw.RequestID)
|
||||
s.With(s.handleMw)
|
||||
|
||||
s.Get("/ping", s.handlePing)
|
||||
|
||||
// todo build rest api router
|
||||
}
|
||||
|
||||
func (s *Server) responseError(w http.ResponseWriter, e error) {
|
||||
apiErr := mapError(e)
|
||||
|
||||
out, err := json.Marshal(apiErr)
|
||||
if err != nil {
|
||||
s.log.Error("error marshal api error", logger.Err(err))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(apiErr.Code)
|
||||
w.Write(out)
|
||||
}
|
||||
|
||||
func (s *Server) handleMw(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
s.closeMu.RLock()
|
||||
defer s.closeMu.Unlock()
|
||||
|
||||
if s.closed { // keep mutex closed
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func (s *Server) handlePing(w http.ResponseWriter, req *http.Request) {
|
||||
if err := s.controllers.Ping.HandlePing(s.ctx, req, w); err != nil {
|
||||
s.responseError(w, err)
|
||||
}
|
||||
}
|
88
backend/internal/logger/logger.go
Normal file
88
backend/internal/logger/logger.go
Normal file
@ -0,0 +1,88 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
type LoggerBuilder struct {
|
||||
local bool
|
||||
addSource bool
|
||||
lvl slog.Level
|
||||
writers []io.Writer
|
||||
}
|
||||
|
||||
func (b *LoggerBuilder) WithWriter(w io.Writer) *LoggerBuilder {
|
||||
b.writers = append(b.writers, w)
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *LoggerBuilder) WithLevel(l slog.Level) *LoggerBuilder {
|
||||
b.lvl = l
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *LoggerBuilder) Local() *LoggerBuilder {
|
||||
b.local = true
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *LoggerBuilder) WithSource() *LoggerBuilder {
|
||||
b.addSource = true
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *LoggerBuilder) Build() *slog.Logger {
|
||||
w := io.MultiWriter(b.writers...)
|
||||
|
||||
if b.local {
|
||||
opts := PrettyHandlerOptions{
|
||||
SlogOpts: &slog.HandlerOptions{
|
||||
Level: b.lvl,
|
||||
AddSource: b.addSource,
|
||||
},
|
||||
}
|
||||
|
||||
handler := opts.NewPrettyHandler(w)
|
||||
|
||||
return slog.New(handler)
|
||||
}
|
||||
|
||||
return slog.New(
|
||||
slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
||||
Level: b.lvl,
|
||||
AddSource: b.addSource,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func newLogger(lvl slog.Level, w io.Writer) *slog.Logger {
|
||||
return slog.New(
|
||||
slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: lvl}),
|
||||
)
|
||||
}
|
||||
|
||||
func Err(err error) slog.Attr {
|
||||
return slog.Attr{
|
||||
Key: "error",
|
||||
Value: slog.StringValue(err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
func MapLevel(lvl string) slog.Level {
|
||||
switch lvl {
|
||||
case "dev", "local", "debug":
|
||||
return slog.LevelDebug
|
||||
case "warn":
|
||||
return slog.LevelWarn
|
||||
case "error":
|
||||
return slog.LevelError
|
||||
default:
|
||||
return slog.LevelInfo
|
||||
}
|
||||
}
|
32
backend/internal/logger/slogdiscard.go
Normal file
32
backend/internal/logger/slogdiscard.go
Normal file
@ -0,0 +1,32 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
func NewDiscardLogger() *slog.Logger {
|
||||
return slog.New(NewDiscardHandler())
|
||||
}
|
||||
|
||||
type DiscardHandler struct{}
|
||||
|
||||
func NewDiscardHandler() *DiscardHandler {
|
||||
return &DiscardHandler{}
|
||||
}
|
||||
|
||||
func (h *DiscardHandler) Handle(_ context.Context, _ slog.Record) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *DiscardHandler) WithAttrs(_ []slog.Attr) slog.Handler {
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *DiscardHandler) WithGroup(_ string) slog.Handler {
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *DiscardHandler) Enabled(_ context.Context, _ slog.Level) bool {
|
||||
return false
|
||||
}
|
97
backend/internal/logger/slogpretty.go
Normal file
97
backend/internal/logger/slogpretty.go
Normal file
@ -0,0 +1,97 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
stdlog "log"
|
||||
"log/slog"
|
||||
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
type PrettyHandlerOptions struct {
|
||||
SlogOpts *slog.HandlerOptions
|
||||
}
|
||||
|
||||
type PrettyHandler struct {
|
||||
opts PrettyHandlerOptions
|
||||
slog.Handler
|
||||
l *stdlog.Logger
|
||||
attrs []slog.Attr
|
||||
}
|
||||
|
||||
func (opts PrettyHandlerOptions) NewPrettyHandler(
|
||||
out io.Writer,
|
||||
) *PrettyHandler {
|
||||
h := &PrettyHandler{
|
||||
Handler: slog.NewJSONHandler(out, opts.SlogOpts),
|
||||
l: stdlog.New(out, "", 0),
|
||||
}
|
||||
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *PrettyHandler) Handle(_ context.Context, r slog.Record) error {
|
||||
level := r.Level.String() + ":"
|
||||
|
||||
switch r.Level {
|
||||
case slog.LevelDebug:
|
||||
level = color.MagentaString(level)
|
||||
case slog.LevelInfo:
|
||||
level = color.BlueString(level)
|
||||
case slog.LevelWarn:
|
||||
level = color.YellowString(level)
|
||||
case slog.LevelError:
|
||||
level = color.RedString(level)
|
||||
}
|
||||
|
||||
fields := make(map[string]interface{}, r.NumAttrs())
|
||||
|
||||
r.Attrs(func(a slog.Attr) bool {
|
||||
fields[a.Key] = a.Value.Any()
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
for _, a := range h.attrs {
|
||||
fields[a.Key] = a.Value.Any()
|
||||
}
|
||||
|
||||
var b []byte
|
||||
var err error
|
||||
|
||||
if len(fields) > 0 {
|
||||
b, err = json.MarshalIndent(fields, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
timeStr := r.Time.Format("[15:05:05.000]")
|
||||
msg := color.CyanString(r.Message)
|
||||
|
||||
h.l.Println(
|
||||
timeStr,
|
||||
level,
|
||||
msg,
|
||||
color.WhiteString(string(b)),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *PrettyHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
return &PrettyHandler{
|
||||
Handler: h.Handler,
|
||||
l: h.l,
|
||||
attrs: attrs,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *PrettyHandler) WithGroup(name string) slog.Handler {
|
||||
return &PrettyHandler{
|
||||
Handler: h.Handler.WithGroup(name),
|
||||
l: h.l,
|
||||
}
|
||||
}
|
41
backend/internal/service/service.go
Normal file
41
backend/internal/service/service.go
Normal file
@ -0,0 +1,41 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/emochka2007/block-accounting/internal/interface/rest"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type Service interface {
|
||||
Run(ctx context.Context) error
|
||||
Stop()
|
||||
}
|
||||
|
||||
type service struct {
|
||||
log *slog.Logger
|
||||
rest *rest.Server
|
||||
}
|
||||
|
||||
func NewService(
|
||||
log *slog.Logger,
|
||||
rest *rest.Server,
|
||||
) Service {
|
||||
return &service{
|
||||
log: log,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *service) Run(ctx context.Context) error {
|
||||
g, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
g.Go(func() error {
|
||||
return s.rest.Serve(ctx)
|
||||
})
|
||||
|
||||
return g.Wait()
|
||||
}
|
||||
|
||||
func (s *service) Stop() {
|
||||
}
|
85
backend/internal/sqlutils/tx.go
Normal file
85
backend/internal/sqlutils/tx.go
Normal file
@ -0,0 +1,85 @@
|
||||
package sqltools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type DBTX interface {
|
||||
Query(query string, args ...any) (*sql.Rows, error)
|
||||
QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
|
||||
QueryRow(query string, args ...any) *sql.Row
|
||||
QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
|
||||
Exec(query string, args ...any) (sql.Result, error)
|
||||
ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
|
||||
}
|
||||
|
||||
type TransactionalStorage interface {
|
||||
Transaction(ctx context.Context, db *sql.DB, f func(ctx context.Context) error) error
|
||||
Conn(ctx context.Context) DBTX
|
||||
}
|
||||
|
||||
type txCtxKey struct{}
|
||||
|
||||
func Transaction(ctx context.Context, db *sql.DB, fn func(context.Context) error) error {
|
||||
var err error
|
||||
|
||||
var tx *sql.Tx = new(sql.Tx)
|
||||
|
||||
hasExternalTx := hasExternalTransaction(ctx)
|
||||
|
||||
defer func() {
|
||||
if hasExternalTx {
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error perform operation. %w", err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if rbErr := tx.Rollback(); rbErr != nil {
|
||||
err = errors.Join(fmt.Errorf("error rollback transaction. %w", rbErr), err)
|
||||
return
|
||||
}
|
||||
|
||||
err = fmt.Errorf("error execute transactional operation. %w", err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if commitErr := tx.Commit(); commitErr != nil {
|
||||
if rbErr := tx.Rollback(); rbErr != nil {
|
||||
err = errors.Join(fmt.Errorf("error rollback transaction. %w", rbErr), commitErr, err)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err = fmt.Errorf("error commit transaction. %w", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if !hasExternalTx {
|
||||
tx, err = db.BeginTx(ctx, &sql.TxOptions{
|
||||
Isolation: sql.LevelRepeatableRead,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error begin transaction. %w", err)
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, txCtxKey{}, tx)
|
||||
}
|
||||
|
||||
return fn(ctx)
|
||||
}
|
||||
|
||||
func hasExternalTransaction(ctx context.Context) bool {
|
||||
if _, ok := ctx.Value(txCtxKey{}).(*sql.Tx); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
Loading…
Reference in New Issue
Block a user