tmp
This commit is contained in:
parent
e35a9daaf7
commit
cfb4940fe8
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
*.sqlite
|
||||
*.db
|
||||
assets/*
|
3
README.md
Normal file
3
README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# DrainCloud Light
|
||||
DrainCloud Light is an all-in-one lightweight DrainCloud distribution designed to work in resource-constrained environments.
|
||||
It requires **# TODO put requirements here **
|
5
cmd/main.go
Normal file
5
cmd/main.go
Normal file
@ -0,0 +1,5 @@
|
||||
package main
|
||||
|
||||
func main() {
|
||||
|
||||
}
|
10
docker-compose.yaml
Normal file
10
docker-compose.yaml
Normal file
@ -0,0 +1,10 @@
|
||||
services:
|
||||
database:
|
||||
image: postgres:16
|
||||
container_name: draincloud-db
|
||||
ports:
|
||||
- 5432:5432
|
||||
environment:
|
||||
POSTGRES_USERNAME: draincloud
|
||||
POSTGRES_DB: draincloud
|
||||
POSTGRES_PASSWORD: draincloud.dev.secret
|
47
go.mod
47
go.mod
@ -1,3 +1,50 @@
|
||||
module git.optclblast.xyz/draincloud/draincloud-light
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
github.com/fatih/color v1.17.0
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/jackc/pgx/v5 v5.7.1
|
||||
github.com/jmoiron/sqlx v1.4.0
|
||||
github.com/mattn/go-sqlite3 v1.14.23
|
||||
github.com/pressly/goose/v3 v3.22.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/rogpeppe/go-internal v1.13.1 // indirect
|
||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/net v0.28.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
google.golang.org/protobuf v1.34.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
154
go.sum
Normal file
154
go.sum
Normal file
@ -0,0 +1,154 @@
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
||||
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
||||
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/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
|
||||
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
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/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0=
|
||||
github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
||||
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
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/pressly/goose/v3 v3.22.1 h1:2zICEfr1O3yTP9BRZMGPj7qFxQ+ik6yeo+z1LMuioLc=
|
||||
github.com/pressly/goose/v3 v3.22.1/go.mod h1:xtMpbstWyCpyH+0cxLTMCENWBG+0CSxvTsXhW95d5eo=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE=
|
||||
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
|
||||
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
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.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6 h1:5D53IMaUuA5InSeMu9eJtlQXS2NxAhyWQvkKEgXZhHI=
|
||||
modernc.org/gc/v3 v3.0.0-20240107210532-573471604cb6/go.mod h1:Qz0X07sNOR1jWYCrJMEnbW/X55x206Q7Vt4mz6/wHp4=
|
||||
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
||||
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
||||
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
||||
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
||||
modernc.org/sqlite v1.33.0 h1:WWkA/T2G17okiLGgKAj4/RMIvgyMT19yQ038160IeYk=
|
||||
modernc.org/sqlite v1.33.0/go.mod h1:9uQ9hF/pCZoYZK73D/ud5Z7cIRIILSZI8NdIemVMTX8=
|
||||
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
28
internal/app/app.go
Normal file
28
internal/app/app.go
Normal file
@ -0,0 +1,28 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type DrainCloud struct {
|
||||
mux *gin.Engine
|
||||
}
|
||||
|
||||
func New() *DrainCloud {
|
||||
mux := gin.Default()
|
||||
|
||||
d := new(DrainCloud)
|
||||
|
||||
authGroup := mux.Group("/auth")
|
||||
{
|
||||
authGroup.POST("/register", d.Register)
|
||||
}
|
||||
|
||||
d.mux = mux
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *DrainCloud) Register(ctx *gin.Context) {
|
||||
|
||||
}
|
40
internal/closer/closer.go
Normal file
40
internal/closer/closer.go
Normal file
@ -0,0 +1,40 @@
|
||||
package closer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"git.optclblast.xyz/draincloud/draincloud-light/internal/logger"
|
||||
)
|
||||
|
||||
var globalCloser *Closer = &Closer{
|
||||
closeFns: make([]func() error, 0),
|
||||
}
|
||||
|
||||
type Closer struct {
|
||||
closeFns []func() error
|
||||
}
|
||||
|
||||
func (c *Closer) Add(fn func() error) {
|
||||
c.closeFns = append(c.closeFns, fn)
|
||||
}
|
||||
|
||||
func (c *Closer) Close() error {
|
||||
var commonErr error
|
||||
for _, fn := range c.closeFns {
|
||||
if err := fn(); err != nil {
|
||||
logger.Error(context.Background(), "[closer][Close] error at close func call", logger.Err(err))
|
||||
commonErr = errors.Join(commonErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
return commonErr
|
||||
}
|
||||
|
||||
func Add(fn func() error) {
|
||||
globalCloser.Add(fn)
|
||||
}
|
||||
|
||||
func Close() error {
|
||||
return globalCloser.Close()
|
||||
}
|
6
internal/domain/requests.go
Normal file
6
internal/domain/requests.go
Normal file
@ -0,0 +1,6 @@
|
||||
package domain
|
||||
|
||||
type RegisterRequest struct {
|
||||
Login string `json:"login"`
|
||||
Password string `json:"password"`
|
||||
}
|
154
internal/logger/builder.go
Normal file
154
internal/logger/builder.go
Normal file
@ -0,0 +1,154 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type _key string
|
||||
|
||||
//nolint:gochecknoglobals // ...
|
||||
var loggerKey _key = "_core_logger"
|
||||
|
||||
type LoggerOpt func(p *loggerParams)
|
||||
|
||||
func NewLoggerContext(ctx context.Context, opts ...LoggerOpt) context.Context {
|
||||
p := new(loggerParams)
|
||||
|
||||
for _, o := range opts {
|
||||
o(p)
|
||||
}
|
||||
|
||||
log := p.build()
|
||||
|
||||
return context.WithValue(ctx, loggerKey, log)
|
||||
}
|
||||
|
||||
type loggerParams struct {
|
||||
local bool
|
||||
addSource bool
|
||||
lvl slog.Level
|
||||
writers []io.Writer
|
||||
}
|
||||
|
||||
func WithWriter(w io.Writer) LoggerOpt {
|
||||
return func(p *loggerParams) {
|
||||
p.writers = append(p.writers, w)
|
||||
}
|
||||
}
|
||||
|
||||
func WithLevel(l slog.Level) LoggerOpt {
|
||||
return func(p *loggerParams) {
|
||||
p.lvl = l
|
||||
}
|
||||
}
|
||||
|
||||
func Local() LoggerOpt {
|
||||
return func(p *loggerParams) {
|
||||
p.local = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithSource() LoggerOpt {
|
||||
return func(p *loggerParams) {
|
||||
p.addSource = true
|
||||
}
|
||||
}
|
||||
|
||||
func Err(err error) slog.Attr {
|
||||
return slog.Attr{
|
||||
Key: "error",
|
||||
Value: slog.StringValue(err.Error()),
|
||||
}
|
||||
}
|
||||
|
||||
func MapLevel(lvl string) slog.Level {
|
||||
switch strings.ToLower(lvl) {
|
||||
case "debug":
|
||||
return LevelDebug
|
||||
case "info":
|
||||
return LevelInfo
|
||||
case "notice":
|
||||
return LevelNotice
|
||||
case "warn":
|
||||
return LevelWarn
|
||||
case "error":
|
||||
return LevelError
|
||||
case "critical":
|
||||
return LevelCritial
|
||||
case "alert":
|
||||
return LevelAlert
|
||||
case "emergency":
|
||||
return LevelEmergency
|
||||
default:
|
||||
return LevelInfo
|
||||
}
|
||||
}
|
||||
|
||||
func (b *loggerParams) build() *slog.Logger {
|
||||
if len(b.writers) == 0 {
|
||||
b.writers = append(b.writers, os.Stdout)
|
||||
}
|
||||
|
||||
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 newLogger(b.lvl, w)
|
||||
}
|
||||
|
||||
func newLogger(lvl slog.Level, w io.Writer) *slog.Logger {
|
||||
return slog.New(
|
||||
slog.NewJSONHandler(w, &slog.HandlerOptions{
|
||||
Level: lvl,
|
||||
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
||||
if a.Key == slog.LevelKey {
|
||||
level := a.Value.Any().(slog.Level)
|
||||
|
||||
switch {
|
||||
case level < LevelInfo:
|
||||
a.Value = slog.StringValue("DEBUG")
|
||||
case level < LevelNotice:
|
||||
a.Value = slog.StringValue("INFO")
|
||||
case level < LevelWarn:
|
||||
a.Value = slog.StringValue("NOTICE")
|
||||
case level < LevelError:
|
||||
a.Value = slog.StringValue("WARNING")
|
||||
case level < LevelCritial:
|
||||
a.Value = slog.StringValue("ERROR")
|
||||
case level < LevelAlert:
|
||||
a.Value = slog.StringValue("CRITICAL")
|
||||
case level < LevelEmergency:
|
||||
a.Value = slog.StringValue("ALERT")
|
||||
default:
|
||||
a.Value = slog.StringValue("EMERGENCY")
|
||||
}
|
||||
}
|
||||
|
||||
return a
|
||||
},
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
func loggerFromCtx(ctx context.Context) *slog.Logger {
|
||||
if l, ok := ctx.Value(loggerKey).(*slog.Logger); ok {
|
||||
return l
|
||||
}
|
||||
|
||||
return globalLogger
|
||||
}
|
35
internal/logger/discard.go
Normal file
35
internal/logger/discard.go
Normal file
@ -0,0 +1,35 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
)
|
||||
|
||||
//nolint:unused //...
|
||||
func newDiscardLogger() *slog.Logger {
|
||||
return slog.New(newDiscardHandler())
|
||||
}
|
||||
|
||||
//nolint:unused //...
|
||||
type DiscardHandler struct{}
|
||||
|
||||
//nolint:unused //...
|
||||
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
|
||||
}
|
77
internal/logger/logger.go
Normal file
77
internal/logger/logger.go
Normal file
@ -0,0 +1,77 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
//nolint:gochecknoglobals // ...
|
||||
var globalLogger *slog.Logger = newLogger(LevelInfo, os.Stdout)
|
||||
|
||||
const (
|
||||
LevelEmergency = slog.Level(10000)
|
||||
LevelAlert = slog.Level(1000)
|
||||
LevelCritial = slog.Level(100)
|
||||
LevelError = slog.LevelError
|
||||
LevelWarn = slog.LevelWarn
|
||||
LevelNotice = slog.Level(2)
|
||||
LevelInfo = slog.LevelInfo
|
||||
LevelDebug = slog.LevelDebug
|
||||
)
|
||||
|
||||
func Fatal(ctx context.Context, message string, attrs ...any) {
|
||||
l := loggerFromCtx(ctx)
|
||||
|
||||
l.Log(ctx, LevelEmergency, message, attrs...)
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func Emergency(ctx context.Context, message string, attrs ...any) {
|
||||
l := loggerFromCtx(ctx)
|
||||
|
||||
l.Log(ctx, LevelEmergency, message, attrs...)
|
||||
}
|
||||
|
||||
func Alert(ctx context.Context, message string, attrs ...any) {
|
||||
l := loggerFromCtx(ctx)
|
||||
|
||||
l.Log(ctx, LevelAlert, message, attrs...)
|
||||
}
|
||||
|
||||
func Critial(ctx context.Context, message string, attrs ...any) {
|
||||
l := loggerFromCtx(ctx)
|
||||
|
||||
l.Log(ctx, LevelCritial, message, attrs...)
|
||||
}
|
||||
|
||||
func Error(ctx context.Context, message string, attrs ...any) {
|
||||
l := loggerFromCtx(ctx)
|
||||
|
||||
l.ErrorContext(ctx, message, attrs...)
|
||||
}
|
||||
|
||||
func Warn(ctx context.Context, message string, attrs ...any) {
|
||||
l := loggerFromCtx(ctx)
|
||||
|
||||
l.WarnContext(ctx, message, attrs...)
|
||||
}
|
||||
|
||||
func Notice(ctx context.Context, message string, attrs ...any) {
|
||||
l := loggerFromCtx(ctx)
|
||||
|
||||
l.Log(ctx, LevelNotice, message, attrs...)
|
||||
}
|
||||
|
||||
func Info(ctx context.Context, message string, attrs ...any) {
|
||||
l := loggerFromCtx(ctx)
|
||||
|
||||
l.InfoContext(ctx, message, attrs...)
|
||||
}
|
||||
|
||||
func Debug(ctx context.Context, message string, attrs ...any) {
|
||||
l := loggerFromCtx(ctx)
|
||||
|
||||
l.Log(ctx, LevelCritial, message, attrs...)
|
||||
}
|
97
internal/logger/slogpretty.go
Normal file
97
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,
|
||||
}
|
||||
}
|
15
internal/server/server.go
Normal file
15
internal/server/server.go
Normal file
@ -0,0 +1,15 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
mux *http.ServeMux // Mux
|
||||
ctx context.Context // Global application context
|
||||
}
|
||||
|
||||
func RegisterHandler(ctx context.Context, h http.HandlerFunc) {
|
||||
|
||||
}
|
17
internal/storage/interface.go
Normal file
17
internal/storage/interface.go
Normal file
@ -0,0 +1,17 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.optclblast.xyz/draincloud/draincloud-light/internal/storage/models"
|
||||
)
|
||||
|
||||
type Database interface {
|
||||
AuthStorage
|
||||
}
|
||||
|
||||
type AuthStorage interface {
|
||||
AddUser(ctx context.Context, user *models.User) error
|
||||
GetUserByLogin(ctx context.Context, login string) (*models.User, error)
|
||||
GetUserByID(ctx context.Context, id uint64) (*models.User, error)
|
||||
}
|
12
internal/storage/models/auth.go
Normal file
12
internal/storage/models/auth.go
Normal file
@ -0,0 +1,12 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
type User struct {
|
||||
ID uint64
|
||||
Username string
|
||||
Login string
|
||||
PasswordHash []byte
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
89
internal/storage/postgres/database.go
Normal file
89
internal/storage/postgres/database.go
Normal file
@ -0,0 +1,89 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.optclblast.xyz/draincloud/draincloud-light/internal/closer"
|
||||
"git.optclblast.xyz/draincloud/draincloud-light/internal/logger"
|
||||
"git.optclblast.xyz/draincloud/draincloud-light/internal/storage/models"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
db *pgx.Conn
|
||||
}
|
||||
|
||||
func New(ctx context.Context, dsn string) *Database {
|
||||
db, err := pgx.Connect(ctx, dsn)
|
||||
if err != nil {
|
||||
logger.Fatal(ctx, "failed to connect to postgres", logger.Err(err))
|
||||
}
|
||||
|
||||
closer.Add(func() error {
|
||||
ctx, cancel := context.WithTimeout(ctx, 2*time.Second)
|
||||
defer cancel()
|
||||
return db.Close(ctx)
|
||||
})
|
||||
|
||||
return &Database{db: db}
|
||||
}
|
||||
|
||||
type dbtx interface {
|
||||
Exec(ctx context.Context, stmt string, args ...any) (pgconn.CommandTag, error)
|
||||
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
|
||||
Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
|
||||
}
|
||||
|
||||
func (d *Database) AddUser(ctx context.Context, login string, username string, passwordHash []byte) (uint64, error) {
|
||||
return addUser(ctx, d.db, login, username, passwordHash)
|
||||
}
|
||||
|
||||
func (d *Database) GetUserByID(ctx context.Context, id uint64) (*models.User, error) {
|
||||
return getUserByID(ctx, d.db, id)
|
||||
}
|
||||
|
||||
func (d *Database) GetUserByLogin(ctx context.Context, login string) (*models.User, error) {
|
||||
return getUserByLogin(ctx, d.db, login)
|
||||
}
|
||||
|
||||
func addUser(ctx context.Context, conn dbtx, login string, username string, passwordHash []byte) (uint64, error) {
|
||||
const stmt = `INSERT INTO users (login,username,password)
|
||||
VALUES ($1,$2,$3,$4) RETURNING id`
|
||||
|
||||
row := conn.QueryRow(ctx, stmt, login, username, passwordHash)
|
||||
|
||||
var id uint64
|
||||
|
||||
if err := row.Scan(&id); err != nil {
|
||||
return 0, fmt.Errorf("failed to insert user data into users table: %w", err)
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func getUserByID(ctx context.Context, conn dbtx, id uint64) (*models.User, error) {
|
||||
const stmt = `SELECT * FROM users WHERE id = $1 LIMIT 1`
|
||||
u := new(models.User)
|
||||
|
||||
row := conn.QueryRow(ctx, stmt, id)
|
||||
if err := row.Scan(&u.ID, &u.Login, &u.Username, &u.PasswordHash, &u.CreatedAt, &u.UpdatedAt); err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch user by id: %w", err)
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func getUserByLogin(ctx context.Context, conn dbtx, login string) (*models.User, error) {
|
||||
const stmt = `SELECT * FROM users WHERE login = $1 LIMIT 1`
|
||||
u := new(models.User)
|
||||
|
||||
row := conn.QueryRow(ctx, stmt, login)
|
||||
if err := row.Scan(&u.ID, &u.Login, &u.Username, &u.PasswordHash, &u.CreatedAt, &u.UpdatedAt); err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch user by login: %w", err)
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
66
internal/storage/transaction.go
Normal file
66
internal/storage/transaction.go
Normal file
@ -0,0 +1,66 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
type txKey struct{}
|
||||
|
||||
var ctxKey txKey = txKey{}
|
||||
|
||||
type DBTX interface {
|
||||
sqlx.Ext
|
||||
sqlx.ExtContext
|
||||
}
|
||||
|
||||
func Transaction(ctx context.Context, db *sqlx.DB, fn func(context.Context) error) (err error) {
|
||||
tx := txFromContext(ctx)
|
||||
if tx == nil {
|
||||
tx, err = db.BeginTxx(ctx, &sql.TxOptions{
|
||||
Isolation: sql.LevelRepeatableRead,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to begin tx: %w", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err == nil {
|
||||
err = tx.Commit()
|
||||
}
|
||||
if err != nil {
|
||||
if rbErr := tx.Rollback(); rbErr != nil {
|
||||
err = errors.Join(err, rbErr)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
ctx = txContext(ctx, tx)
|
||||
}
|
||||
|
||||
return fn(ctx)
|
||||
}
|
||||
|
||||
func Conn(ctx context.Context, db DBTX) DBTX {
|
||||
if tx := txFromContext(ctx); tx != nil {
|
||||
return tx
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func txFromContext(ctx context.Context) *sqlx.Tx {
|
||||
if tx, ok := ctx.Value(ctxKey).(*sqlx.Tx); ok {
|
||||
return tx
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func txContext(parent context.Context, tx *sqlx.Tx) context.Context {
|
||||
return context.WithValue(parent, tx, ctxKey)
|
||||
}
|
53
migrations/20240927205337_add_users.go
Normal file
53
migrations/20240927205337_add_users.go
Normal file
@ -0,0 +1,53 @@
|
||||
package migrations
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
|
||||
"git.optclblast.xyz/draincloud/draincloud-light/internal/logger"
|
||||
"github.com/pressly/goose/v3"
|
||||
)
|
||||
|
||||
func init() {
|
||||
goose.AddMigrationContext(upAddUsers, downAddUsers)
|
||||
}
|
||||
|
||||
func upAddUsers(ctx context.Context, tx *sql.Tx) error {
|
||||
const stmt = `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id bigserial PRIMARY KEY
|
||||
, username TEXT DEFAULT NULL
|
||||
, login TEXT NOT NULL UNIQUE
|
||||
, PASSWORD bytea NOT NULL
|
||||
, created_at timestamptz DEFAULT current_timestamp
|
||||
, updated_at timestamptz DEFAULT current_timestamp
|
||||
);
|
||||
|
||||
ALTER TABLE users ADD CONSTRAINT users_username_len CHECK(length(username) > 250) NOT VALID;
|
||||
ALTER TABLE users ADD CONSTRAINT users_login_len CHECK(length(username) > 250) NOT VALID;
|
||||
|
||||
CREATE INDEX CONCURRENTLY idx_users_login ON
|
||||
users (login);
|
||||
CREATE INDEX CONCURRENTLY idx_users_username ON
|
||||
users (username);`
|
||||
|
||||
if _, err := tx.ExecContext(ctx, stmt); err != nil {
|
||||
logger.Error(ctx, "[migration] error", logger.Err(err))
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func downAddUsers(ctx context.Context, tx *sql.Tx) error {
|
||||
const stmt = `
|
||||
DROP INDEX CONCURRENTLY IF EXISTS idx_users_login;
|
||||
DROP INDEX CONCURRENTLY IF EXISTS idx_users_username;
|
||||
DROP TABLE IF EXISTS users;`
|
||||
|
||||
if _, err := tx.ExecContext(ctx, stmt); err != nil {
|
||||
logger.Error(ctx, "[migration] error", logger.Err(err))
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user