Initial commit
This commit is contained in:
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/bin/*
|
||||||
|
contrib/dbg/.*
|
||||||
|
debug
|
||||||
|
debug.test
|
||||||
|
__debug_bin
|
||||||
|
__debug_bin*
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
|
*.un~
|
||||||
|
*.swp
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.log
|
||||||
|
/logs
|
||||||
|
static
|
||||||
|
testdata/
|
||||||
|
/payouts.properties
|
||||||
20
.vscode/launch.json
vendored
Normal file
20
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Launch Package",
|
||||||
|
"type": "go",
|
||||||
|
"request": "launch",
|
||||||
|
"mode": "auto",
|
||||||
|
"cwd": "${workspaceFolder}/config",
|
||||||
|
"program": "${workspaceFolder}/cmd/payouts",
|
||||||
|
"env": {
|
||||||
|
"CONFIG_PATH": "${workspaceFolder}/payouts.properties"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
19
cmd/payouts/main.go
Normal file
19
cmd/payouts/main.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/fx"
|
||||||
|
|
||||||
|
"payouts/internal/api"
|
||||||
|
"payouts/internal/config"
|
||||||
|
"payouts/internal/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
app := fx.New(
|
||||||
|
api.Module,
|
||||||
|
config.Module,
|
||||||
|
log.Module,
|
||||||
|
)
|
||||||
|
app.Run()
|
||||||
|
}
|
||||||
22
config/payouts.properties
Normal file
22
config/payouts.properties
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Server.Port = :8080
|
||||||
|
Server.WriteTimeout = 35s
|
||||||
|
Server.ReadTimeout = 35s
|
||||||
|
Server.EnablePProfEndpoints = false
|
||||||
|
|
||||||
|
Socket.MaxHttpBufferSize = 2097152
|
||||||
|
Socket.PingInterval = 25s
|
||||||
|
Socket.PingTimeout = 20s
|
||||||
|
Socket.Debug = false
|
||||||
|
|
||||||
|
# Prometheus settings
|
||||||
|
Metrics.Endpoint = /metrics
|
||||||
|
Metrics.HistogramBuckets = 0.001,0.002,0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10
|
||||||
|
|
||||||
|
Metrics.Http.HistogramEnabled = true
|
||||||
|
Metrics.Http.Buckets = 0.001,0.002,0.005,0.01,0.025,0.05,0.1,0.25,0.5,1,2.5,5,10
|
||||||
|
|
||||||
|
Log.Level = DEBUG
|
||||||
|
Log.FilePath = ./logs/payouts.log
|
||||||
|
Log.TextOutput = false
|
||||||
|
Log.StdoutEnabled = true
|
||||||
|
Log.FileEnabled = false
|
||||||
42
go.mod
Normal file
42
go.mod
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
module payouts
|
||||||
|
|
||||||
|
go 1.24.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-viper/encoding/javaproperties v0.1.0
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||||
|
github.com/gorilla/mux v1.8.1
|
||||||
|
github.com/ogier/pflag v0.0.1
|
||||||
|
github.com/prometheus/client_golang v1.23.2
|
||||||
|
github.com/samber/slog-multi v1.7.1
|
||||||
|
github.com/spf13/viper v1.21.0
|
||||||
|
go.uber.org/fx v1.24.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
|
github.com/prometheus/common v0.66.1 // indirect
|
||||||
|
github.com/prometheus/procfs v0.16.1 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
|
github.com/samber/lo v1.52.0 // indirect
|
||||||
|
github.com/samber/slog-common v0.20.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
go.uber.org/dig v1.19.0 // indirect
|
||||||
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
|
go.uber.org/zap v1.26.0 // indirect
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
|
golang.org/x/sys v0.35.0 // indirect
|
||||||
|
golang.org/x/text v0.28.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.36.8 // indirect
|
||||||
|
)
|
||||||
93
go.sum
Normal file
93
go.sum
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/go-viper/encoding/javaproperties v0.1.0 h1:4pQN/pez/rMy9ITZ++SgLH6VIN3zWzNNuWFHKjrpn6w=
|
||||||
|
github.com/go-viper/encoding/javaproperties v0.1.0/go.mod h1:LGaThjx5J/GFdQRJscxLMQsYt0XKAM7IW9YzsJTv6jw=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
|
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
|
github.com/ogier/pflag v0.0.1 h1:RW6JSWSu/RkSatfcLtogGfFgpim5p7ARQ10ECk5O750=
|
||||||
|
github.com/ogier/pflag v0.0.1/go.mod h1:zkFki7tvTa0tafRvTBIZTvzYyAu6kQhPZFnshFFPE+g=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||||
|
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
||||||
|
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
||||||
|
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
||||||
|
github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
||||||
|
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
||||||
|
github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg=
|
||||||
|
github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
|
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
||||||
|
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||||
|
github.com/samber/slog-common v0.20.0 h1:WaLnm/aCvBJSk5nR5aXZTFBaV0B47A+AEaEOiZDeUnc=
|
||||||
|
github.com/samber/slog-common v0.20.0/go.mod h1:+Ozat1jgnnE59UAlmNX1IF3IByHsODnnwf9jUcBZ+m8=
|
||||||
|
github.com/samber/slog-multi v1.7.1 h1:aCLXHRxgU+2v0PVlEOh7phynzM7CRo89ZgFtOwaqVEE=
|
||||||
|
github.com/samber/slog-multi v1.7.1/go.mod h1:A4KQC99deqfkCDJcL/cO3kX6McX7FffQAx/8QHink+c=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
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.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||||
|
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||||
|
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||||
|
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
||||||
|
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
||||||
|
google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
|
||||||
|
google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
95
internal/api/module.go
Normal file
95
internal/api/module.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"net/http/pprof"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
|
||||||
|
"payouts/internal/api/version"
|
||||||
|
appConfig "payouts/internal/config"
|
||||||
|
"payouts/internal/service/monitoring"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Module is a fx module
|
||||||
|
var Module = fx.Options(
|
||||||
|
version.Module,
|
||||||
|
monitoring.Module,
|
||||||
|
fx.Invoke(RegisterRoutes),
|
||||||
|
)
|
||||||
|
|
||||||
|
const BaseRoute = "/api/v1"
|
||||||
|
|
||||||
|
// Params represents the module input params
|
||||||
|
type Params struct {
|
||||||
|
fx.In
|
||||||
|
Logger *slog.Logger
|
||||||
|
|
||||||
|
AppConfig *appConfig.App
|
||||||
|
|
||||||
|
Version version.Handler
|
||||||
|
|
||||||
|
Metrics monitoring.Metrics
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterRoutes registers the api routes and starts the http server
|
||||||
|
func RegisterRoutes(p Params, lc fx.Lifecycle) {
|
||||||
|
|
||||||
|
router := mux.NewRouter()
|
||||||
|
router.StrictSlash(true)
|
||||||
|
|
||||||
|
router.HandleFunc(version.Route, p.Version.VersionHandler).Methods(http.MethodGet)
|
||||||
|
|
||||||
|
if p.AppConfig.Server.EnablePProfEndpoints {
|
||||||
|
router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
||||||
|
router.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
||||||
|
router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
||||||
|
router.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
||||||
|
router.NewRoute().PathPrefix("/debug/pprof/").HandlerFunc(pprof.Index)
|
||||||
|
}
|
||||||
|
|
||||||
|
apiRouter := router.PathPrefix(BaseRoute).Subrouter()
|
||||||
|
apiRouter.HandleFunc("/test", func(http.ResponseWriter, *http.Request) {
|
||||||
|
slog.Info("Test called", slog.String("sample", "value"))
|
||||||
|
|
||||||
|
})
|
||||||
|
// data
|
||||||
|
apiRouter.Use(p.Metrics.GetMiddleware())
|
||||||
|
|
||||||
|
router.Handle(p.AppConfig.Metrics.Endpoint, promhttp.Handler())
|
||||||
|
|
||||||
|
srv := http.Server{
|
||||||
|
Handler: router,
|
||||||
|
|
||||||
|
Addr: p.AppConfig.Server.Port,
|
||||||
|
WriteTimeout: p.AppConfig.Server.WriteTimeout,
|
||||||
|
ReadTimeout: p.AppConfig.Server.ReadTimeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
lc.Append(fx.Hook{
|
||||||
|
OnStart: func(c context.Context) error {
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
slog.Info(fmt.Sprintf("Starting server on port %s", p.AppConfig.Server.Port))
|
||||||
|
if p.AppConfig.Server.Tls.Enabled {
|
||||||
|
err = srv.ListenAndServeTLS(p.AppConfig.Server.Tls.CertFile, p.AppConfig.Server.Tls.KeyFile)
|
||||||
|
} else {
|
||||||
|
err = srv.ListenAndServe()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
OnStop: func(ctx context.Context) error {
|
||||||
|
return srv.Shutdown(ctx)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
45
internal/api/version/handler.go
Normal file
45
internal/api/version/handler.go
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"go.uber.org/fx"
|
||||||
|
|
||||||
|
// import embedded version
|
||||||
|
"payouts/internal/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Route version route
|
||||||
|
const Route = "/version"
|
||||||
|
|
||||||
|
// Module is a fx module
|
||||||
|
var Module = fx.Options(
|
||||||
|
fx.Provide(New),
|
||||||
|
)
|
||||||
|
|
||||||
|
// New constructs a new config Handler.
|
||||||
|
func New() (Handler, error) {
|
||||||
|
|
||||||
|
return &handler{
|
||||||
|
version.Version,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler config handler interface
|
||||||
|
type Handler interface {
|
||||||
|
VersionHandler(http.ResponseWriter, *http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
version string
|
||||||
|
}
|
||||||
|
|
||||||
|
// VersionHandler handles the version requests
|
||||||
|
func (h *handler) VersionHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
io.WriteString(w, h.version)
|
||||||
|
}
|
||||||
12
internal/config/app.go
Normal file
12
internal/config/app.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
logging "payouts/internal/log/config"
|
||||||
|
monitoring "payouts/internal/service/monitoring/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type App struct {
|
||||||
|
Server Server
|
||||||
|
Metrics monitoring.Metrics
|
||||||
|
Log logging.Log
|
||||||
|
}
|
||||||
86
internal/config/module.go
Normal file
86
internal/config/module.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-viper/encoding/javaproperties"
|
||||||
|
"github.com/go-viper/mapstructure/v2"
|
||||||
|
"github.com/ogier/pflag"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
|
||||||
|
monitoring "payouts/internal/service/monitoring/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ConfigPathArg = "config-path"
|
||||||
|
ConfigPathDefault = "./payouts.properties"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Module = fx.Provide(NewAppConfig)
|
||||||
|
|
||||||
|
func getConfigData(filePath string) (string, string, string) {
|
||||||
|
dir, file := filepath.Split(filePath)
|
||||||
|
base := filepath.Base(file)
|
||||||
|
ext := filepath.Ext(base)
|
||||||
|
|
||||||
|
confPath, _ := filepath.Abs(dir)
|
||||||
|
confName := strings.TrimSuffix(base, ext)
|
||||||
|
confType := strings.Trim(ext, ".")
|
||||||
|
|
||||||
|
return confPath, confName, confType
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAppConfig() (*App, error) {
|
||||||
|
mainConfig := &App{}
|
||||||
|
|
||||||
|
configPaths := []string{ConfigPathDefault}
|
||||||
|
|
||||||
|
configPath := pflag.String(ConfigPathArg, "", "")
|
||||||
|
pflag.Parse()
|
||||||
|
|
||||||
|
configPaths = append(configPaths, *configPath)
|
||||||
|
|
||||||
|
codecRegistry := viper.NewCodecRegistry()
|
||||||
|
codec := &javaproperties.Codec{}
|
||||||
|
codecRegistry.RegisterCodec("properties", codec)
|
||||||
|
codecRegistry.RegisterCodec("props", codec)
|
||||||
|
codecRegistry.RegisterCodec("prop", codec)
|
||||||
|
|
||||||
|
conf := viper.New()
|
||||||
|
|
||||||
|
for num, path := range configPaths {
|
||||||
|
if len(path) < 1 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tempConf := viper.NewWithOptions(
|
||||||
|
viper.WithCodecRegistry(codecRegistry),
|
||||||
|
)
|
||||||
|
|
||||||
|
confPath, confName, confType := getConfigData(path)
|
||||||
|
tempConf.AddConfigPath(confPath)
|
||||||
|
tempConf.SetConfigName(confName)
|
||||||
|
tempConf.SetConfigType(confType)
|
||||||
|
|
||||||
|
err := tempConf.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
// complain on missed non-default config
|
||||||
|
if num > 0 {
|
||||||
|
fmt.Printf("Can't read config from %v, Error: %v\n", path, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_ = conf.MergeConfigMap(tempConf.AllSettings())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := conf.Unmarshal(mainConfig, viper.DecodeHook(
|
||||||
|
mapstructure.ComposeDecodeHookFunc(
|
||||||
|
mapstructure.TextUnmarshallerHookFunc(),
|
||||||
|
mapstructure.StringToSliceHookFunc(","),
|
||||||
|
mapstructure.StringToTimeDurationHookFunc(),
|
||||||
|
monitoring.CommaSeparatedFloat64SliceHookFunc(),
|
||||||
|
)))
|
||||||
|
|
||||||
|
return mainConfig, err
|
||||||
|
}
|
||||||
20
internal/config/server.go
Normal file
20
internal/config/server.go
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Tls struct {
|
||||||
|
Enabled bool
|
||||||
|
CertFile string
|
||||||
|
KeyFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server represents the server configiration
|
||||||
|
type Server struct {
|
||||||
|
Tls Tls
|
||||||
|
Port string
|
||||||
|
WriteTimeout time.Duration
|
||||||
|
ReadTimeout time.Duration
|
||||||
|
EnablePProfEndpoints bool
|
||||||
|
}
|
||||||
14
internal/log/config/log.go
Normal file
14
internal/log/config/log.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "log/slog"
|
||||||
|
|
||||||
|
type Log struct {
|
||||||
|
Level slog.Level
|
||||||
|
|
||||||
|
FilePath string
|
||||||
|
|
||||||
|
TextOutput bool
|
||||||
|
StdoutEnabled bool
|
||||||
|
FileEnabled bool
|
||||||
|
FluentEnabled bool
|
||||||
|
}
|
||||||
56
internal/log/module.go
Normal file
56
internal/log/module.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package log
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
slogmulti "github.com/samber/slog-multi"
|
||||||
|
"go.uber.org/fx"
|
||||||
|
|
||||||
|
"payouts/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Module = fx.Options(
|
||||||
|
fx.Provide(NewLogger),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Params represents the module input params
|
||||||
|
type Params struct {
|
||||||
|
fx.In
|
||||||
|
|
||||||
|
AppConfig *config.App
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogger(p Params) (*slog.Logger, error) {
|
||||||
|
logConfig := p.AppConfig.Log
|
||||||
|
opts := &slog.HandlerOptions{
|
||||||
|
Level: logConfig.Level,
|
||||||
|
}
|
||||||
|
|
||||||
|
handlers := []slog.Handler{}
|
||||||
|
if logConfig.StdoutEnabled {
|
||||||
|
if logConfig.TextOutput {
|
||||||
|
handlers = append(handlers, slog.NewTextHandler(os.Stdout, opts))
|
||||||
|
} else {
|
||||||
|
handlers = append(handlers, slog.NewJSONHandler(os.Stdout, opts))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if logConfig.FileEnabled {
|
||||||
|
file, err := os.OpenFile(logConfig.FilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if logConfig.TextOutput {
|
||||||
|
handlers = append(handlers, slog.NewTextHandler(file, opts))
|
||||||
|
} else {
|
||||||
|
handlers = append(handlers, slog.NewJSONHandler(file, opts))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := slog.New(slogmulti.Fanout(handlers...))
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
|
||||||
|
return logger, nil
|
||||||
|
}
|
||||||
62
internal/service/monitoring/config/metrics.go
Normal file
62
internal/service/monitoring/config/metrics.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) New Cloud Technologies, Ltd., 2013-2026
|
||||||
|
*
|
||||||
|
* You can not use the contents of the file in any way without New Cloud Technologies Ltd. written permission.
|
||||||
|
* To obtain such a permit, you should contact New Cloud Technologies, Ltd. at https://myoffice.ru/contacts/
|
||||||
|
*/
|
||||||
|
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-viper/mapstructure/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CommaSeparatedFloat64Slice []float64
|
||||||
|
|
||||||
|
func CommaSeparatedFloat64SliceHookFunc() mapstructure.DecodeHookFuncType {
|
||||||
|
return func(
|
||||||
|
f reflect.Type,
|
||||||
|
t reflect.Type,
|
||||||
|
data interface{},
|
||||||
|
) (interface{}, error) {
|
||||||
|
// Check that the data is string
|
||||||
|
if f.Kind() != reflect.String {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that the target type is our custom type
|
||||||
|
if t != reflect.TypeOf(CommaSeparatedFloat64Slice{}) {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
stringSlice := strings.Split(data.(string), ",")
|
||||||
|
floatSlice := make([]float64, 0)
|
||||||
|
for _, str := range stringSlice {
|
||||||
|
val, err := strconv.ParseFloat(strings.TrimSpace(str), 64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
floatSlice = append(floatSlice, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the parsed value
|
||||||
|
return CommaSeparatedFloat64Slice(floatSlice), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HttpMetrics configuration properties for http monitoring
|
||||||
|
type HttpMetrics struct {
|
||||||
|
HistogramEnabled bool
|
||||||
|
Buckets CommaSeparatedFloat64Slice
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metrics configuration properties for monitoring
|
||||||
|
type Metrics struct {
|
||||||
|
Endpoint string
|
||||||
|
HistogramBuckets CommaSeparatedFloat64Slice
|
||||||
|
Http HttpMetrics
|
||||||
|
}
|
||||||
95
internal/service/monitoring/metrics.go
Normal file
95
internal/service/monitoring/metrics.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package monitoring
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
|
|
||||||
|
"payouts/internal/service/monitoring/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
METRICS_NAMESPACE = "payouts"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Metrics represents the metrics service
|
||||||
|
type Metrics interface {
|
||||||
|
GetMiddleware() func(next http.Handler) http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
// metrics represents the metrics service implementation
|
||||||
|
type metrics struct {
|
||||||
|
httpDuration prometheus.ObserverVec
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMetrics instantiates metrics service
|
||||||
|
func NewMetrics(config config.Metrics) (Metrics, error) {
|
||||||
|
var httpDuration prometheus.ObserverVec
|
||||||
|
if config.Http.HistogramEnabled {
|
||||||
|
httpDuration = CreateHttpHistogram(config.Http.Buckets)
|
||||||
|
} else {
|
||||||
|
httpDuration = CreateHttpSummary(config.Http.Buckets)
|
||||||
|
|
||||||
|
}
|
||||||
|
return &metrics{
|
||||||
|
httpDuration: httpDuration,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMiddleware returns the middleware to be used in mux router
|
||||||
|
func (m *metrics) GetMiddleware() func(next http.Handler) http.Handler {
|
||||||
|
return GetMiddleware(m.httpDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateHttpSummary(buckets []float64) *prometheus.SummaryVec {
|
||||||
|
return promauto.NewSummaryVec(prometheus.SummaryOpts{
|
||||||
|
Name: "http_server_requests_seconds",
|
||||||
|
Help: "Duration of HTTP requests.",
|
||||||
|
}, []string{"uri", "method", "code"})
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateHttpHistogram(buckets []float64) *prometheus.HistogramVec {
|
||||||
|
return promauto.NewHistogramVec(prometheus.HistogramOpts{
|
||||||
|
Name: "http_server_requests_seconds",
|
||||||
|
Help: "Duration of HTTP requests.",
|
||||||
|
Buckets: buckets,
|
||||||
|
}, []string{"uri", "method", "code"})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMiddleware(histogramVec prometheus.ObserverVec) func(next http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
route := mux.CurrentRoute(r)
|
||||||
|
path, _ := route.GetPathTemplate()
|
||||||
|
wd := &writerDelegate{
|
||||||
|
ResponseWriter: w,
|
||||||
|
statusCode: http.StatusOK,
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
defer func() {
|
||||||
|
duration := time.Since(start).Seconds()
|
||||||
|
code := strconv.Itoa(wd.statusCode)
|
||||||
|
histogramVec.WithLabelValues(path, r.Method, code).Observe(duration)
|
||||||
|
}()
|
||||||
|
|
||||||
|
next.ServeHTTP(wd, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type writerDelegate struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
statusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
// override the WriteHeader method
|
||||||
|
func (w *writerDelegate) WriteHeader(statusCode int) {
|
||||||
|
w.statusCode = statusCode
|
||||||
|
w.ResponseWriter.WriteHeader(statusCode)
|
||||||
|
}
|
||||||
24
internal/service/monitoring/module.go
Normal file
24
internal/service/monitoring/module.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package monitoring
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.uber.org/fx"
|
||||||
|
|
||||||
|
"payouts/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Module is a fx module
|
||||||
|
var Module = fx.Options(
|
||||||
|
fx.Provide(New),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Params represents the module input params
|
||||||
|
type Params struct {
|
||||||
|
fx.In
|
||||||
|
|
||||||
|
AppConfig *config.App
|
||||||
|
}
|
||||||
|
|
||||||
|
// New instantiates the metrics service
|
||||||
|
func New(p Params) (Metrics, error) {
|
||||||
|
return NewMetrics(p.AppConfig.Metrics)
|
||||||
|
}
|
||||||
6
internal/version/package.go
Normal file
6
internal/version/package.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import _ "embed"
|
||||||
|
|
||||||
|
//go:embed version.txt
|
||||||
|
var Version string
|
||||||
1
internal/version/version.txt
Normal file
1
internal/version/version.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
unknown
|
||||||
Reference in New Issue
Block a user