From b23ac7617e7cd8a031b9c972fadfddb1c5c437b4 Mon Sep 17 00:00:00 2001 From: Tigor Hutasuhut Date: Mon, 5 Aug 2024 23:06:32 +0700 Subject: [PATCH] more updates --- .gitignore | 3 - Makefile | 13 +- flake.nix | 25 ++ go.mod | 24 ++ go.sum | 119 +++++++++ go/.gitignore | 3 + go/api/api.go | 18 ++ go/api/devices_create.go | 47 ++++ go/api/devices_get_by_slug.go | 22 ++ go/bobgen.yaml | 2 + go/cmd/bluemage/main.go | 6 +- go/converter/converter.go | 31 +++ go/pkg/caller/caller.go | 84 +++++++ go/pkg/errs/errs.go | 231 ++++++++++++++++++ go/server/device_handlers.go | 57 +++++ go/server/server.go | 5 + go/tools/tools.go | 12 + .../20240804155218_create_metrics_table.sql | 6 +- ...20240804155710_create_subreddits_table.sql | 12 +- .../20240805124501_create_devices_table.sql | 42 ++++ schemas/proto/device/v1/device.proto | 50 +++- web/.gitignore | 1 + 22 files changed, 783 insertions(+), 30 deletions(-) create mode 100644 go.sum create mode 100644 go/.gitignore create mode 100644 go/api/api.go create mode 100644 go/api/devices_create.go create mode 100644 go/api/devices_get_by_slug.go create mode 100644 go/bobgen.yaml create mode 100644 go/converter/converter.go create mode 100644 go/pkg/caller/caller.go create mode 100644 go/pkg/errs/errs.go create mode 100644 go/server/device_handlers.go create mode 100644 go/server/server.go create mode 100644 go/tools/tools.go create mode 100644 schemas/migrations/20240805124501_create_devices_table.sql diff --git a/.gitignore b/.gitignore index 34a79a2..8899d84 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,3 @@ go.work .direnv bin/ -gen/ - -*.db diff --git a/Makefile b/Makefile index 19f22ad..073681c 100644 --- a/Makefile +++ b/Makefile @@ -1,17 +1,18 @@ .ONESHELL: export PATH := $(shell pwd)/bin:$(shell pwd)/web/node_modules/.bin:$(PATH) -export GOOSE_DRIVER=sqlite3 -export GOOSE_DBSTRING=./data.db -export GOOSE_MIGRATION_DIR=schemas/migrations +export GOOSE_DRIVER ?= sqlite3 +export GOOSE_DBSTRING ?= ./go/data.db +export GOOSE_MIGRATION_DIR ?= schemas/migrations build: generate-go generate-web go build -o bin/bluemage ./go/cmd/bluemage/main.go -generate-go: +generate-go: migrate rm -rf go/gen - cd ./schemas/proto - buf generate --template buf.gen.go.yaml . + (cd ./schemas/proto && 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: rm -rf web/gen diff --git a/flake.nix b/flake.nix index 8c25edd..2080eb5 100644 --- a/flake.nix +++ b/flake.nix @@ -30,6 +30,29 @@ ''; 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 { devShell.${system} = pkgs.mkShell { @@ -45,7 +68,9 @@ protoc-gen-go protoc-gen-connect-go protoc-gen-validate + impl goverter + bobgen-sqlite ]; }; }; diff --git a/go.mod b/go.mod index 998b841..1254a13 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,27 @@ module github.com/tigorlazuardi/bluemage 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 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..8d26e64 --- /dev/null +++ b/go.sum @@ -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= diff --git a/go/.gitignore b/go/.gitignore new file mode 100644 index 0000000..88ab312 --- /dev/null +++ b/go/.gitignore @@ -0,0 +1,3 @@ +models/ +*.db +gen/ diff --git a/go/api/api.go b/go/api/api.go new file mode 100644 index 0000000..2d3f9c9 --- /dev/null +++ b/go/api/api.go @@ -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() +} diff --git a/go/api/devices_create.go b/go/api/devices_create.go new file mode 100644 index 0000000..4a68257 --- /dev/null +++ b/go/api/devices_create.go @@ -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 +} diff --git a/go/api/devices_get_by_slug.go b/go/api/devices_get_by_slug.go new file mode 100644 index 0000000..4cd18f0 --- /dev/null +++ b/go/api/devices_get_by_slug.go @@ -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 +} diff --git a/go/bobgen.yaml b/go/bobgen.yaml new file mode 100644 index 0000000..e58a149 --- /dev/null +++ b/go/bobgen.yaml @@ -0,0 +1,2 @@ +sqlite: + dsn: ../data.db diff --git a/go/cmd/bluemage/main.go b/go/cmd/bluemage/main.go index 38dd16d..332d13d 100644 --- a/go/cmd/bluemage/main.go +++ b/go/cmd/bluemage/main.go @@ -1,3 +1,7 @@ package main -func main() {} +import "time" + +func main() { + time.Now().Unix() +} diff --git a/go/converter/converter.go b/go/converter/converter.go new file mode 100644 index 0000000..46249c4 --- /dev/null +++ b/go/converter/converter.go @@ -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 +} diff --git a/go/pkg/caller/caller.go b/go/pkg/caller/caller.go new file mode 100644 index 0000000..80b8113 --- /dev/null +++ b/go/pkg/caller/caller.go @@ -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 +} diff --git a/go/pkg/errs/errs.go b/go/pkg/errs/errs.go new file mode 100644 index 0000000..5351485 --- /dev/null +++ b/go/pkg/errs/errs.go @@ -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 + } +} diff --git a/go/server/device_handlers.go b/go/server/device_handlers.go new file mode 100644 index 0000000..a6f5d32 --- /dev/null +++ b/go/server/device_handlers.go @@ -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 +} diff --git a/go/server/server.go b/go/server/server.go new file mode 100644 index 0000000..c787f34 --- /dev/null +++ b/go/server/server.go @@ -0,0 +1,5 @@ +package server + +type Server struct { + DeviceHandler +} diff --git a/go/tools/tools.go b/go/tools/tools.go new file mode 100644 index 0000000..e12de1f --- /dev/null +++ b/go/tools/tools.go @@ -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" +) diff --git a/schemas/migrations/20240804155218_create_metrics_table.sql b/schemas/migrations/20240804155218_create_metrics_table.sql index 1deaa46..95a8eb7 100644 --- a/schemas/migrations/20240804155218_create_metrics_table.sql +++ b/schemas/migrations/20240804155218_create_metrics_table.sql @@ -3,15 +3,15 @@ CREATE TABLE metrics ( name VARCHAR(255) NOT NULL PRIMARY KEY COLLATE NOCASE, value BIGINT DEFAULT 0 NOT NULL, - created_at BIGINT DEFAULT 0 NOT NULL, - updated_at BIGINT DEFAULT 0 NOT NULL + created_at BIGINT DEFAULT (strftime('%s', 'now')) NOT NULL, + updated_at BIGINT DEFAULT (strftime('%s', 'now')) NOT NULL ); CREATE UNIQUE INDEX idx_metrics_name ON metrics(name); CREATE TRIGGER update_metrics_timestamp_after_update AFTER UPDATE ON metrics FOR EACH ROW 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; -- +goose StatementEnd diff --git a/schemas/migrations/20240804155710_create_subreddits_table.sql b/schemas/migrations/20240804155710_create_subreddits_table.sql index 2cff9d7..417d874 100644 --- a/schemas/migrations/20240804155710_create_subreddits_table.sql +++ b/schemas/migrations/20240804155710_create_subreddits_table.sql @@ -2,26 +2,26 @@ -- +goose StatementBegin CREATE TABLE subreddits ( 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', schedule VARCHAR(20) NOT NULL DEFAULT '@daily', countback INT NOT NULL DEFAULT 300, image_cover VARCHAR(255) NOT NULL DEFAULT '', - created_at BIGINT DEFAULT 0 NOT NULL, - updated_at BIGINT DEFAULT 0 NOT NULL + created_at BIGINT DEFAULT (strftime('%s', 'now')) NOT NULL, + updated_at BIGINT DEFAULT (strftime('%s', 'now')) NOT NULL ); CREATE UNIQUE INDEX idx_subreddits_name ON subreddits(name); CREATE TRIGGER subreddits_update_timestamp AFTER UPDATE ON subreddits FOR EACH ROW 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; CREATE TRIGGER subreddits_insert_create_total_image_metrics AFTER INSERT on subreddits FOR EACH ROW BEGIN - INSERT INTO metrics (name, value, created_at, updated_at) - VALUES (CONCAT('subreddits.', NEW.name, '.total_images'), 0, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); + INSERT INTO metrics (name, value) + VALUES (CONCAT('subreddits.', NEW.name, '.total_images'), 0); END; CREATE TRIGGER subreddits_delete_total_image_metrics AFTER DELETE on subreddits FOR EACH ROW diff --git a/schemas/migrations/20240805124501_create_devices_table.sql b/schemas/migrations/20240805124501_create_devices_table.sql new file mode 100644 index 0000000..e4cba9b --- /dev/null +++ b/schemas/migrations/20240805124501_create_devices_table.sql @@ -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 diff --git a/schemas/proto/device/v1/device.proto b/schemas/proto/device/v1/device.proto index 583c7d5..3317bea 100644 --- a/schemas/proto/device/v1/device.proto +++ b/schemas/proto/device/v1/device.proto @@ -7,26 +7,55 @@ import "buf/validate/validate.proto"; option go_package = "github.com/tigorlazuardi/bluemage/go/gen/proto/device/v1"; 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. 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 { // 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. 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. - bool enable = 2; + // `disabled` is a flag to set if a device is enabled upon creation. Default false. + bool disabled = 2; // The name of the device. This is used for display purposes. string name = 3; // 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. - 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. // @@ -43,7 +72,6 @@ message CreateDeviceRequest { // 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. - // double aspect_ratio_tolerance = 6; // 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. // // 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. // // A value of 0 indicates no minimum y resolution. // // 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. // // A value of 0 indicates no maximum x resolution. // // 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. // // A value of 0 indicates no maximum y resolution. // // 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. 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. - bool windows_wallpaper_mode = 12; + bool single_folder_mode = 12; } message CreateDeviceResponse { diff --git a/web/.gitignore b/web/.gitignore index a547bf3..e8a8dda 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -22,3 +22,4 @@ dist-ssr *.njsproj *.sln *.sw? +gen/