more updates
This commit is contained in:
parent
856c846dbd
commit
b23ac7617e
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -23,6 +23,3 @@ go.work
|
||||||
|
|
||||||
.direnv
|
.direnv
|
||||||
bin/
|
bin/
|
||||||
gen/
|
|
||||||
|
|
||||||
*.db
|
|
||||||
|
|
13
Makefile
13
Makefile
|
@ -1,17 +1,18 @@
|
||||||
.ONESHELL:
|
.ONESHELL:
|
||||||
|
|
||||||
export PATH := $(shell pwd)/bin:$(shell pwd)/web/node_modules/.bin:$(PATH)
|
export PATH := $(shell pwd)/bin:$(shell pwd)/web/node_modules/.bin:$(PATH)
|
||||||
export GOOSE_DRIVER=sqlite3
|
export GOOSE_DRIVER ?= sqlite3
|
||||||
export GOOSE_DBSTRING=./data.db
|
export GOOSE_DBSTRING ?= ./go/data.db
|
||||||
export GOOSE_MIGRATION_DIR=schemas/migrations
|
export GOOSE_MIGRATION_DIR ?= schemas/migrations
|
||||||
|
|
||||||
build: generate-go generate-web
|
build: generate-go generate-web
|
||||||
go build -o bin/bluemage ./go/cmd/bluemage/main.go
|
go build -o bin/bluemage ./go/cmd/bluemage/main.go
|
||||||
|
|
||||||
generate-go:
|
generate-go: migrate
|
||||||
rm -rf go/gen
|
rm -rf go/gen
|
||||||
cd ./schemas/proto
|
(cd ./schemas/proto && buf generate --template buf.gen.go.yaml .)
|
||||||
buf generate --template buf.gen.go.yaml .
|
(cd go/gen && bobgen-sqlite --config ../bobgen.yaml)
|
||||||
|
(cd go && goverter gen -g 'output:file ../gen/converter/converter.go' -g 'output:package github.com/tigorlazuardi/bluemage/go/gen/converter' ./converter)
|
||||||
|
|
||||||
generate-web:
|
generate-web:
|
||||||
rm -rf web/gen
|
rm -rf web/gen
|
||||||
|
|
25
flake.nix
25
flake.nix
|
@ -30,6 +30,29 @@
|
||||||
'';
|
'';
|
||||||
vendorHash = "sha256-uQ1qKZLRwsgXKqSAERSqf+1cYKp6MTeVbfGs+qcdakE=";
|
vendorHash = "sha256-uQ1qKZLRwsgXKqSAERSqf+1cYKp6MTeVbfGs+qcdakE=";
|
||||||
};
|
};
|
||||||
|
bobgen-sqlite = pkgs.buildGoModule rec {
|
||||||
|
name = "bobgen-sqlite";
|
||||||
|
version = "0.28.1";
|
||||||
|
src = pkgs.fetchFromGitHub {
|
||||||
|
owner = "stephenafamo";
|
||||||
|
repo = "bob";
|
||||||
|
rev = "v${version}";
|
||||||
|
sha256 = "sha256-iLcSY5BBmjuICsIC9u6wyrp7elDt4yY8Ji7UWmsJ688=";
|
||||||
|
};
|
||||||
|
nativeBuildInputs = [ pkgs.go ];
|
||||||
|
buildPhase = ''
|
||||||
|
runHook preBuild
|
||||||
|
go build -o bobgen-sqlite ./gen/bobgen-sqlite
|
||||||
|
runHook postBuild
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
runHook preInstall
|
||||||
|
mkdir -p $out/bin
|
||||||
|
cp bobgen-sqlite $out/bin
|
||||||
|
runHook postInstall
|
||||||
|
'';
|
||||||
|
vendorHash = "sha256-9vLcyCAhW/Dq0zV8FhM0T3Ofn/8XsUhV5KlphNidGDw=";
|
||||||
|
};
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
devShell.${system} = pkgs.mkShell {
|
devShell.${system} = pkgs.mkShell {
|
||||||
|
@ -45,7 +68,9 @@
|
||||||
protoc-gen-go
|
protoc-gen-go
|
||||||
protoc-gen-connect-go
|
protoc-gen-connect-go
|
||||||
protoc-gen-validate
|
protoc-gen-validate
|
||||||
|
impl
|
||||||
goverter
|
goverter
|
||||||
|
bobgen-sqlite
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
24
go.mod
24
go.mod
|
@ -1,3 +1,27 @@
|
||||||
module github.com/tigorlazuardi/bluemage
|
module github.com/tigorlazuardi/bluemage
|
||||||
|
|
||||||
go 1.22.5
|
go 1.22.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2
|
||||||
|
connectrpc.com/connect v1.16.2
|
||||||
|
github.com/aarondl/opt v0.0.0-20240623220848-083f18ab9536
|
||||||
|
github.com/bufbuild/protovalidate-go v0.6.3
|
||||||
|
github.com/jaswdr/faker/v2 v2.3.0
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16
|
||||||
|
github.com/stephenafamo/bob v0.28.1
|
||||||
|
google.golang.org/protobuf v1.34.2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/aarondl/json v0.0.0-20221020222930-8b0db17ef1bf // indirect
|
||||||
|
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||||
|
github.com/google/cel-go v0.20.1 // indirect
|
||||||
|
github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 // indirect
|
||||||
|
github.com/stephenafamo/scan v0.4.2 // indirect
|
||||||
|
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
|
||||||
|
)
|
||||||
|
|
119
go.sum
Normal file
119
go.sum
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2 h1:SZRVx928rbYZ6hEKUIN+vtGDkl7uotABRWGY4OAg5gM=
|
||||||
|
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2/go.mod h1:ylS4c28ACSI59oJrOdW4pHS4n0Hw4TgSPHn8rpHl4Yw=
|
||||||
|
connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE=
|
||||||
|
connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc=
|
||||||
|
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||||
|
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||||
|
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||||
|
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
|
||||||
|
github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
|
||||||
|
github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
|
||||||
|
github.com/aarondl/json v0.0.0-20221020222930-8b0db17ef1bf h1:+edM69bH/X6JpYPmJYBRLanAMe1V5yRXYU3hHUovGcE=
|
||||||
|
github.com/aarondl/json v0.0.0-20221020222930-8b0db17ef1bf/go.mod h1:FZqLhJSj2tg0ZN48GB1zvj00+ZYcHPqgsC7yzcgCq6k=
|
||||||
|
github.com/aarondl/opt v0.0.0-20240623220848-083f18ab9536 h1:vhpjulzH5Tr4S3uJ3Y/9pNL481kPq5ERj13ceAW0/uE=
|
||||||
|
github.com/aarondl/opt v0.0.0-20240623220848-083f18ab9536/go.mod h1:l4/5NZtYd/SIohsFhaJQQe+sPOTG22furpZ5FvcYOzk=
|
||||||
|
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230321174746-8dcc6526cfb1 h1:X8MJ0fnN5FPdcGF5Ij2/OW+HgiJrRg3AfHAx1PJtIzM=
|
||||||
|
github.com/antlr/antlr4/runtime/Go/antlr/v4 v4.0.0-20230321174746-8dcc6526cfb1/go.mod h1:pSwJ0fSY5KhvocuWSx4fz3BA8OrA1bQn+K1Eli3BRwM=
|
||||||
|
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
|
||||||
|
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||||
|
github.com/bufbuild/protovalidate-go v0.6.3 h1:wxQyzW035zM16Binbaz/nWAzS12dRIXhZdSUWRY7Fv0=
|
||||||
|
github.com/bufbuild/protovalidate-go v0.6.3/go.mod h1:J4PtwP9Z2YAGgB0+o+tTWEDtLtXvz/gfhFZD8pbzM/U=
|
||||||
|
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/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
|
||||||
|
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1 h1:TQcrn6Wq+sKGkpyPvppOz99zsMBaUOKXq6HSv655U1c=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.0.0-alpha.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
|
||||||
|
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
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/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs=
|
||||||
|
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
|
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||||
|
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||||
|
github.com/jaswdr/faker/v2 v2.3.0 h1:jgQ9UmU2Eb5tSQ8JkUS4tPoyTM2OtThQpOpwk7Fa9RY=
|
||||||
|
github.com/jaswdr/faker/v2 v2.3.0/go.mod h1:ROK8xwQV0hYOLDUtxCQgHGcl10jbVzIvqHxcIDdwY2Q=
|
||||||
|
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
|
||||||
|
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
|
||||||
|
github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
|
||||||
|
github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
|
||||||
|
github.com/knadh/koanf/providers/confmap v0.1.0 h1:gOkxhHkemwG4LezxxN8DMOFopOPghxRVp7JbIvdvqzU=
|
||||||
|
github.com/knadh/koanf/providers/confmap v0.1.0/go.mod h1:2uLhxQzJnyHKfxG927awZC7+fyHFdQkd697K4MdLnIU=
|
||||||
|
github.com/knadh/koanf/providers/env v0.1.0 h1:LqKteXqfOWyx5Ab9VfGHmjY9BvRXi+clwyZozgVRiKg=
|
||||||
|
github.com/knadh/koanf/providers/env v0.1.0/go.mod h1:RE8K9GbACJkeEnkl8L/Qcj8p4ZyPXZIQ191HJi44ZaQ=
|
||||||
|
github.com/knadh/koanf/providers/file v0.1.0 h1:fs6U7nrV58d3CFAFh8VTde8TM262ObYf3ODrc//Lp+c=
|
||||||
|
github.com/knadh/koanf/providers/file v0.1.0/go.mod h1:rjJ/nHQl64iYCtAW2QQnF0eSmDEX/YZ/eNFj5yR6BvA=
|
||||||
|
github.com/knadh/koanf/v2 v2.1.0 h1:eh4QmHHBuU8BybfIJ8mB8K8gsGCD/AUQTdwGq/GzId8=
|
||||||
|
github.com/knadh/koanf/v2 v2.1.0/go.mod h1:4mnTRbZCK+ALuBXHZMjDfG9y714L7TykVnZkXbMU3Es=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||||
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||||
|
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||||
|
github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249 h1:NHrXEjTNQY7P0Zfx1aMrNhpgxHmow66XQtm0aQLY0AE=
|
||||||
|
github.com/nsf/jsondiff v0.0.0-20210926074059-1e845ec5d249/go.mod h1:mpRZBD8SJ55OIICQ3iWH0Yz3cjzA61JdqMLoWXeB2+8=
|
||||||
|
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/qdm12/reprint v0.0.0-20200326205758-722754a53494 h1:wSmWgpuccqS2IOfmYrbRiUgv+g37W5suLLLxwwniTSc=
|
||||||
|
github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494/go.mod h1:yipyliwI08eQ6XwDm1fEwKPdF/xdbkiHtrU+1Hg+vc4=
|
||||||
|
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||||
|
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||||
|
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||||
|
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||||
|
github.com/stephenafamo/bob v0.28.1 h1:yQaHuhP9HoCldoRIrhB7SwcHKMCCSw5499h7ETFcBLs=
|
||||||
|
github.com/stephenafamo/bob v0.28.1/go.mod h1:S/D3dAbBZjBOcts9iv4ywsJDApFK86s3bUBulCoT6kE=
|
||||||
|
github.com/stephenafamo/fakedb v0.0.0-20221230081958-0b86f816ed97 h1:XItoZNmhOih06TC02jK7l3wlpZ0XT/sPQYutDcGOQjg=
|
||||||
|
github.com/stephenafamo/fakedb v0.0.0-20221230081958-0b86f816ed97/go.mod h1:bM3Vmw1IakoaXocHmMIGgJFYob0vuK+CFWiJHQvz0jQ=
|
||||||
|
github.com/stephenafamo/scan v0.4.2 h1:mPmOjV84VCQdlYPPX1jZZHt5LYRHAPdEe/yuSaMi92Q=
|
||||||
|
github.com/stephenafamo/scan v0.4.2/go.mod h1:FhIUJ8pLNyex36xGFiazDJJ5Xry0UkAi+RkWRrEcRMg=
|
||||||
|
github.com/stephenafamo/sqlparser v0.0.0-20230326220333-c2adaf4c30e8 h1:HR6pFkWHsRRVm+VUSjltpaI56XFl2hdwxdGw7hHIPDk=
|
||||||
|
github.com/stephenafamo/sqlparser v0.0.0-20230326220333-c2adaf4c30e8/go.mod h1:hOw+0TLAWETjE8UJG+q5bdj7EArbIombNBnEUoF7XwM=
|
||||||
|
github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs=
|
||||||
|
github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo=
|
||||||
|
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/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/volatiletech/inflect v0.0.1 h1:2a6FcMQyhmPZcLa+uet3VJ8gLn/9svWhJxJYwvE8KsU=
|
||||||
|
github.com/volatiletech/inflect v0.0.1/go.mod h1:IBti31tG6phkHitLlr5j7shC5SOo//x0AjDzaJU1PLA=
|
||||||
|
github.com/volatiletech/strmangle v0.0.6 h1:AdOYE3B2ygRDq4rXDij/MMwq6KVK/pWAYxpC7CLrkKQ=
|
||||||
|
github.com/volatiletech/strmangle v0.0.6/go.mod h1:ycDvbDkjDvhC0NUU8w3fWwl5JEMTV56vTKXzR3GeR+0=
|
||||||
|
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||||
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
|
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
|
||||||
|
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
|
||||||
|
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||||
|
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
|
||||||
|
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
|
||||||
|
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
|
||||||
|
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||||
|
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda h1:b6F6WIV4xHHD0FA4oIyzU6mHWg2WI2X1RBehwa5QN38=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240401170217-c3f982113cda/go.mod h1:AHcE/gZH76Bk/ROZhQphlRoWo5xKDEtz3eVEO1LfA8c=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||||
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
|
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
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=
|
||||||
|
mvdan.cc/gofumpt v0.5.0 h1:0EQ+Z56k8tXjj/6TQD25BFNKQXpCvT0rnansIc7Ug5E=
|
||||||
|
mvdan.cc/gofumpt v0.5.0/go.mod h1:HBeVDtMKRZpXyxFciAirzdKklDlGu8aAy1wEbH5Y9js=
|
3
go/.gitignore
vendored
Normal file
3
go/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
models/
|
||||||
|
*.db
|
||||||
|
gen/
|
18
go/api/api.go
Normal file
18
go/api/api.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/stephenafamo/bob"
|
||||||
|
)
|
||||||
|
|
||||||
|
type API struct {
|
||||||
|
mu sync.Mutex
|
||||||
|
db bob.Executor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (api *API) lockf(f func()) {
|
||||||
|
api.mu.Lock()
|
||||||
|
defer api.mu.Unlock()
|
||||||
|
f()
|
||||||
|
}
|
47
go/api/devices_create.go
Normal file
47
go/api/devices_create.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"connectrpc.com/connect"
|
||||||
|
"github.com/aarondl/opt/omit"
|
||||||
|
"github.com/mattn/go-sqlite3"
|
||||||
|
"github.com/tigorlazuardi/bluemage/go/gen/models"
|
||||||
|
"github.com/tigorlazuardi/bluemage/go/pkg/errs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *API) DevicesCreate(ctx context.Context, params *models.Device) (device *models.Device, err error) {
|
||||||
|
now := time.Now()
|
||||||
|
api.lockf(func() {
|
||||||
|
device, err = models.Devices.Insert(ctx, api.db, &models.DeviceSetter{
|
||||||
|
Slug: omit.From(params.Slug),
|
||||||
|
Name: omit.From(params.Name),
|
||||||
|
ResolutionX: omit.From(params.ResolutionX),
|
||||||
|
ResolutionY: omit.From(params.ResolutionY),
|
||||||
|
AspectRatioTolerance: omit.From(params.AspectRatioTolerance),
|
||||||
|
MinX: omit.From(params.MinX),
|
||||||
|
MinY: omit.From(params.MinY),
|
||||||
|
MaxX: omit.From(params.MaxX),
|
||||||
|
MaxY: omit.From(params.MaxY),
|
||||||
|
NSFW: omit.From(params.NSFW),
|
||||||
|
SingleFolderMode: omit.From(params.SingleFolderMode),
|
||||||
|
Disabled: omit.From(params.Disabled),
|
||||||
|
CreatedAt: omit.From(now.Unix()),
|
||||||
|
UpdatedAt: omit.From(now.Unix()),
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
var sqliteErr sqlite3.Error
|
||||||
|
if errors.As(err, &sqliteErr) {
|
||||||
|
if sqliteErr.Code == sqlite3.ErrConstraint {
|
||||||
|
return nil, errs.
|
||||||
|
Wrapw(sqliteErr, "device already exists", "params", params).
|
||||||
|
Code(connect.CodeAlreadyExists)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errs.Wrapw(err, "failed to create device", "params", params)
|
||||||
|
}
|
||||||
|
return device, nil
|
||||||
|
}
|
22
go/api/devices_get_by_slug.go
Normal file
22
go/api/devices_get_by_slug.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"connectrpc.com/connect"
|
||||||
|
"github.com/tigorlazuardi/bluemage/go/gen/models"
|
||||||
|
"github.com/tigorlazuardi/bluemage/go/pkg/errs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (api *API) GetDevice(ctx context.Context, slug string) (device *models.Device, err error) {
|
||||||
|
device, err = models.FindDevice(ctx, api.db, slug)
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == "sql: no rows in result set" {
|
||||||
|
return device, errs.Wrapw(err, "device not found", "device", device).Code(connect.CodeNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
return device, errs.Wrapw(err, "failed to find device", "device", device)
|
||||||
|
}
|
||||||
|
|
||||||
|
return device, nil
|
||||||
|
}
|
2
go/bobgen.yaml
Normal file
2
go/bobgen.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
sqlite:
|
||||||
|
dsn: ../data.db
|
|
@ -1,3 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
func main() {}
|
import "time"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
time.Now().Unix()
|
||||||
|
}
|
||||||
|
|
31
go/converter/converter.go
Normal file
31
go/converter/converter.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package converter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/tigorlazuardi/bluemage/go/gen/models"
|
||||||
|
device "github.com/tigorlazuardi/bluemage/go/gen/proto/device/v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// goverter:converter
|
||||||
|
// goverter:extend BoolToInt8
|
||||||
|
// goverter:extend Int8ToBool
|
||||||
|
type DeviceConverter interface {
|
||||||
|
// goverter:ignore CreatedAt UpdatedAt
|
||||||
|
// goverter:map Nsfw NSFW
|
||||||
|
CreateDeviceRequestToModelsDevice(*device.CreateDeviceRequest) *models.Device
|
||||||
|
// goverter:ignore state sizeCache unknownFields
|
||||||
|
ModelsDeviceToCreateDeviceResponse(*models.Device) *device.CreateDeviceResponse
|
||||||
|
// goverter:ignore state sizeCache unknownFields
|
||||||
|
// goverter:map NSFW Nsfw
|
||||||
|
ModelsDeviceToGetDeviceResponse(*models.Device) *device.GetDeviceResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func BoolToInt8(b bool) int8 {
|
||||||
|
if b {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func Int8ToBool(i int8) bool {
|
||||||
|
return i > 0
|
||||||
|
}
|
84
go/pkg/caller/caller.go
Normal file
84
go/pkg/caller/caller.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
package caller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Caller struct {
|
||||||
|
PC uintptr
|
||||||
|
Frame runtime.Frame
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ca Caller) File() string {
|
||||||
|
return ca.Frame.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ca Caller) ShortFile() string {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return ca.Frame.File
|
||||||
|
}
|
||||||
|
if after, found := strings.CutPrefix(ca.Frame.File, wd); found {
|
||||||
|
return strings.TrimPrefix(after, string(os.PathSeparator))
|
||||||
|
}
|
||||||
|
return ca.Frame.File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ca Caller) Line() int {
|
||||||
|
return ca.Frame.Line
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ca Caller) Function() string {
|
||||||
|
return ca.Frame.Function
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ca Caller) ShortFunction() string {
|
||||||
|
split := strings.Split(ca.Frame.Function, string(os.PathSeparator))
|
||||||
|
return split[len(split)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ca Caller) LogValue() slog.Value {
|
||||||
|
if ca.PC == 0 {
|
||||||
|
return slog.AnyValue(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return slog.GroupValue(
|
||||||
|
slog.String("file", ca.ShortFile()),
|
||||||
|
slog.Int("line", ca.Line()),
|
||||||
|
slog.String("function", ca.ShortFunction()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(skip int) Caller {
|
||||||
|
var c Caller
|
||||||
|
pcs := make([]uintptr, 1)
|
||||||
|
n := runtime.Callers(skip, pcs)
|
||||||
|
if n == 0 {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
c.PC = pcs[0]
|
||||||
|
c.Frame, _ = runtime.CallersFrames(pcs).Next()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func From(pc uintptr) Caller {
|
||||||
|
var c Caller
|
||||||
|
c.PC = pc
|
||||||
|
c.Frame, _ = runtime.CallersFrames([]uintptr{pc}).Next()
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type contextKey struct{}
|
||||||
|
|
||||||
|
func WithContext(ctx context.Context, caller Caller) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKey{}, caller)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromContext(ctx context.Context) (Caller, bool) {
|
||||||
|
call, ok := ctx.Value(contextKey{}).(Caller)
|
||||||
|
return call, ok
|
||||||
|
}
|
231
go/pkg/errs/errs.go
Normal file
231
go/pkg/errs/errs.go
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
package errs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"connectrpc.com/connect"
|
||||||
|
"github.com/tigorlazuardi/bluemage/go/pkg/caller"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Error interface {
|
||||||
|
error
|
||||||
|
Message(msg string, args ...any) Error
|
||||||
|
GetMessage() string
|
||||||
|
Code(status connect.Code) Error
|
||||||
|
GetCode() connect.Code
|
||||||
|
Caller(pc caller.Caller) Error
|
||||||
|
GetCaller() caller.Caller
|
||||||
|
Details(...any) Error
|
||||||
|
GetDetails() []any
|
||||||
|
Log(ctx context.Context) Error
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ Error = (*Err)(nil)
|
||||||
|
_ slog.LogValuer = (*Err)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Err struct {
|
||||||
|
message string
|
||||||
|
code connect.Code
|
||||||
|
caller caller.Caller
|
||||||
|
details []any
|
||||||
|
origin error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unwrap implements the implementation of errors.Unwrap.
|
||||||
|
func (er *Err) Unwrap() error {
|
||||||
|
return er.origin
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogValue implements slog.LogValuer.
|
||||||
|
func (er *Err) LogValue() slog.Value {
|
||||||
|
values := make([]slog.Attr, 0, 5)
|
||||||
|
if er.message != "" {
|
||||||
|
values = append(values, slog.String("message", er.message))
|
||||||
|
}
|
||||||
|
if er.code != 0 && er.code != connect.CodeInternal {
|
||||||
|
values = append(values, slog.String("code", er.code.String()))
|
||||||
|
}
|
||||||
|
if er.caller.PC != 0 {
|
||||||
|
values = append(values, slog.Attr{Key: "caller", Value: er.caller.LogValue()})
|
||||||
|
}
|
||||||
|
if len(er.details) > 0 {
|
||||||
|
values = append(values, slog.Group("details", er.details...))
|
||||||
|
}
|
||||||
|
if er.origin == nil {
|
||||||
|
values = append(values, slog.Any("error", er.origin))
|
||||||
|
} else if lv, ok := er.origin.(slog.LogValuer); ok {
|
||||||
|
values = append(values, slog.Attr{Key: "error", Value: lv.LogValue()})
|
||||||
|
} else {
|
||||||
|
values = append(values, slog.Attr{Key: "error", Value: slog.GroupValue(
|
||||||
|
slog.String("type", reflect.TypeOf(er.origin).String()),
|
||||||
|
slog.String("message", er.origin.Error()),
|
||||||
|
slog.Any("data", er.origin),
|
||||||
|
)})
|
||||||
|
}
|
||||||
|
|
||||||
|
return slog.GroupValue(values...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Caller implements Error.
|
||||||
|
func (e *Err) Caller(pc caller.Caller) Error {
|
||||||
|
e.caller = pc
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Code implements Error.
|
||||||
|
func (e *Err) Code(status connect.Code) Error {
|
||||||
|
e.code = status
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Details implements Error.
|
||||||
|
func (e *Err) Details(keysAndValues ...any) Error {
|
||||||
|
e.details = keysAndValues
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error implements Error.
|
||||||
|
//
|
||||||
|
// The error message is constructed by concatenating the message of the error,
|
||||||
|
// discarding any empty and duplicate messages in the chain.
|
||||||
|
//
|
||||||
|
// The error messages are concatenated with a colon and a space.
|
||||||
|
func (e *Err) Error() string {
|
||||||
|
s := strings.Builder{}
|
||||||
|
if e.message != "" {
|
||||||
|
s.WriteString(e.message)
|
||||||
|
if e.origin != nil {
|
||||||
|
s.WriteString(": ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unwrap := errors.Unwrap(e)
|
||||||
|
for unwrap != nil {
|
||||||
|
var current string
|
||||||
|
if e, ok := unwrap.(Error); ok && e.GetMessage() != "" {
|
||||||
|
current = e.GetMessage()
|
||||||
|
} else {
|
||||||
|
current = unwrap.Error()
|
||||||
|
}
|
||||||
|
next := errors.Unwrap(unwrap)
|
||||||
|
if next != nil {
|
||||||
|
current, _ = strings.CutSuffix(current, next.Error())
|
||||||
|
current, _ = strings.CutSuffix(current, ": ")
|
||||||
|
}
|
||||||
|
if current != "" {
|
||||||
|
s.WriteString(current)
|
||||||
|
if next != nil {
|
||||||
|
s.WriteString(": ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unwrap = next
|
||||||
|
}
|
||||||
|
return s.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCaller implements Error.
|
||||||
|
func (e *Err) GetCaller() caller.Caller {
|
||||||
|
return e.caller
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCode implements Error.
|
||||||
|
func (e *Err) GetCode() connect.Code {
|
||||||
|
return e.code
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDetails implements Error.
|
||||||
|
func (e *Err) GetDetails() []any {
|
||||||
|
return e.details
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMessage implements Error.
|
||||||
|
func (e *Err) GetMessage() string {
|
||||||
|
return e.message
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log implements Error.
|
||||||
|
func (e *Err) Log(ctx context.Context) Error {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message implements Error.
|
||||||
|
func (e *Err) Message(msg string, args ...any) Error {
|
||||||
|
e.message = fmt.Sprintf(msg, args...)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
|
||||||
|
func Wrap(err error, message string) Error {
|
||||||
|
return &Err{
|
||||||
|
origin: err,
|
||||||
|
caller: caller.New(3),
|
||||||
|
message: message,
|
||||||
|
code: connect.CodeInternal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Wrapw(err error, message string, details ...any) Error {
|
||||||
|
return &Err{
|
||||||
|
origin: err,
|
||||||
|
details: details,
|
||||||
|
message: message,
|
||||||
|
caller: caller.New(3),
|
||||||
|
code: connect.CodeInternal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Wrapf(err error, message string, args ...any) Error {
|
||||||
|
message = fmt.Sprintf(message, args...)
|
||||||
|
return &Err{
|
||||||
|
origin: err,
|
||||||
|
message: message,
|
||||||
|
caller: caller.New(3),
|
||||||
|
code: connect.CodeInternal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Fail(message string, details ...any) Error {
|
||||||
|
return &Err{
|
||||||
|
origin: errors.New(message),
|
||||||
|
details: details,
|
||||||
|
caller: caller.New(3),
|
||||||
|
code: connect.CodeInternal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Failf(message string, args ...any) Error {
|
||||||
|
return &Err{
|
||||||
|
origin: fmt.Errorf(message, args...),
|
||||||
|
caller: caller.New(3),
|
||||||
|
code: connect.CodeInternal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IntoConnectError(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if e := FindError(err); e != nil {
|
||||||
|
return connect.NewError(e.GetCode(), e)
|
||||||
|
}
|
||||||
|
return connect.NewError(connect.CodeUnknown, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindError(err error) Error {
|
||||||
|
for {
|
||||||
|
if e, ok := err.(Error); ok {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
if e, ok := err.(interface{ Unwrap() error }); ok {
|
||||||
|
err = e.Unwrap()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
57
go/server/device_handlers.go
Normal file
57
go/server/device_handlers.go
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"connectrpc.com/connect"
|
||||||
|
"github.com/bufbuild/protovalidate-go"
|
||||||
|
"github.com/tigorlazuardi/bluemage/go/api"
|
||||||
|
"github.com/tigorlazuardi/bluemage/go/gen/converter"
|
||||||
|
device "github.com/tigorlazuardi/bluemage/go/gen/proto/device/v1"
|
||||||
|
"github.com/tigorlazuardi/bluemage/go/gen/proto/device/v1/v1connect"
|
||||||
|
"github.com/tigorlazuardi/bluemage/go/pkg/errs"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ v1connect.DeviceServiceHandler = (*DeviceHandler)(nil)
|
||||||
|
|
||||||
|
type DeviceHandler struct {
|
||||||
|
API *api.API
|
||||||
|
|
||||||
|
v1connect.UnimplementedDeviceServiceHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
convert converter.DeviceConverterImpl
|
||||||
|
validate, _ = protovalidate.New()
|
||||||
|
)
|
||||||
|
|
||||||
|
// CreateDevice implements v1connect.DeviceServiceHandler.
|
||||||
|
func (d *DeviceHandler) CreateDevice(ctx context.Context, request *connect.Request[device.CreateDeviceRequest]) (*connect.Response[device.CreateDeviceResponse], error) {
|
||||||
|
err := validate.Validate(request.Msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, connect.NewError(connect.CodeInvalidArgument, err)
|
||||||
|
}
|
||||||
|
dev := convert.CreateDeviceRequestToModelsDevice(request.Msg)
|
||||||
|
dev, err = d.API.DevicesCreate(ctx, dev)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.IntoConnectError(err)
|
||||||
|
}
|
||||||
|
devResp := convert.ModelsDeviceToCreateDeviceResponse(dev)
|
||||||
|
return connect.NewResponse(devResp), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDevice implements v1connect.DeviceServiceHandler.
|
||||||
|
func (d *DeviceHandler) GetDevice(ctx context.Context, request *connect.Request[device.GetDeviceRequest]) (*connect.Response[device.GetDeviceResponse], error) {
|
||||||
|
err := validate.Validate(request.Msg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, connect.NewError(connect.CodeInvalidArgument, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dev, err := d.API.GetDevice(ctx, request.Msg.Slug)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.IntoConnectError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
devResp := convert.ModelsDeviceToGetDeviceResponse(dev)
|
||||||
|
return connect.NewResponse(devResp), nil
|
||||||
|
}
|
5
go/server/server.go
Normal file
5
go/server/server.go
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
DeviceHandler
|
||||||
|
}
|
12
go/tools/tools.go
Normal file
12
go/tools/tools.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package tools
|
||||||
|
|
||||||
|
// These blank imports are used to ensure that dependency tools are included in the go.mod file.
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "connectrpc.com/connect"
|
||||||
|
_ "github.com/aarondl/opt"
|
||||||
|
_ "github.com/bufbuild/protovalidate-go"
|
||||||
|
_ "github.com/stephenafamo/bob"
|
||||||
|
_ "google.golang.org/protobuf/reflect/protoreflect"
|
||||||
|
_ "google.golang.org/protobuf/runtime/protoimpl"
|
||||||
|
)
|
|
@ -3,15 +3,15 @@
|
||||||
CREATE TABLE metrics (
|
CREATE TABLE metrics (
|
||||||
name VARCHAR(255) NOT NULL PRIMARY KEY COLLATE NOCASE,
|
name VARCHAR(255) NOT NULL PRIMARY KEY COLLATE NOCASE,
|
||||||
value BIGINT DEFAULT 0 NOT NULL,
|
value BIGINT DEFAULT 0 NOT NULL,
|
||||||
created_at BIGINT DEFAULT 0 NOT NULL,
|
created_at BIGINT DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
updated_at BIGINT DEFAULT 0 NOT NULL
|
updated_at BIGINT DEFAULT (strftime('%s', 'now')) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX idx_metrics_name ON metrics(name);
|
CREATE UNIQUE INDEX idx_metrics_name ON metrics(name);
|
||||||
|
|
||||||
CREATE TRIGGER update_metrics_timestamp_after_update AFTER UPDATE ON metrics FOR EACH ROW
|
CREATE TRIGGER update_metrics_timestamp_after_update AFTER UPDATE ON metrics FOR EACH ROW
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE metrics SET updated_at = CURRENT_TIMESTAMP WHERE name = old.name;
|
UPDATE metrics SET updated_at = (strftime('%s', 'now')) WHERE name = old.name;
|
||||||
END;
|
END;
|
||||||
-- +goose StatementEnd
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
|
|
@ -2,26 +2,26 @@
|
||||||
-- +goose StatementBegin
|
-- +goose StatementBegin
|
||||||
CREATE TABLE subreddits (
|
CREATE TABLE subreddits (
|
||||||
name VARCHAR(30) NOT NULL PRIMARY KEY COLLATE NOCASE,
|
name VARCHAR(30) NOT NULL PRIMARY KEY COLLATE NOCASE,
|
||||||
disable_scheduler INT NOT NULL DEFAULT 0,
|
disable_scheduler TINYINT NOT NULL DEFAULT 0,
|
||||||
"type" VARCHAR(5) NOT NULL DEFAULT 'r',
|
"type" VARCHAR(5) NOT NULL DEFAULT 'r',
|
||||||
schedule VARCHAR(20) NOT NULL DEFAULT '@daily',
|
schedule VARCHAR(20) NOT NULL DEFAULT '@daily',
|
||||||
countback INT NOT NULL DEFAULT 300,
|
countback INT NOT NULL DEFAULT 300,
|
||||||
image_cover VARCHAR(255) NOT NULL DEFAULT '',
|
image_cover VARCHAR(255) NOT NULL DEFAULT '',
|
||||||
created_at BIGINT DEFAULT 0 NOT NULL,
|
created_at BIGINT DEFAULT (strftime('%s', 'now')) NOT NULL,
|
||||||
updated_at BIGINT DEFAULT 0 NOT NULL
|
updated_at BIGINT DEFAULT (strftime('%s', 'now')) NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE UNIQUE INDEX idx_subreddits_name ON subreddits(name);
|
CREATE UNIQUE INDEX idx_subreddits_name ON subreddits(name);
|
||||||
|
|
||||||
CREATE TRIGGER subreddits_update_timestamp AFTER UPDATE ON subreddits FOR EACH ROW
|
CREATE TRIGGER subreddits_update_timestamp AFTER UPDATE ON subreddits FOR EACH ROW
|
||||||
BEGIN
|
BEGIN
|
||||||
UPDATE subreddits SET updated_at = CURRENT_TIMESTAMP WHERE name = old.name;
|
UPDATE subreddits SET updated_at = (strftime('%s', 'now')) WHERE name = old.name;
|
||||||
END;
|
END;
|
||||||
|
|
||||||
CREATE TRIGGER subreddits_insert_create_total_image_metrics AFTER INSERT on subreddits FOR EACH ROW
|
CREATE TRIGGER subreddits_insert_create_total_image_metrics AFTER INSERT on subreddits FOR EACH ROW
|
||||||
BEGIN
|
BEGIN
|
||||||
INSERT INTO metrics (name, value, created_at, updated_at)
|
INSERT INTO metrics (name, value)
|
||||||
VALUES (CONCAT('subreddits.', NEW.name, '.total_images'), 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP);
|
VALUES (CONCAT('subreddits.', NEW.name, '.total_images'), 0);
|
||||||
END;
|
END;
|
||||||
|
|
||||||
CREATE TRIGGER subreddits_delete_total_image_metrics AFTER DELETE on subreddits FOR EACH ROW
|
CREATE TRIGGER subreddits_delete_total_image_metrics AFTER DELETE on subreddits FOR EACH ROW
|
||||||
|
|
42
schemas/migrations/20240805124501_create_devices_table.sql
Normal file
42
schemas/migrations/20240805124501_create_devices_table.sql
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
-- +goose Up
|
||||||
|
-- +goose StatementBegin
|
||||||
|
CREATE TABLE devices (
|
||||||
|
slug VARCHAR(255) PRIMARY KEY COLLATE NOCASE,
|
||||||
|
disabled TINYINT NOT NULL DEFAULT 0,
|
||||||
|
name TEXT NOT NULL COLLATE NOCASE,
|
||||||
|
resolution_x DOUBLE NOT NULL,
|
||||||
|
resolution_y DOUBLE NOT NULL,
|
||||||
|
aspect_ratio_tolerance DOUBLE NOT NULL DEFAULT 0.2,
|
||||||
|
min_x INTEGER NOT NULL DEFAULT 0,
|
||||||
|
min_y INTEGER NOT NULL DEFAULT 0,
|
||||||
|
max_x INTEGER NOT NULL DEFAULT 0,
|
||||||
|
max_y INTEGER NOT NULL DEFAULT 0,
|
||||||
|
nsfw TINYINT NOT NULL DEFAULT 0,
|
||||||
|
single_folder_mode TINYINT NOT NULL DEFAULT 0,
|
||||||
|
created_at BIGINT NOT NULL DEFAULT (strftime('%s', 'now')),
|
||||||
|
updated_at BIGINT NOT NULL DEFAULT (strftime('%s', 'now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX idx_devices_unique_slug ON devices(slug);
|
||||||
|
|
||||||
|
CREATE TRIGGER devices_update_timestamp AFTER UPDATE ON devices FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
UPDATE devices SET updated_at = strftime('%s', 'now') WHERE slug = old.slug;
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER devices_insert_create_total_image_metrics AFTER INSERT on devices FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
INSERT INTO metrics (name, value)
|
||||||
|
VALUES (CONCAT('devices.', NEW.slug, '.total_images'), 0);
|
||||||
|
END;
|
||||||
|
|
||||||
|
CREATE TRIGGER devices_delete_total_image_metrics AFTER DELETE on devices FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
DELETE FROM metrics WHERE name = CONCAT('devices.', OLD.slug, '.total_images');
|
||||||
|
END;
|
||||||
|
-- +goose StatementEnd
|
||||||
|
|
||||||
|
-- +goose Down
|
||||||
|
-- +goose StatementBegin
|
||||||
|
DROP TABLE devices;
|
||||||
|
-- +goose StatementEnd
|
|
@ -7,26 +7,55 @@ import "buf/validate/validate.proto";
|
||||||
option go_package = "github.com/tigorlazuardi/bluemage/go/gen/proto/device/v1";
|
option go_package = "github.com/tigorlazuardi/bluemage/go/gen/proto/device/v1";
|
||||||
|
|
||||||
service DeviceService {
|
service DeviceService {
|
||||||
|
// GetDevice fetches a device by its slug.
|
||||||
|
//
|
||||||
|
// If the device is not found, an error will be returned.
|
||||||
|
rpc GetDevice(GetDeviceRequest) returns (GetDeviceResponse) {}
|
||||||
|
|
||||||
// CreateDevice creates a new device.
|
// CreateDevice creates a new device.
|
||||||
rpc CreateDevice(CreateDeviceRequest) returns (CreateDeviceResponse) {}
|
rpc CreateDevice(CreateDeviceRequest) returns (CreateDeviceResponse) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message GetDeviceRequest {
|
||||||
|
// The `slug` is a unique identifier for the device.
|
||||||
|
string slug = 1 [(buf.validate.field).string.min_len = 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetDeviceResponse {
|
||||||
|
string slug = 1;
|
||||||
|
bool disabled = 2;
|
||||||
|
string name = 3;
|
||||||
|
double resolution_x = 4;
|
||||||
|
double resolution_y = 5;
|
||||||
|
double aspect_ratio_tolerance = 6;
|
||||||
|
int32 min_x = 7;
|
||||||
|
int32 min_y = 8;
|
||||||
|
int32 max_x = 9;
|
||||||
|
int32 max_y = 10;
|
||||||
|
bool nsfw = 11;
|
||||||
|
bool single_folder_mode = 12;
|
||||||
|
int64 created_at = 13;
|
||||||
|
int64 updated_at = 14;
|
||||||
|
}
|
||||||
|
|
||||||
message CreateDeviceRequest {
|
message CreateDeviceRequest {
|
||||||
// The `slug` is a unique identifier for the device, used to identify the device within the system.
|
// The `slug` is a unique identifier for the device, used to identify the device within the system.
|
||||||
// Each `slug` must be unique across all devices.
|
// Each `slug` must be unique across all devices.
|
||||||
string slug = 1 [(buf.validate.field).string.min_len = 1];
|
string slug = 1 [(buf.validate.field).string.min_len = 1];
|
||||||
|
|
||||||
// `enable` is a flag to set if a device is enabled upon creation. Default false.
|
// `disabled` is a flag to set if a device is enabled upon creation. Default false.
|
||||||
bool enable = 2;
|
bool disabled = 2;
|
||||||
|
|
||||||
// The name of the device. This is used for display purposes.
|
// The name of the device. This is used for display purposes.
|
||||||
string name = 3;
|
string name = 3;
|
||||||
|
|
||||||
// The display x resolution of the device.
|
// The display x resolution of the device.
|
||||||
double resolution_x = 4;
|
// The value must be greater than 0.
|
||||||
|
double resolution_x = 4 [(buf.validate.field).double.gt = 0];
|
||||||
|
|
||||||
// The display y resolution of the device.
|
// The display y resolution of the device.
|
||||||
double resolution_y = 5;
|
// The value must be greater than 0.
|
||||||
|
double resolution_y = 5 [(buf.validate.field).double.gt = 0];
|
||||||
|
|
||||||
// aspect_ratio_tolerance is a tolerance for aspect ratio images to be accepted.
|
// aspect_ratio_tolerance is a tolerance for aspect ratio images to be accepted.
|
||||||
//
|
//
|
||||||
|
@ -43,7 +72,6 @@ message CreateDeviceRequest {
|
||||||
// accepted by the device even though it is 2 times bigger than the device resolution.
|
// accepted by the device even though it is 2 times bigger than the device resolution.
|
||||||
//
|
//
|
||||||
// If you want to filter image size, use min_x, min_y, max_x, max_y fields.
|
// If you want to filter image size, use min_x, min_y, max_x, max_y fields.
|
||||||
//
|
|
||||||
double aspect_ratio_tolerance = 6;
|
double aspect_ratio_tolerance = 6;
|
||||||
|
|
||||||
// The minimum x resolution required for an image to be accepted by the device.
|
// The minimum x resolution required for an image to be accepted by the device.
|
||||||
|
@ -51,35 +79,35 @@ message CreateDeviceRequest {
|
||||||
// A value of 0 indicates no minimum x resolution.
|
// A value of 0 indicates no minimum x resolution.
|
||||||
//
|
//
|
||||||
// The default value is 0.
|
// The default value is 0.
|
||||||
int32 min_x = 7;
|
int32 min_x = 7 [(buf.validate.field).int32.gte = 0];
|
||||||
|
|
||||||
// The minimum y resolution required for an image to be accepted by the device.
|
// The minimum y resolution required for an image to be accepted by the device.
|
||||||
//
|
//
|
||||||
// A value of 0 indicates no minimum y resolution.
|
// A value of 0 indicates no minimum y resolution.
|
||||||
//
|
//
|
||||||
// The default value is 0.
|
// The default value is 0.
|
||||||
int32 min_y = 8;
|
int32 min_y = 8 [(buf.validate.field).int32.gte = 0];
|
||||||
|
|
||||||
// The maximum x resolution allowed for an image to be accepted by the device.
|
// The maximum x resolution allowed for an image to be accepted by the device.
|
||||||
//
|
//
|
||||||
// A value of 0 indicates no maximum x resolution.
|
// A value of 0 indicates no maximum x resolution.
|
||||||
//
|
//
|
||||||
// The default value is 0.
|
// The default value is 0.
|
||||||
int32 max_x = 9;
|
int32 max_x = 9 [(buf.validate.field).int32.gte = 0];
|
||||||
|
|
||||||
// The maximum y resolution allowed for an image to be accepted by the device.
|
// The maximum y resolution allowed for an image to be accepted by the device.
|
||||||
//
|
//
|
||||||
// A value of 0 indicates no maximum y resolution.
|
// A value of 0 indicates no maximum y resolution.
|
||||||
//
|
//
|
||||||
// The default value is 0.
|
// The default value is 0.
|
||||||
int32 max_y = 10;
|
int32 max_y = 10 [(buf.validate.field).int32.gte = 0];
|
||||||
|
|
||||||
// The `nsfw` parameter allows the device to accept NSFW (Not Safe For Work) images when set to true.
|
// The `nsfw` parameter allows the device to accept NSFW (Not Safe For Work) images when set to true.
|
||||||
bool nsfw = 11;
|
bool nsfw = 11;
|
||||||
|
|
||||||
// `windows_wallpaper_mode` when set to true, downloaded images will be put on single folder location
|
// `single_folder_mode` when set to true, downloaded images will be put on single folder location
|
||||||
// instead of inside folders separated by subreddit names.
|
// instead of inside folders separated by subreddit names.
|
||||||
bool windows_wallpaper_mode = 12;
|
bool single_folder_mode = 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateDeviceResponse {
|
message CreateDeviceResponse {
|
||||||
|
|
1
web/.gitignore
vendored
1
web/.gitignore
vendored
|
@ -22,3 +22,4 @@ dist-ssr
|
||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
gen/
|
||||||
|
|
Loading…
Reference in a new issue