From 2ab67023eb25a6678482ef54805fcb0f6a32b1db Mon Sep 17 00:00:00 2001
From: Sergei Loshkarev <saloshkarev@miem.hse.ru>
Date: Wed, 11 Oct 2023 05:37:22 +0300
Subject: [PATCH 01/12] add service base

---
 .gitignore                                    |   1 +
 cmd/app/main.go                               |  19 +++
 config.yaml                                   |   4 +
 go.mod                                        |  38 ++++++
 go.sum                                        |  99 ++++++++++++++
 internal/app/app.go                           |  77 +++++++++++
 internal/config/config.go                     |  28 ++++
 internal/entity/manipulator.go                |  49 +++++++
 internal/entity/setup.go                      |  15 +++
 internal/handlers/schemas.go                  |  18 +++
 internal/handlers/setups_router.go            |  79 ++++++++++++
 internal/handlers/utils.go                    |  22 ++++
 .../repositories/manipulator_repository.go    |  83 ++++++++++++
 internal/repositories/setup_repository.go     | 121 ++++++++++++++++++
 internal/repositories/utils.go                |   1 +
 .../services/setup_manipulator_service.go     |  70 ++++++++++
 internal/services/setup_service.go            |  55 ++++++++
 pkg/errs/errors.go                            |  19 +++
 18 files changed, 798 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 cmd/app/main.go
 create mode 100644 config.yaml
 create mode 100644 go.mod
 create mode 100644 go.sum
 create mode 100644 internal/app/app.go
 create mode 100644 internal/config/config.go
 create mode 100644 internal/entity/manipulator.go
 create mode 100644 internal/entity/setup.go
 create mode 100644 internal/handlers/schemas.go
 create mode 100644 internal/handlers/setups_router.go
 create mode 100644 internal/handlers/utils.go
 create mode 100644 internal/repositories/manipulator_repository.go
 create mode 100644 internal/repositories/setup_repository.go
 create mode 100644 internal/repositories/utils.go
 create mode 100644 internal/services/setup_manipulator_service.go
 create mode 100644 internal/services/setup_service.go
 create mode 100644 pkg/errs/errors.go

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cfd0ade
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/db.sqlite
diff --git a/cmd/app/main.go b/cmd/app/main.go
new file mode 100644
index 0000000..3ff7891
--- /dev/null
+++ b/cmd/app/main.go
@@ -0,0 +1,19 @@
+package main
+
+import (
+	"flag"
+	"git.miem.hse.ru/hubman/configurator/internal/app"
+	"git.miem.hse.ru/hubman/configurator/internal/config"
+	"log"
+)
+
+func main() {
+	confPath := flag.String("conf", "config.yaml", "Filepath to config")
+	flag.Parse()
+	conf, err := config.NewConfig(*confPath)
+	if err != nil {
+		log.Fatalln("Failed to load config")
+	}
+	app := app.NewApp(conf)
+	app.Run()
+}
diff --git a/config.yaml b/config.yaml
new file mode 100644
index 0000000..40a16e5
--- /dev/null
+++ b/config.yaml
@@ -0,0 +1,4 @@
+db:
+  path: "db.sqlite"
+http:
+  port: 8080
\ No newline at end of file
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..e842870
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,38 @@
+module git.miem.hse.ru/hubman/configurator
+
+go 1.20
+
+require (
+	github.com/BurntSushi/toml v1.2.1 // indirect
+	github.com/bytedance/sonic v1.10.1 // indirect
+	github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
+	github.com/chenzhuoyu/iasm v0.9.0 // indirect
+	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
+	github.com/gin-contrib/sse v0.1.0 // indirect
+	github.com/gin-gonic/gin v1.9.1 // 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.15.5 // indirect
+	github.com/goccy/go-json v0.10.2 // indirect
+	github.com/ilyakaznacheev/cleanenv v1.5.0 // indirect
+	github.com/jmoiron/sqlx v1.3.5 // indirect
+	github.com/joho/godotenv v1.5.1 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.5 // indirect
+	github.com/leodido/go-urn v1.2.4 // indirect
+	github.com/mattn/go-isatty v0.0.19 // indirect
+	github.com/mattn/go-sqlite3 v1.14.17 // 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.1.0 // indirect
+	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
+	github.com/ugorji/go/codec v1.2.11 // indirect
+	golang.org/x/arch v0.5.0 // indirect
+	golang.org/x/crypto v0.14.0 // indirect
+	golang.org/x/net v0.16.0 // indirect
+	golang.org/x/sys v0.13.0 // indirect
+	golang.org/x/text v0.13.0 // indirect
+	google.golang.org/protobuf v1.31.0 // indirect
+	gopkg.in/yaml.v3 v3.0.1 // indirect
+	olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
+)
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..7cce769
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,99 @@
+github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
+github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
+github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
+github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
+github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
+github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
+github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
+github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
+github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d/go.mod h1:8EPpVsBuRksnlj1mLy4AWzRNQYxauNi62uWcE3to6eA=
+github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo=
+github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
+github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
+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.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
+github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
+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.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
+github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+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/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+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/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4=
+github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk=
+github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
+github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+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.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
+github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
+github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
+github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
+github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
+github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
+github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
+github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+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/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
+github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+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.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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+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.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
+github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
+golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
+golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+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.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
+google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+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=
+nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
+olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ=
+olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw=
+rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
diff --git a/internal/app/app.go b/internal/app/app.go
new file mode 100644
index 0000000..27c4c25
--- /dev/null
+++ b/internal/app/app.go
@@ -0,0 +1,77 @@
+package app
+
+import (
+	"fmt"
+	"git.miem.hse.ru/hubman/configurator/internal/config"
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/internal/handlers"
+	"git.miem.hse.ru/hubman/configurator/internal/repositories"
+	"git.miem.hse.ru/hubman/configurator/internal/services"
+	"github.com/gin-gonic/gin"
+	"github.com/jmoiron/sqlx"
+	_ "github.com/mattn/go-sqlite3"
+	"log"
+)
+
+type App struct {
+	httpEngine *gin.Engine
+	conf       *config.Config
+}
+
+func NewApp(conf *config.Config) *App {
+	httpEngine := gin.Default()
+	db, err := initDB(conf)
+	if err != nil {
+		log.Fatalln("Can't init DB: ", err)
+	}
+	setupRepository := repositories.NewSqliteSetupRepository(db)
+	manipulatorRepository := repositories.NewSqliteManipulatorRepository(db)
+
+	setupService := services.NewSetupService(setupRepository)
+	manipulatorService := services.NewSetupManipulatorService(setupService, manipulatorRepository)
+
+	setupRouter := handlers.NewSetupRouter(setupService)
+	httpEngine.GET("/setups", setupRouter.GetList)
+	httpEngine.POST("/setups", setupRouter.Post)
+	httpEngine.GET("/setups/:setup_id", setupRouter.Get)
+	httpEngine.PATCH("/setups/:setup_id", setupRouter.Patch)
+	httpEngine.DELETE("/setups/:setup_id", setupRouter.Delete)
+
+	setupManipulatorRouter := handlers.NewSetupManipulatorRouter(manipulatorService)
+	httpEngine.GET("/setups/:setup_id/manipulators", setupManipulatorRouter.GetList)
+	httpEngine.POST("/setups/:setup_id/manipulators", setupManipulatorRouter.Post)
+	httpEngine.GET("/setups/:setup_id/manipulators/:manipulator_id", setupManipulatorRouter.Get)
+	httpEngine.PATCH("/setups/:setup_id/manipulators/:manipulator_id", setupManipulatorRouter.Patch)
+	httpEngine.DELETE("/setups/:setup_id/manipulators/:manipulator_id", setupManipulatorRouter.Delete)
+
+	return &App{
+		conf:       conf,
+		httpEngine: httpEngine,
+	}
+}
+
+func (a *App) Run() {
+	log.Println("Server started")
+	err := a.httpEngine.Run(
+		fmt.Sprintf(":%d", a.conf.Http.Port),
+	)
+	if err != nil {
+		log.Fatalln("Error in htpEngine: ", err)
+	}
+}
+
+func initDB(conf *config.Config) (*sqlx.DB, error) {
+	db, err := sqlx.Connect("sqlite3", conf.Db.Path)
+	if err != nil {
+		return nil, err
+	}
+	_, err = db.Exec(entity.SetupDML)
+	if err != nil {
+		return nil, err
+	}
+	_, err = db.Exec(entity.ManipulatorDML)
+	if err != nil {
+		return nil, err
+	}
+	return db, nil
+}
diff --git a/internal/config/config.go b/internal/config/config.go
new file mode 100644
index 0000000..fc5eb8a
--- /dev/null
+++ b/internal/config/config.go
@@ -0,0 +1,28 @@
+package config
+
+import (
+	"fmt"
+	"github.com/ilyakaznacheev/cleanenv"
+)
+
+type Config struct {
+	Http `yaml:"http"`
+	Db   `yaml:"db""`
+}
+
+type Http struct {
+	Port int `yaml:"port"`
+}
+
+type Db struct {
+	Path string `yaml:"path"`
+}
+
+func NewConfig(path string) (*Config, error) {
+	cfg := &Config{}
+	err := cleanenv.ReadConfig(path, cfg)
+	if err != nil {
+		return nil, fmt.Errorf("config error: %w", err)
+	}
+	return cfg, nil
+}
diff --git a/internal/entity/manipulator.go b/internal/entity/manipulator.go
new file mode 100644
index 0000000..318f88c
--- /dev/null
+++ b/internal/entity/manipulator.go
@@ -0,0 +1,49 @@
+package entity
+
+import (
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"net/url"
+	"regexp"
+)
+
+type Manipulator struct {
+	Id          int     `db:"id" json:"id"`
+	SetupId     int     `db:"setup_id" json:"setup_id"`
+	Name        string  `db:"name" json:"name"`
+	Alias       string  `db:"alias" json:"alias"`
+	Description *string `db:"description" json:"description"`
+	URL         string  `db:"url" json:"url"`
+}
+
+func (m *Manipulator) CheckAlias() error {
+	match, err := regexp.MatchString(`^[A-Za-z_][0-9A-Za-z_].*$`, m.Alias)
+
+	if err != nil || !match {
+		return errs.ErrManipulatorIncorrectParams
+	}
+	return nil
+}
+
+func (m *Manipulator) CheckURL() error {
+	_, err := url.ParseRequestURI(m.URL)
+	if err != nil {
+		return errs.ErrManipulatorIncorrectParams
+	}
+	return nil
+}
+
+const ManipulatorDML = `
+CREATE TABLE IF NOT EXISTS t_manipulator (
+ 	   id INTEGER PRIMARY KEY AUTOINCREMENT,
+ 	   setup_id INTEGER, 
+ 	   name TEXT NOT NULL,
+ 	   alias TEXT NOT NULL,
+ 	   description TEXT,
+ 	   url TEXT,
+ 	   
+ 	   FOREIGN KEY (setup_id) REFERENCES t_setup(id),
+ 	   UNIQUE (setup_id, name),
+       UNIQUE (setup_id, alias),
+       UNIQUE (setup_id, url)
+)
+`
diff --git a/internal/entity/setup.go b/internal/entity/setup.go
new file mode 100644
index 0000000..7755947
--- /dev/null
+++ b/internal/entity/setup.go
@@ -0,0 +1,15 @@
+package entity
+
+type Setup struct {
+	Id          int    `db:"id" json:"id"`
+	Name        string `db:"name" json:"name"`
+	Description string `db:"description" json:"description"`
+}
+
+const SetupDML = `
+CREATE TABLE IF NOT EXISTS t_setup (
+ 	   id INTEGER PRIMARY KEY AUTOINCREMENT,
+ 	   name TEXT NOT NULL UNIQUE,
+ 	   description TEXT
+)
+`
diff --git a/internal/handlers/schemas.go b/internal/handlers/schemas.go
new file mode 100644
index 0000000..5bc4a4a
--- /dev/null
+++ b/internal/handlers/schemas.go
@@ -0,0 +1,18 @@
+package handlers
+
+type SetupPost struct {
+	Name        string `json:"name"`
+	Description string `json:"description"`
+}
+
+type SetupPatch struct {
+	Name        *string `json:"name"`
+	Description *string `json:"description"`
+}
+
+type ManipulatorPost struct {
+	Name        string  `json:"name"`
+	URL         string  `json:"url"`
+	Description *string `json:"description"`
+	Alias       string  `json:"alias"`
+}
diff --git a/internal/handlers/setups_router.go b/internal/handlers/setups_router.go
new file mode 100644
index 0000000..b0a6866
--- /dev/null
+++ b/internal/handlers/setups_router.go
@@ -0,0 +1,79 @@
+package handlers
+
+import (
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/internal/services"
+	"github.com/gin-gonic/gin"
+	"net/http"
+)
+
+type SetupRouter struct {
+	setupService *services.SetupService
+}
+
+func NewSetupRouter(setupService *services.SetupService) *SetupRouter {
+	return &SetupRouter{setupService: setupService}
+}
+
+func (r *SetupRouter) Post(c *gin.Context) {
+	setupPost := &SetupPost{}
+	if err := c.Bind(setupPost); err != nil {
+		return
+	}
+	newSetup := &entity.Setup{
+		Name:        setupPost.Name,
+		Description: setupPost.Description,
+	}
+	if err := r.setupService.Add(newSetup); err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.JSON(http.StatusCreated, newSetup)
+}
+
+func (r *SetupRouter) GetList(c *gin.Context) {
+	setups, err := r.setupService.GetList()
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, setups)
+}
+
+func (r *SetupRouter) Get(c *gin.Context) {
+	setup, err := r.setupService.GetById(
+		c.Params.ByName("setup_id"),
+	)
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, setup)
+}
+
+func (r *SetupRouter) Delete(c *gin.Context) {
+	err := r.setupService.Delete(c.Params.ByName("setup_id"))
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.Status(http.StatusOK)
+}
+
+func (r *SetupRouter) Patch(c *gin.Context) {
+	setupPatch := &SetupPatch{}
+	if err := c.Bind(setupPatch); err != nil {
+		return
+	}
+
+	setup, err := r.setupService.Update(
+		c.Params.ByName("setup_id"),
+		setupPatch.Name,
+		setupPatch.Description,
+	)
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, setup)
+}
diff --git a/internal/handlers/utils.go b/internal/handlers/utils.go
new file mode 100644
index 0000000..3dcc6ec
--- /dev/null
+++ b/internal/handlers/utils.go
@@ -0,0 +1,22 @@
+package handlers
+
+import (
+	"errors"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"github.com/gin-gonic/gin"
+	"net/http"
+)
+
+type ErrorSchema struct {
+	Code string `json:"status"`
+}
+
+func BuildErrResp(c *gin.Context, err error) {
+	if errors.Is(err, errs.ErrNotFound) {
+		c.JSON(http.StatusNotFound, &ErrorSchema{err.Error()})
+	} else if errors.Is(err, errs.ErrIncorrectParams) {
+		c.JSON(http.StatusBadRequest, &ErrorSchema{err.Error()})
+	} else {
+		c.JSON(http.StatusInternalServerError, &ErrorSchema{err.Error()})
+	}
+}
diff --git a/internal/repositories/manipulator_repository.go b/internal/repositories/manipulator_repository.go
new file mode 100644
index 0000000..9e55159
--- /dev/null
+++ b/internal/repositories/manipulator_repository.go
@@ -0,0 +1,83 @@
+package repositories
+
+import (
+	"errors"
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"github.com/jmoiron/sqlx"
+	"github.com/mattn/go-sqlite3"
+)
+
+type ManipulatorRepository interface {
+	GetBySetupId(setupId int) ([]*entity.Manipulator, error)
+	GetById(id int) (*entity.Manipulator, error)
+	Add(manipulator *entity.Manipulator) error
+	//Update(id int, name *string, description *string) error
+	Delete(id int) error
+}
+
+type SqliteManipulatorRepository struct {
+	db *sqlx.DB
+}
+
+func NewSqliteManipulatorRepository(db *sqlx.DB) *SqliteManipulatorRepository {
+	return &SqliteManipulatorRepository{db: db}
+}
+
+func (r SqliteManipulatorRepository) GetBySetupId(setupId int) ([]*entity.Manipulator, error) {
+	manipulators := []*entity.Manipulator{}
+	err := r.db.Select(&manipulators, "SELECT * FROM t_manipulator WHERE setup_id=$1", setupId)
+	if err != nil {
+		return nil, errs.ErrInternalError
+	}
+	return manipulators, nil
+}
+
+func (r SqliteManipulatorRepository) GetById(id int) (*entity.Manipulator, error) {
+	manipulators := []*entity.Manipulator{}
+	err := r.db.Select(&manipulators, "SELECT * FROM t_manipulator WHERE id=$1", id)
+	if err != nil {
+		return nil, errs.ErrInternalError
+	}
+
+	var manipulator *entity.Manipulator
+	if len(manipulators) == 1 {
+		manipulator = manipulators[0]
+	} else {
+		return nil, errs.ErrManipulatorNotFound
+	}
+	return manipulator, nil
+}
+
+func (r SqliteManipulatorRepository) Add(manipulator *entity.Manipulator) error {
+	res, err := r.db.NamedExec(`
+		INSERT INTO t_manipulator(setup_id, name, alias, description, url)
+		VALUES (:setup_id, :name, :alias, :description, :url) RETURNING id`,
+		manipulator,
+	)
+	if err != nil {
+		return wrapSqilteManipulatorError(err)
+	}
+	id, err := res.LastInsertId()
+	if err != nil {
+		return errs.ErrInternalError
+	}
+	manipulator.Id = int(id)
+	return nil
+}
+
+func (r SqliteManipulatorRepository) Delete(id int) error {
+
+	panic("implement me")
+}
+
+func wrapSqilteManipulatorError(err error) error {
+	var sqliteErr sqlite3.Error
+	if errors.As(err, &sqliteErr) {
+		if errors.Is(sqliteErr.Code, sqlite3.ErrConstraint) {
+			return errs.ErrManipulatorIncorrectParams
+		}
+		return errs.ErrInternalError
+	}
+	return err
+}
diff --git a/internal/repositories/setup_repository.go b/internal/repositories/setup_repository.go
new file mode 100644
index 0000000..ce351cb
--- /dev/null
+++ b/internal/repositories/setup_repository.go
@@ -0,0 +1,121 @@
+package repositories
+
+import (
+	"errors"
+	"fmt"
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	querybuilder "git.miem.hse.ru/hubman/configurator/pkg/query-builder"
+	"github.com/jmoiron/sqlx"
+	"github.com/mattn/go-sqlite3"
+)
+
+type SetupRepository interface {
+	GetAll() ([]*entity.Setup, error)
+	GetById(id int) (*entity.Setup, error)
+	Add(setup *entity.Setup) error
+	Update(id int, name *string, description *string) error
+	Delete(id int) error
+}
+
+type SqliteSetupRepository struct {
+	db *sqlx.DB
+}
+
+func NewSqliteSetupRepository(db *sqlx.DB) *SqliteSetupRepository {
+	return &SqliteSetupRepository{db: db}
+}
+
+func (r SqliteSetupRepository) GetAll() ([]*entity.Setup, error) {
+	setups := []*entity.Setup{}
+	err := r.db.Select(&setups, "SELECT * FROM t_setup")
+	if err != nil {
+		return setups, errs.ErrInternalError
+	}
+	return setups, nil
+}
+
+func (r SqliteSetupRepository) GetById(id int) (*entity.Setup, error) {
+	setups := []*entity.Setup{}
+	err := r.db.Select(&setups, "SELECT * FROM t_setup WHERE id=$1", id)
+	if err != nil {
+		return nil, errs.ErrInternalError
+	}
+
+	var setup *entity.Setup
+	if len(setups) == 1 {
+		setup = setups[0]
+	} else {
+		return nil, errs.ErrSetupNotFound
+	}
+	return setup, nil
+}
+
+func (r SqliteSetupRepository) Delete(id int) error {
+	res, err := r.db.Exec("DELETE FROM t_setup WHERE id=$1", id)
+	if err != nil {
+		return errs.ErrInternalError
+	}
+	affected, err := res.RowsAffected()
+	if err != nil {
+		return errs.ErrInternalError
+	}
+	if affected == 0 {
+		return errs.ErrNotFound
+	}
+
+	return nil
+}
+
+func (r SqliteSetupRepository) Add(setup *entity.Setup) error {
+	res, err := r.db.NamedExec(
+		"INSERT INTO t_setup(name, description) VALUES (:name, :description) RETURNING id",
+		setup,
+	)
+	if err != nil {
+		return wrapSqilteSetupError(err)
+	}
+	id, err := res.LastInsertId()
+	if err != nil {
+		return errs.ErrInternalError
+	}
+	setup.Id = int(id)
+	return nil
+}
+
+func (r SqliteSetupRepository) Update(id int, name *string, description *string) error {
+	setClause := querybuilder.BuildUpdateSetClause(
+		querybuilder.AttrValue{"name", name},
+		querybuilder.AttrValue{"description", description},
+	)
+	res, err := r.db.NamedExec(
+		fmt.Sprintf("UPDATE t_setup SET %s WHERE id=:id", setClause),
+		struct {
+			Id          int
+			Name        *string
+			Description *string
+		}{id, name, description},
+	)
+	if err != nil {
+		return wrapSqilteSetupError(err)
+	}
+	affected, err := res.RowsAffected()
+	if err != nil {
+		return errs.ErrInternalError
+	}
+	if affected == 0 {
+		return errs.ErrSetupNotFound
+	}
+	return nil
+}
+
+func wrapSqilteSetupError(err error) error {
+	var sqliteErr sqlite3.Error
+	if errors.As(err, &sqliteErr) {
+		if errors.Is(sqliteErr.Code, sqlite3.ErrConstraint) {
+			return errs.ErrSetupIncorrectParams
+		}
+		return errs.ErrInternalError
+	}
+	return err
+}
diff --git a/internal/repositories/utils.go b/internal/repositories/utils.go
new file mode 100644
index 0000000..3f43206
--- /dev/null
+++ b/internal/repositories/utils.go
@@ -0,0 +1 @@
+package repositories
diff --git a/internal/services/setup_manipulator_service.go b/internal/services/setup_manipulator_service.go
new file mode 100644
index 0000000..a826279
--- /dev/null
+++ b/internal/services/setup_manipulator_service.go
@@ -0,0 +1,70 @@
+package services
+
+import (
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/internal/repositories"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"strconv"
+)
+
+type SetupManipulatorService struct {
+	setupService          *SetupService
+	setupRepository       repositories.SetupRepository
+	manipulatorRepository repositories.ManipulatorRepository
+}
+
+func NewSetupManipulatorService(
+	setupService *SetupService,
+	manipulatorRepository repositories.ManipulatorRepository,
+) *SetupManipulatorService {
+	return &SetupManipulatorService{
+		setupService:          setupService,
+		manipulatorRepository: manipulatorRepository}
+}
+
+func (s *SetupManipulatorService) GetList(setupIdStr string) ([]*entity.Manipulator, error) {
+	setup, err := s.setupService.GetById(setupIdStr)
+	if err != nil {
+		return nil, err
+	}
+	return s.manipulatorRepository.GetBySetupId(setup.Id)
+}
+
+func (s *SetupManipulatorService) Add(
+	setupIdStr string, manipulator *entity.Manipulator,
+) error {
+	setup, err := s.setupService.GetById(setupIdStr)
+	if err != nil {
+		return err
+	}
+	if manipulator == nil {
+		return errs.ErrInternalError
+	}
+	if err := manipulator.CheckAlias(); err != nil {
+		return err
+	}
+	if err := manipulator.CheckURL(); err != nil {
+		return err
+	}
+	manipulator.SetupId = setup.Id
+	return s.manipulatorRepository.Add(manipulator)
+}
+
+func (s *SetupManipulatorService) GetById(setupIdStr, manipulatorIdStr string) (*entity.Manipulator, error) {
+	setupId, err := strconv.Atoi(setupIdStr)
+	if err != nil {
+		return nil, errs.ErrSetupNotFound
+	}
+	manipulatorId, err := strconv.Atoi(manipulatorIdStr)
+	if err != nil {
+		return nil, errs.ErrManipulatorNotFound
+	}
+	manipulator, err := s.manipulatorRepository.GetById(manipulatorId)
+	if err != nil {
+		return nil, err
+	}
+	if manipulator.SetupId != setupId {
+		return nil, errs.ErrManipulatorNotFound
+	}
+	return manipulator, nil
+}
diff --git a/internal/services/setup_service.go b/internal/services/setup_service.go
new file mode 100644
index 0000000..a78e525
--- /dev/null
+++ b/internal/services/setup_service.go
@@ -0,0 +1,55 @@
+package services
+
+import (
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/internal/repositories"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"strconv"
+)
+
+type SetupService struct {
+	r repositories.SetupRepository
+}
+
+func NewSetupService(r repositories.SetupRepository) *SetupService {
+	return &SetupService{r: r}
+}
+
+func (s *SetupService) GetList() ([]*entity.Setup, error) {
+	return s.r.GetAll()
+}
+
+func (s *SetupService) Delete(setupId string) error {
+	id, err := strconv.Atoi(setupId)
+	if err != nil {
+		return errs.ErrSetupNotFound
+	}
+	return s.r.Delete(id)
+}
+
+func (s *SetupService) GetById(setupId string) (*entity.Setup, error) {
+	id, err := strconv.Atoi(setupId)
+	if err != nil {
+		return nil, errs.ErrSetupNotFound
+	}
+	return s.r.GetById(id)
+}
+
+func (s *SetupService) Add(setup *entity.Setup) error {
+	return s.r.Add(setup)
+}
+
+func (s *SetupService) Update(setupId string, name *string, description *string) (*entity.Setup, error) {
+	id, err := strconv.Atoi(setupId)
+	if err != nil {
+		return nil, errs.ErrSetupNotFound
+	}
+
+	if description != nil && name != nil {
+		err := s.r.Update(id, name, description)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return s.r.GetById(id)
+}
diff --git a/pkg/errs/errors.go b/pkg/errs/errors.go
new file mode 100644
index 0000000..643cf9e
--- /dev/null
+++ b/pkg/errs/errors.go
@@ -0,0 +1,19 @@
+package errs
+
+import (
+	"errors"
+	"fmt"
+)
+
+var (
+	ErrNotFound        = errors.New("NOT_FOUND")
+	ErrIncorrectParams = errors.New("INCORRECT_PARAMS")
+
+	ErrInternalError = errors.New("INTERNAL_ERROR")
+
+	ErrSetupNotFound        = fmt.Errorf("SETUP_%w", ErrNotFound)
+	ErrSetupIncorrectParams = fmt.Errorf("SETUP_%w", ErrIncorrectParams)
+
+	ErrManipulatorIncorrectParams = fmt.Errorf("MANIPULATOR_%w", ErrIncorrectParams)
+	ErrManipulatorNotFound        = fmt.Errorf("MANIPULATOR_%w", ErrNotFound)
+)
-- 
GitLab


From d9f9097e5f2a00aa9e58a3b5a40406a5f3c96dc4 Mon Sep 17 00:00:00 2001
From: Sergei Loshkarev <saloshkarev@miem.hse.ru>
Date: Sun, 15 Oct 2023 03:29:26 +0300
Subject: [PATCH 02/12] add logic for device and executor

---
 go.mod                                        |   3 +
 go.sum                                        |   7 +
 internal/app/app.go                           |  46 +++++-
 internal/entity/device.go                     |  63 +++++++
 internal/handlers/setup_executor_router.go    |  69 ++++++++
 internal/handlers/setup_manipulator_router.go |  66 ++++++++
 internal/handlers/setup_util.go               |  42 +++++
 internal/repositories/device_repository.go    | 156 ++++++++++++++++++
 internal/services/device_store_service.go     |  45 +++++
 internal/services/executor_service.go         |  43 +++++
 internal/services/setup_service.go            |   8 +
 pkg/errs/errors.go                            |   3 +
 pkg/query-builder/builder.go                  |  21 +++
 13 files changed, 564 insertions(+), 8 deletions(-)
 create mode 100644 internal/entity/device.go
 create mode 100644 internal/handlers/setup_executor_router.go
 create mode 100644 internal/handlers/setup_manipulator_router.go
 create mode 100644 internal/handlers/setup_util.go
 create mode 100644 internal/repositories/device_repository.go
 create mode 100644 internal/services/device_store_service.go
 create mode 100644 internal/services/executor_service.go
 create mode 100644 pkg/query-builder/builder.go

diff --git a/go.mod b/go.mod
index e842870..25a39b2 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.20
 
 require (
 	github.com/BurntSushi/toml v1.2.1 // indirect
+	github.com/Masterminds/squirrel v1.5.4 // indirect
 	github.com/bytedance/sonic v1.10.1 // indirect
 	github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
 	github.com/chenzhuoyu/iasm v0.9.0 // indirect
@@ -19,6 +20,8 @@ require (
 	github.com/joho/godotenv v1.5.1 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.5 // indirect
+	github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
+	github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
 	github.com/leodido/go-urn v1.2.4 // indirect
 	github.com/mattn/go-isatty v0.0.19 // indirect
 	github.com/mattn/go-sqlite3 v1.14.17 // indirect
diff --git a/go.sum b/go.sum
index 7cce769..84908a2 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,7 @@
 github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
 github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
+github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
 github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
 github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
 github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
@@ -42,6 +44,10 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
 github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
 github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
 github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
+github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
+github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
+github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
+github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
 github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
 github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
@@ -61,6 +67,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
 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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 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=
diff --git a/internal/app/app.go b/internal/app/app.go
index 27c4c25..f7a10d4 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -26,23 +26,49 @@ func NewApp(conf *config.Config) *App {
 	}
 	setupRepository := repositories.NewSqliteSetupRepository(db)
 	manipulatorRepository := repositories.NewSqliteManipulatorRepository(db)
+	deviceRepository := repositories.NewSqliteDeviceRepository(db)
 
 	setupService := services.NewSetupService(setupRepository)
 	manipulatorService := services.NewSetupManipulatorService(setupService, manipulatorRepository)
+	deviceStoreService := services.NewDeviceStoreService(deviceRepository)
+	executorService := services.NewExecutorService(deviceStoreService)
 
 	setupRouter := handlers.NewSetupRouter(setupService)
 	httpEngine.GET("/setups", setupRouter.GetList)
 	httpEngine.POST("/setups", setupRouter.Post)
-	httpEngine.GET("/setups/:setup_id", setupRouter.Get)
-	httpEngine.PATCH("/setups/:setup_id", setupRouter.Patch)
-	httpEngine.DELETE("/setups/:setup_id", setupRouter.Delete)
+
+	setupedNS := httpEngine.Group("/setups/:setup_id")
+	setupedNS.Use(handlers.CheckSetupId(setupService))
+	{
+		setupedNS.GET("/", setupRouter.Get)
+		setupedNS.PATCH("/", setupRouter.Patch)
+		setupedNS.DELETE("/", setupRouter.Delete)
+	}
 
 	setupManipulatorRouter := handlers.NewSetupManipulatorRouter(manipulatorService)
-	httpEngine.GET("/setups/:setup_id/manipulators", setupManipulatorRouter.GetList)
-	httpEngine.POST("/setups/:setup_id/manipulators", setupManipulatorRouter.Post)
-	httpEngine.GET("/setups/:setup_id/manipulators/:manipulator_id", setupManipulatorRouter.Get)
-	httpEngine.PATCH("/setups/:setup_id/manipulators/:manipulator_id", setupManipulatorRouter.Patch)
-	httpEngine.DELETE("/setups/:setup_id/manipulators/:manipulator_id", setupManipulatorRouter.Delete)
+	setupManipulatorNS := httpEngine.Group("/setups/:setup_id/manipulators")
+	setupManipulatorNS.Use(handlers.CheckSetupId(setupService))
+	{
+		setupManipulatorNS.GET("/", setupManipulatorRouter.GetList)
+		//httpEngine.POST("", setupManipulatorRouter.Post)
+		//httpEngine.GET("/:manipulator_id", setupManipulatorRouter.Get)
+		//httpEngine.PATCH("/:manipulator_id", setupManipulatorRouter.Patch)
+		//httpEngine.DELETE("/:manipulator_id", setupManipulatorRouter.Delete)
+	}
+
+	setupExecutorRouter := handlers.NewSetupExecutorRouter(executorService)
+	setupExecutorNS := httpEngine.Group("/setups/:setup_id/executors")
+	setupExecutorNS.Use(handlers.CheckSetupId(setupService))
+	{
+		setupExecutorNS.GET("/", setupExecutorRouter.GetList)
+		setupExecutorNS.POST("/", setupExecutorRouter.Post)
+
+		executoredNS := setupExecutorNS.Group("/:executor_id")
+		executoredNS.Use(handlers.CheckExecutorId(executorService))
+		executoredNS.GET("/", setupExecutorRouter.Get)
+		executoredNS.PATCH("/", setupExecutorRouter.Patch)
+		executoredNS.DELETE("/", setupExecutorRouter.Delete)
+	}
 
 	return &App{
 		conf:       conf,
@@ -73,5 +99,9 @@ func initDB(conf *config.Config) (*sqlx.DB, error) {
 	if err != nil {
 		return nil, err
 	}
+	_, err = db.Exec(entity.DeviceDML)
+	if err != nil {
+		return nil, err
+	}
 	return db, nil
 }
diff --git a/internal/entity/device.go b/internal/entity/device.go
new file mode 100644
index 0000000..0c123d2
--- /dev/null
+++ b/internal/entity/device.go
@@ -0,0 +1,63 @@
+package entity
+
+type DeviceType string
+
+const DeviceTypeManipulator DeviceType = "manipulator"
+const DeviceTypeExecutor DeviceType = "executor"
+
+type Device struct {
+	Id          int        `db:"id" json:"id"`
+	DeviceType  DeviceType `db:"device_type" json:"-"`
+	SetupId     int        `db:"setup_id" json:"setup_id"`
+	Name        string     `db:"name" json:"name"`
+	Alias       string     `db:"alias" json:"alias"`
+	Description *string    `db:"description" json:"description"`
+	URL         string     `db:"url" json:"url"`
+}
+
+type NewDevice struct {
+	Name        string  `db:"name" json:"name"`
+	Alias       string  `db:"alias" json:"alias"`
+	Description *string `db:"description" json:"description"`
+	URL         string  `db:"url" json:"url"`
+}
+
+type UpdDevice struct {
+	Name        *string `db:"name" json:"name"`
+	Alias       *string `db:"alias" json:"alias"`
+	Description *string `db:"description" json:"description"`
+	URL         *string `db:"url" json:"url"`
+}
+
+//	func (m *Manipulator) CheckAlias() error {
+//		match, err := regexp.MatchString(`^[A-Za-z_][0-9A-Za-z_].*$`, m.Alias)
+//
+//		if err != nil || !match {
+//			return errs.ErrManipulatorIncorrectParams
+//		}
+//		return nil
+//	}
+//
+//	func (m *Manipulator) CheckURL() error {
+//		_, err := url.ParseRequestURI(m.URL)
+//		if err != nil {
+//			return errs.ErrManipulatorIncorrectParams
+//		}
+//		return nil
+//	}
+const DeviceDML = `
+CREATE TABLE IF NOT EXISTS Device (
+	   id INTEGER PRIMARY KEY AUTOINCREMENT,
+	   device_type TEXT NOT NULL CHECK (device_type IN ('manipulator', 'executor')),
+	   setup_id INTEGER,
+	   name TEXT NOT NULL,
+	   alias TEXT NOT NULL,
+	   description TEXT,
+	   url TEXT NOT NULL,
+
+	   FOREIGN KEY (setup_id) REFERENCES t_setup(id),
+	   UNIQUE (setup_id, name),
+       UNIQUE (setup_id, alias),
+       UNIQUE (setup_id, device_type, url)
+)
+`
diff --git a/internal/handlers/setup_executor_router.go b/internal/handlers/setup_executor_router.go
new file mode 100644
index 0000000..dca9b86
--- /dev/null
+++ b/internal/handlers/setup_executor_router.go
@@ -0,0 +1,69 @@
+package handlers
+
+import (
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/internal/services"
+	"github.com/gin-gonic/gin"
+	"net/http"
+)
+
+type SetupExecutorRouter struct {
+	executorService *services.ExecutorService
+}
+
+func NewSetupExecutorRouter(executorService *services.ExecutorService) *SetupExecutorRouter {
+	return &SetupExecutorRouter{executorService}
+}
+
+func (r *SetupExecutorRouter) GetList(c *gin.Context) {
+	executors, err := r.executorService.GetList(getSetupId(c))
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, executors)
+}
+
+func (r *SetupExecutorRouter) Post(c *gin.Context) {
+	newDevice := &entity.NewDevice{}
+	if err := c.BindJSON(newDevice); err != nil {
+		return
+	}
+	executor, err := r.executorService.Add(getSetupId(c), newDevice)
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, executor)
+}
+
+func (r *SetupExecutorRouter) Get(c *gin.Context) {
+	executor, err := r.executorService.GetOne(getSetupId(c), getExecutorId(c))
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, executor)
+}
+
+func (r *SetupExecutorRouter) Patch(c *gin.Context) {
+	updDevice := &entity.UpdDevice{}
+	if err := c.BindJSON(updDevice); err != nil {
+		return
+	}
+	executor, err := r.executorService.Update(getSetupId(c), getExecutorId(c), updDevice)
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, executor)
+}
+
+func (r *SetupExecutorRouter) Delete(c *gin.Context) {
+	err := r.executorService.Delete(getSetupId(c), getExecutorId(c))
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.Status(http.StatusOK)
+}
diff --git a/internal/handlers/setup_manipulator_router.go b/internal/handlers/setup_manipulator_router.go
new file mode 100644
index 0000000..9490cba
--- /dev/null
+++ b/internal/handlers/setup_manipulator_router.go
@@ -0,0 +1,66 @@
+package handlers
+
+import (
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/internal/services"
+	"github.com/gin-gonic/gin"
+	"net/http"
+)
+
+type SetupManipulatorRouter struct {
+	manipulatorService *services.SetupManipulatorService
+}
+
+func NewSetupManipulatorRouter(setupService *services.SetupManipulatorService) *SetupManipulatorRouter {
+	return &SetupManipulatorRouter{setupService}
+}
+
+func (r *SetupManipulatorRouter) GetList(c *gin.Context) {
+	manipulators, err := r.manipulatorService.GetList(
+		c.Params.ByName("setup_id"),
+	)
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, manipulators)
+}
+
+func (r *SetupManipulatorRouter) Get(c *gin.Context) {
+	manipulator, err := r.manipulatorService.GetById(
+		c.Params.ByName("setup_id"),
+		c.Params.ByName("manipulator_id"),
+	)
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, manipulator)
+}
+
+func (r *SetupManipulatorRouter) Post(c *gin.Context) {
+	manipulatorPost := &ManipulatorPost{}
+	if err := c.Bind(manipulatorPost); err != nil {
+		return
+	}
+	newManipulator := &entity.Manipulator{
+		Name:        manipulatorPost.Name,
+		Description: manipulatorPost.Description,
+		URL:         manipulatorPost.URL,
+		Alias:       manipulatorPost.Alias,
+	}
+	err := r.manipulatorService.Add(c.Params.ByName("setup_id"), newManipulator)
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.JSON(http.StatusCreated, newManipulator)
+}
+
+func (r *SetupManipulatorRouter) Patch(c *gin.Context) {
+
+}
+
+func (r *SetupManipulatorRouter) Delete(c *gin.Context) {
+
+}
diff --git a/internal/handlers/setup_util.go b/internal/handlers/setup_util.go
new file mode 100644
index 0000000..7f50448
--- /dev/null
+++ b/internal/handlers/setup_util.go
@@ -0,0 +1,42 @@
+package handlers
+
+import (
+	"git.miem.hse.ru/hubman/configurator/internal/services"
+	"github.com/gin-gonic/gin"
+)
+
+func CheckSetupId(setupService *services.SetupService) gin.HandlerFunc {
+	return func(c *gin.Context) {
+		setupId, err := setupService.CheckId(c.Params.ByName("setup_id"))
+		if err != nil {
+			BuildErrResp(c, err)
+			c.Abort()
+			return
+		}
+		//_, err = setupService.GetById(setupId)
+		c.Set("setupId", setupId)
+		c.Next()
+		return
+	}
+}
+
+func getSetupId(c *gin.Context) int {
+	return c.MustGet("setupId").(int)
+}
+
+func CheckExecutorId(executorService *services.ExecutorService) gin.HandlerFunc {
+	return func(c *gin.Context) {
+		executorId, err := executorService.CheckId(c.Params.ByName("executor_id"))
+		if err != nil {
+			BuildErrResp(c, err)
+			c.Abort()
+			return
+		}
+		c.Set("executorId", executorId)
+		return
+	}
+}
+
+func getExecutorId(c *gin.Context) int {
+	return c.MustGet("executorId").(int)
+}
diff --git a/internal/repositories/device_repository.go b/internal/repositories/device_repository.go
new file mode 100644
index 0000000..604779c
--- /dev/null
+++ b/internal/repositories/device_repository.go
@@ -0,0 +1,156 @@
+package repositories
+
+import (
+	"database/sql"
+	"errors"
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	sq "github.com/Masterminds/squirrel"
+	"github.com/jmoiron/sqlx"
+	"github.com/mattn/go-sqlite3"
+	"log"
+)
+
+type DeviceRepository interface {
+	GetBySetupAndType(setupId int, dtype entity.DeviceType) ([]*entity.Device, error)
+	GetOne(setupId int, deviceType entity.DeviceType, id int) (*entity.Device, error)
+	Add(setupId int, deviceType entity.DeviceType, newDevice *entity.NewDevice) (*entity.Device, error)
+	Delete(setupId int, dtype entity.DeviceType, id int) error
+	Update(setupId int, dtype entity.DeviceType, id int, updDevice *entity.UpdDevice) error
+}
+
+type SqliteDeviceRepository struct {
+	db *sqlx.DB
+}
+
+func NewSqliteDeviceRepository(db *sqlx.DB) *SqliteDeviceRepository {
+	return &SqliteDeviceRepository{db: db}
+}
+
+func (r *SqliteDeviceRepository) GetBySetupAndType(setupId int, dtype entity.DeviceType) ([]*entity.Device, error) {
+	query, params := sq.Select("*").From("Device").Where(sq.Eq{
+		"setup_id":    setupId,
+		"device_type": dtype,
+	}).MustSql()
+
+	devices := make([]*entity.Device, 0, 0)
+	err := r.db.Select(&devices, query, params...)
+	if err != nil {
+		return nil, errs.ErrInternalError
+	}
+	return devices, nil
+}
+
+func (r *SqliteDeviceRepository) Add(
+	setupId int, deviceType entity.DeviceType, newDevice *entity.NewDevice,
+) (*entity.Device, error) {
+	res, err := sq.Insert("Device").SetMap(map[string]any{
+		"setup_id":    setupId,
+		"device_type": deviceType,
+		"name":        newDevice.Name,
+		"alias":       newDevice.Alias,
+		"description": newDevice.Description,
+		"url":         newDevice.URL,
+	}).RunWith(r.db).Exec()
+	if err != nil {
+		return nil, wrapSqliteDeviceError(err)
+	}
+
+	id, err := res.LastInsertId()
+	if err != nil {
+		return nil, errs.ErrInternalError
+	}
+	return &entity.Device{
+		Id:          int(id),
+		SetupId:     setupId,
+		DeviceType:  deviceType,
+		Name:        newDevice.Name,
+		Alias:       newDevice.Alias,
+		Description: newDevice.Description,
+		URL:         newDevice.URL,
+	}, nil
+}
+
+func (r *SqliteDeviceRepository) GetOne(setupId int, dtype entity.DeviceType, id int) (*entity.Device, error) {
+	query, params := sq.Select("*").From("Device").Where(sq.Eq{
+		"id":          id,
+		"setup_id":    setupId,
+		"device_type": dtype,
+	}).MustSql()
+	row := r.db.QueryRowx(query, params...)
+	if row == nil || row.Err() != nil {
+		log.Println(row)
+		return nil, wrapSqliteDeviceError(row.Err())
+	}
+	var device entity.Device
+	err := row.StructScan(&device)
+	if err != nil {
+		return nil, wrapSqliteDeviceError(err)
+	}
+	return &device, err
+}
+
+func (r *SqliteDeviceRepository) Update(setupId int, dtype entity.DeviceType, id int, updDevice *entity.UpdDevice) error {
+	setMap := make(map[string]any)
+	if updDevice.Name != nil {
+		setMap["name"] = updDevice.Name
+	}
+	if updDevice.Description != nil {
+		setMap["description"] = updDevice.Description
+	}
+	if updDevice.URL != nil {
+		setMap["url"] = updDevice.URL
+	}
+	if updDevice.Alias != nil {
+		setMap["alias"] = updDevice.Alias
+	}
+	query, params := sq.Update("Device").SetMap(setMap).Where(sq.Eq{
+		"setup_id": setupId, "device_type": dtype, "id": id,
+	}).MustSql()
+	res, err := r.db.Exec(query, params...)
+	if err != nil {
+		return wrapSqliteDeviceError(err)
+	}
+	affected, err := res.RowsAffected()
+	if err != nil {
+		return errs.ErrInternalError
+	}
+	if affected == 0 {
+		return errs.ErrDeviceNotFound
+	}
+	return nil
+}
+
+func (r *SqliteDeviceRepository) Delete(setupId int, dtype entity.DeviceType, id int) error {
+	query, params := sq.Delete("Device").Where(sq.Eq{
+		"setup_id":    setupId,
+		"device_type": dtype,
+		"id":          id,
+	}).MustSql()
+	res, err := r.db.Exec(query, params...)
+	if err != nil {
+		return wrapSqliteDeviceError(err)
+	}
+	affected, err := res.RowsAffected()
+	if err != nil {
+		return errs.ErrInternalError
+	}
+	if affected == 0 {
+		return errs.ErrDeviceNotFound
+	}
+	return nil
+}
+
+func wrapSqliteDeviceError(err error) error {
+	var sqliteErr sqlite3.Error
+	if errors.Is(err, sql.ErrNoRows) {
+		return errs.ErrDeviceNotFound
+	}
+	if errors.As(err, &sqliteErr) {
+		if errors.Is(sqliteErr.Code, sqlite3.ErrConstraint) {
+			return errs.ErrDeviceIncorrectParams
+		}
+		return errs.ErrInternalError
+	}
+	return err
+}
diff --git a/internal/services/device_store_service.go b/internal/services/device_store_service.go
new file mode 100644
index 0000000..de5a019
--- /dev/null
+++ b/internal/services/device_store_service.go
@@ -0,0 +1,45 @@
+package services
+
+import (
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/internal/repositories"
+)
+
+type DeviceStoreService struct {
+	deviceRepository repositories.DeviceRepository
+}
+
+func NewDeviceStoreService(deviceRepository repositories.DeviceRepository) *DeviceStoreService {
+	return &DeviceStoreService{deviceRepository: deviceRepository}
+}
+
+func (s *DeviceStoreService) GetBySetupAndType(setupId int, dtype entity.DeviceType) ([]*entity.Device, error) {
+	return s.deviceRepository.GetBySetupAndType(setupId, dtype)
+}
+
+func (s *DeviceStoreService) Add(
+	setupId int, dtype entity.DeviceType, newDevice *entity.NewDevice,
+) (*entity.Device, error) {
+	return s.deviceRepository.Add(setupId, dtype, newDevice)
+}
+
+func (s *DeviceStoreService) GetOne(
+	setupId int, dtype entity.DeviceType, id int,
+) (*entity.Device, error) {
+	return s.deviceRepository.GetOne(setupId, dtype, id)
+}
+
+func (s *DeviceStoreService) Update(
+	setupId int, dtype entity.DeviceType, id int, updDevice *entity.UpdDevice,
+) (*entity.Device, error) {
+	if err := s.deviceRepository.Update(setupId, dtype, id, updDevice); err != nil {
+		return nil, err
+	}
+	return s.deviceRepository.GetOne(setupId, dtype, id)
+}
+
+func (s *DeviceStoreService) Delete(
+	setupId int, dtype entity.DeviceType, id int,
+) error {
+	return s.deviceRepository.Delete(setupId, dtype, id)
+}
diff --git a/internal/services/executor_service.go b/internal/services/executor_service.go
new file mode 100644
index 0000000..488812b
--- /dev/null
+++ b/internal/services/executor_service.go
@@ -0,0 +1,43 @@
+package services
+
+import (
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"strconv"
+)
+
+type ExecutorService struct {
+	deviceStoreService *DeviceStoreService
+}
+
+func NewExecutorService(deviceStoreService *DeviceStoreService) *ExecutorService {
+	return &ExecutorService{deviceStoreService: deviceStoreService}
+}
+
+func (s *ExecutorService) GetList(setupId int) ([]*entity.Device, error) {
+	return s.deviceStoreService.GetBySetupAndType(setupId, entity.DeviceTypeExecutor)
+}
+
+func (s *ExecutorService) CheckId(ExecutorIdStr string) (int, error) {
+	setupId, err := strconv.Atoi(ExecutorIdStr)
+	if err != nil {
+		return 0, errs.ErrDeviceNotFound
+	}
+	return setupId, nil
+}
+
+func (s *ExecutorService) Add(setupId int, newDevice *entity.NewDevice) (*entity.Device, error) {
+	return s.deviceStoreService.Add(setupId, entity.DeviceTypeExecutor, newDevice)
+}
+
+func (s *ExecutorService) GetOne(setupId int, id int) (*entity.Device, error) {
+	return s.deviceStoreService.GetOne(setupId, entity.DeviceTypeExecutor, id)
+}
+
+func (s *ExecutorService) Update(setupId int, id int, updDevice *entity.UpdDevice) (*entity.Device, error) {
+	return s.deviceStoreService.Update(setupId, entity.DeviceTypeExecutor, id, updDevice)
+}
+
+func (s *ExecutorService) Delete(setupId int, id int) error {
+	return s.deviceStoreService.Delete(setupId, entity.DeviceTypeExecutor, id)
+}
diff --git a/internal/services/setup_service.go b/internal/services/setup_service.go
index a78e525..f006e6a 100644
--- a/internal/services/setup_service.go
+++ b/internal/services/setup_service.go
@@ -53,3 +53,11 @@ func (s *SetupService) Update(setupId string, name *string, description *string)
 	}
 	return s.r.GetById(id)
 }
+
+func (s *SetupService) CheckId(setupIdStr string) (int, error) {
+	setupId, err := strconv.Atoi(setupIdStr)
+	if err != nil {
+		return 0, errs.ErrSetupNotFound
+	}
+	return setupId, nil
+}
diff --git a/pkg/errs/errors.go b/pkg/errs/errors.go
index 643cf9e..0fdbe4a 100644
--- a/pkg/errs/errors.go
+++ b/pkg/errs/errors.go
@@ -14,6 +14,9 @@ var (
 	ErrSetupNotFound        = fmt.Errorf("SETUP_%w", ErrNotFound)
 	ErrSetupIncorrectParams = fmt.Errorf("SETUP_%w", ErrIncorrectParams)
 
+	ErrDeviceIncorrectParams = fmt.Errorf("DEVICE_%w", ErrIncorrectParams)
+	ErrDeviceNotFound        = fmt.Errorf("DEVICE_%w", ErrNotFound)
+
 	ErrManipulatorIncorrectParams = fmt.Errorf("MANIPULATOR_%w", ErrIncorrectParams)
 	ErrManipulatorNotFound        = fmt.Errorf("MANIPULATOR_%w", ErrNotFound)
 )
diff --git a/pkg/query-builder/builder.go b/pkg/query-builder/builder.go
new file mode 100644
index 0000000..bb5ebbb
--- /dev/null
+++ b/pkg/query-builder/builder.go
@@ -0,0 +1,21 @@
+package querybuilder
+
+import (
+	"fmt"
+	"strings"
+)
+
+type AttrValue struct {
+	Attr  string
+	Value *string
+}
+
+func BuildUpdateSetClause(pairs ...AttrValue) string {
+	var usedClauses []string
+	for _, pair := range pairs {
+		if pair.Value != nil {
+			usedClauses = append(usedClauses, fmt.Sprintf("%s=:%s", pair.Attr, pair.Attr))
+		}
+	}
+	return strings.Join(usedClauses, ", ")
+}
-- 
GitLab


From 2a614490b2690a08a9514673def60d2eeaff3bb7 Mon Sep 17 00:00:00 2001
From: Sergei Loshkarev <saloshkarev@miem.hse.ru>
Date: Mon, 16 Oct 2023 02:53:59 +0300
Subject: [PATCH 03/12] add logic for device and executor

---
 internal/adapters/device_adapter.go           | 14 +++
 internal/app/app.go                           | 35 ++++----
 internal/entity/device.go                     | 38 ++++----
 internal/entity/device_descriptor.go          | 22 +++++
 internal/entity/rule.go                       | 54 ++++++++++++
 internal/entity/setup.go                      | 18 +++-
 internal/handlers/schemas.go                  | 18 ----
 internal/handlers/setup_manipulator_router.go | 59 +++++++------
 internal/handlers/setup_util.go               | 17 ++++
 internal/handlers/setups_router.go            | 28 +++---
 .../device_descriptor_repository.go           |  1 +
 internal/repositories/device_repository.go    |  4 +-
 .../repositories/device_status_repository.go  |  1 +
 .../repositories/manipulator_repository.go    | 83 -----------------
 internal/repositories/rule_repository.go      | 18 ++++
 internal/repositories/setup_repository.go     | 88 ++++++++++---------
 internal/services/active_setup_service.go     | 16 ++++
 internal/services/manipulator_service.go      | 43 +++++++++
 internal/services/rule_service.go             | 26 ++++++
 .../services/setup_manipulator_service.go     | 70 ---------------
 internal/services/setup_service.go            | 29 ++----
 pkg/query-builder/builder.go                  | 21 -----
 22 files changed, 360 insertions(+), 343 deletions(-)
 create mode 100644 internal/adapters/device_adapter.go
 create mode 100644 internal/entity/device_descriptor.go
 create mode 100644 internal/entity/rule.go
 delete mode 100644 internal/handlers/schemas.go
 create mode 100644 internal/repositories/device_descriptor_repository.go
 create mode 100644 internal/repositories/device_status_repository.go
 delete mode 100644 internal/repositories/manipulator_repository.go
 create mode 100644 internal/repositories/rule_repository.go
 create mode 100644 internal/services/active_setup_service.go
 create mode 100644 internal/services/manipulator_service.go
 create mode 100644 internal/services/rule_service.go
 delete mode 100644 internal/services/setup_manipulator_service.go
 delete mode 100644 pkg/query-builder/builder.go

diff --git a/internal/adapters/device_adapter.go b/internal/adapters/device_adapter.go
new file mode 100644
index 0000000..19c9627
--- /dev/null
+++ b/internal/adapters/device_adapter.go
@@ -0,0 +1,14 @@
+package adapters
+
+import (
+	"net/url"
+)
+
+type DeviceAdapter struct {
+	deviceUrl url.URL
+}
+
+//func (a *DeviceAdapter) CheckStatus() (Status, err) {
+//	a.deviceUrl.JoinPath("/")
+//	//resp, err := http.Get(a.deviceUrl)
+//}
diff --git a/internal/app/app.go b/internal/app/app.go
index f7a10d4..1f0a319 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -25,12 +25,11 @@ func NewApp(conf *config.Config) *App {
 		log.Fatalln("Can't init DB: ", err)
 	}
 	setupRepository := repositories.NewSqliteSetupRepository(db)
-	manipulatorRepository := repositories.NewSqliteManipulatorRepository(db)
 	deviceRepository := repositories.NewSqliteDeviceRepository(db)
 
 	setupService := services.NewSetupService(setupRepository)
-	manipulatorService := services.NewSetupManipulatorService(setupService, manipulatorRepository)
 	deviceStoreService := services.NewDeviceStoreService(deviceRepository)
+	manipulatorService := services.NewManipulatorService(deviceStoreService)
 	executorService := services.NewExecutorService(deviceStoreService)
 
 	setupRouter := handlers.NewSetupRouter(setupService)
@@ -50,10 +49,13 @@ func NewApp(conf *config.Config) *App {
 	setupManipulatorNS.Use(handlers.CheckSetupId(setupService))
 	{
 		setupManipulatorNS.GET("/", setupManipulatorRouter.GetList)
-		//httpEngine.POST("", setupManipulatorRouter.Post)
-		//httpEngine.GET("/:manipulator_id", setupManipulatorRouter.Get)
-		//httpEngine.PATCH("/:manipulator_id", setupManipulatorRouter.Patch)
-		//httpEngine.DELETE("/:manipulator_id", setupManipulatorRouter.Delete)
+		setupManipulatorNS.POST("/", setupManipulatorRouter.Post)
+
+		manipulatoredNS := setupManipulatorNS.Group("/:manipuator_id")
+		manipulatoredNS.Use(handlers.CheckManipulatorId(executorService))
+		manipulatoredNS.GET("/", setupManipulatorRouter.Get)
+		manipulatoredNS.PATCH("/", setupManipulatorRouter.Patch)
+		manipulatoredNS.DELETE("/", setupManipulatorRouter.Delete)
 	}
 
 	setupExecutorRouter := handlers.NewSetupExecutorRouter(executorService)
@@ -88,20 +90,13 @@ func (a *App) Run() {
 
 func initDB(conf *config.Config) (*sqlx.DB, error) {
 	db, err := sqlx.Connect("sqlite3", conf.Db.Path)
-	if err != nil {
-		return nil, err
-	}
-	_, err = db.Exec(entity.SetupDML)
-	if err != nil {
-		return nil, err
-	}
-	_, err = db.Exec(entity.ManipulatorDML)
-	if err != nil {
-		return nil, err
-	}
-	_, err = db.Exec(entity.DeviceDML)
-	if err != nil {
-		return nil, err
+	for _, dmlString := range []string{
+		entity.SetupDML, entity.DeviceDML, entity.RuleDML,
+	} {
+		_, err = db.Exec(dmlString)
+		if err != nil {
+			return nil, err
+		}
 	}
 	return db, nil
 }
diff --git a/internal/entity/device.go b/internal/entity/device.go
index 0c123d2..1736e28 100644
--- a/internal/entity/device.go
+++ b/internal/entity/device.go
@@ -1,5 +1,11 @@
 package entity
 
+import (
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"net/url"
+	"regexp"
+)
+
 type DeviceType string
 
 const DeviceTypeManipulator DeviceType = "manipulator"
@@ -29,22 +35,22 @@ type UpdDevice struct {
 	URL         *string `db:"url" json:"url"`
 }
 
-//	func (m *Manipulator) CheckAlias() error {
-//		match, err := regexp.MatchString(`^[A-Za-z_][0-9A-Za-z_].*$`, m.Alias)
-//
-//		if err != nil || !match {
-//			return errs.ErrManipulatorIncorrectParams
-//		}
-//		return nil
-//	}
-//
-//	func (m *Manipulator) CheckURL() error {
-//		_, err := url.ParseRequestURI(m.URL)
-//		if err != nil {
-//			return errs.ErrManipulatorIncorrectParams
-//		}
-//		return nil
-//	}
+func (m *Device) CheckAlias() error {
+	match, err := regexp.MatchString(`^[A-Za-z_][0-9A-Za-z_].*$`, m.Alias)
+	if err != nil || !match {
+		return errs.ErrManipulatorIncorrectParams
+	}
+	return nil
+}
+
+func (m *Device) CheckURL() error {
+	_, err := url.ParseRequestURI(m.URL)
+	if err != nil {
+		return errs.ErrManipulatorIncorrectParams
+	}
+	return nil
+}
+
 const DeviceDML = `
 CREATE TABLE IF NOT EXISTS Device (
 	   id INTEGER PRIMARY KEY AUTOINCREMENT,
diff --git a/internal/entity/device_descriptor.go b/internal/entity/device_descriptor.go
new file mode 100644
index 0000000..a534366
--- /dev/null
+++ b/internal/entity/device_descriptor.go
@@ -0,0 +1,22 @@
+package entity
+
+type DeviceDescriptor struct {
+	Id          int    `db:"id" json:"id"`
+	DeviceId    int    `db:"device_id" json:"device_id"`
+	Code        string `db:"code" json:"code"`
+	Description string `db:"description" json:"description"`
+	Schema      string `db:"descriptor_schema" json:"schema"`
+}
+
+const DeviceDescriptorDML = `
+	CREATE TABLE IF NOT EXISTS DeviceDescriptor (
+	   id INTEGER PRIMARY KEY AUTOINCREMENT,
+	   device_id INTEGER NOT NULL,
+	   code TEXT NOT NULL,
+	   description TEXT NOT NULL,	   
+	   descriptor_schema TEXT NOT NULL CHECK ( json(descriptor_schema) ),
+
+	   FOREIGN KEY (device_id) REFERENCES Device(id),
+	   UNIQUE (code, device_id)
+)
+`
diff --git a/internal/entity/rule.go b/internal/entity/rule.go
new file mode 100644
index 0000000..88e8a8a
--- /dev/null
+++ b/internal/entity/rule.go
@@ -0,0 +1,54 @@
+package entity
+
+type Rule struct {
+	Id                  int      `db:"id" json:"id"`
+	Name                string   `db:"name" json:"name"`
+	SetupId             int      `db:"setup_id" json:"setup_id"`
+	ManipulatorId       int      `db:"manipulator_id" json:"manipulator_id"`
+	SignalDescriptorId  int      `db:"signal_descriptor_id" json:"signal_descriptor_id"`
+	ExecutorId          int      `db:"executor_id" json:"executor_id"`
+	CommandDescriptorId int      `db:"command_descriptor_id" json:"command_descriptor_id"`
+	Trigger             string   `db:"trigger" json:"trigger"`
+	Transofrms          []string `db:"transforms" json:"transforms"`
+}
+
+type NewRule struct {
+	Name                string   `db:"name" json:"name"`
+	ManipulatorId       int      `db:"manipulator_id" json:"manipulator_id"`
+	SignalDescriptorId  int      `db:"signal_descriptor_id" json:"signal_descriptor_id"`
+	ExecutorId          int      `db:"executor_id" json:"executor_id"`
+	CommandDescriptorId int      `db:"command_descriptor_id" json:"command_descriptor_id"`
+	Trigger             string   `db:"trigger" json:"trigger"`
+	Transofrms          []string `db:"transforms" json:"transforms"`
+}
+
+type UpdRule struct {
+	Name                *string   `db:"name" json:"name"`
+	ManipulatorId       *int      `db:"manipulator_id" json:"manipulator_id"`
+	SignalDescriptorId  *int      `db:"signal_descriptor_id" json:"signal_descriptor_id"`
+	ExecutorId          *int      `db:"executor_id" json:"executor_id"`
+	CommandDescriptorId *int      `db:"command_descriptor_id" json:"command_descriptor_id"`
+	Trigger             *string   `db:"trigger" json:"trigger"`
+	Transofrms          *[]string `db:"transforms" json:"transforms"`
+}
+
+const RuleDML = `
+	CREATE TABLE IF NOT EXISTS Rule (
+	   id INTEGER PRIMARY KEY AUTOINCREMENT,
+	   name TEXT NOT NULL,
+	   setup_id INTEGER NOT NULL,
+	   manipulator_id INTEGER NOT NULL,
+	   signal_descriptor_id INTEGER NOT NULL,
+	   executor_id INTEGER NOT NULL,
+	   command_descriptor_id INTEGER NOT NULL,
+	   trigger TEXT NOT NULL CHECK ( json(trigger) ),
+	   transofrms TEXT NOT NULL CHECK ( json_array(transofrms) ),
+
+	   FOREIGN KEY (setup_id) REFERENCES t_setup(id),
+	   FOREIGN KEY (manipulator_id) REFERENCES Device(id),
+	   FOREIGN KEY (executor_id) REFERENCES Device(id),
+	   FOREIGN KEY (executor_id, command_descriptor_id) REFERENCES DeviceDescriptor(device_id, id),
+	   FOREIGN KEY (manipulator_id, signal_descriptor_id) REFERENCES DeviceDescriptor(device_id, id),
+	   UNIQUE (setup_id, name)
+)
+`
diff --git a/internal/entity/setup.go b/internal/entity/setup.go
index 7755947..722e320 100644
--- a/internal/entity/setup.go
+++ b/internal/entity/setup.go
@@ -1,13 +1,23 @@
 package entity
 
 type Setup struct {
-	Id          int    `db:"id" json:"id"`
-	Name        string `db:"name" json:"name"`
-	Description string `db:"description" json:"description"`
+	Id          int     `db:"id" json:"id"`
+	Name        string  `db:"name" json:"name"`
+	Description *string `db:"description" json:"description"`
+}
+
+type NewSetup struct {
+	Name        string  `db:"name" json:"name"`
+	Description *string `db:"description" json:"description"`
+}
+
+type UpdSetup struct {
+	Name        *string `db:"name" json:"name"`
+	Description *string `db:"description" json:"description"`
 }
 
 const SetupDML = `
-CREATE TABLE IF NOT EXISTS t_setup (
+CREATE TABLE IF NOT EXISTS Setup (
  	   id INTEGER PRIMARY KEY AUTOINCREMENT,
  	   name TEXT NOT NULL UNIQUE,
  	   description TEXT
diff --git a/internal/handlers/schemas.go b/internal/handlers/schemas.go
deleted file mode 100644
index 5bc4a4a..0000000
--- a/internal/handlers/schemas.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package handlers
-
-type SetupPost struct {
-	Name        string `json:"name"`
-	Description string `json:"description"`
-}
-
-type SetupPatch struct {
-	Name        *string `json:"name"`
-	Description *string `json:"description"`
-}
-
-type ManipulatorPost struct {
-	Name        string  `json:"name"`
-	URL         string  `json:"url"`
-	Description *string `json:"description"`
-	Alias       string  `json:"alias"`
-}
diff --git a/internal/handlers/setup_manipulator_router.go b/internal/handlers/setup_manipulator_router.go
index 9490cba..c8706db 100644
--- a/internal/handlers/setup_manipulator_router.go
+++ b/internal/handlers/setup_manipulator_router.go
@@ -8,59 +8,62 @@ import (
 )
 
 type SetupManipulatorRouter struct {
-	manipulatorService *services.SetupManipulatorService
+	manipulatorService *services.ExecutorService
 }
 
-func NewSetupManipulatorRouter(setupService *services.SetupManipulatorService) *SetupManipulatorRouter {
-	return &SetupManipulatorRouter{setupService}
+func NewSetupManipulatorRouter(executorService *services.ExecutorService) *SetupManipulatorRouter {
+	return &SetupManipulatorRouter{executorService}
 }
 
 func (r *SetupManipulatorRouter) GetList(c *gin.Context) {
-	manipulators, err := r.manipulatorService.GetList(
-		c.Params.ByName("setup_id"),
-	)
+	executors, err := r.manipulatorService.GetList(getSetupId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
 	}
-	c.JSON(http.StatusOK, manipulators)
+	c.JSON(http.StatusOK, executors)
 }
 
-func (r *SetupManipulatorRouter) Get(c *gin.Context) {
-	manipulator, err := r.manipulatorService.GetById(
-		c.Params.ByName("setup_id"),
-		c.Params.ByName("manipulator_id"),
-	)
+func (r *SetupManipulatorRouter) Post(c *gin.Context) {
+	newDevice := &entity.NewDevice{}
+	if err := c.BindJSON(newDevice); err != nil {
+		return
+	}
+	executor, err := r.manipulatorService.Add(getSetupId(c), newDevice)
 	if err != nil {
 		BuildErrResp(c, err)
 		return
 	}
-	c.JSON(http.StatusOK, manipulator)
+	c.JSON(http.StatusOK, executor)
 }
 
-func (r *SetupManipulatorRouter) Post(c *gin.Context) {
-	manipulatorPost := &ManipulatorPost{}
-	if err := c.Bind(manipulatorPost); err != nil {
-		return
-	}
-	newManipulator := &entity.Manipulator{
-		Name:        manipulatorPost.Name,
-		Description: manipulatorPost.Description,
-		URL:         manipulatorPost.URL,
-		Alias:       manipulatorPost.Alias,
-	}
-	err := r.manipulatorService.Add(c.Params.ByName("setup_id"), newManipulator)
+func (r *SetupManipulatorRouter) Get(c *gin.Context) {
+	executor, err := r.manipulatorService.GetOne(getSetupId(c), GetManipulatorId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
 	}
-	c.JSON(http.StatusCreated, newManipulator)
+	c.JSON(http.StatusOK, executor)
 }
 
 func (r *SetupManipulatorRouter) Patch(c *gin.Context) {
-
+	updDevice := &entity.UpdDevice{}
+	if err := c.BindJSON(updDevice); err != nil {
+		return
+	}
+	executor, err := r.manipulatorService.Update(getSetupId(c), GetManipulatorId(c), updDevice)
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, executor)
 }
 
 func (r *SetupManipulatorRouter) Delete(c *gin.Context) {
-
+	err := r.manipulatorService.Delete(getSetupId(c), GetManipulatorId(c))
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.Status(http.StatusOK)
 }
diff --git a/internal/handlers/setup_util.go b/internal/handlers/setup_util.go
index 7f50448..da519dd 100644
--- a/internal/handlers/setup_util.go
+++ b/internal/handlers/setup_util.go
@@ -40,3 +40,20 @@ func CheckExecutorId(executorService *services.ExecutorService) gin.HandlerFunc
 func getExecutorId(c *gin.Context) int {
 	return c.MustGet("executorId").(int)
 }
+
+func CheckManipulatorId(executorService *services.ExecutorService) gin.HandlerFunc {
+	return func(c *gin.Context) {
+		executorId, err := executorService.CheckId(c.Params.ByName("manipulator_id"))
+		if err != nil {
+			BuildErrResp(c, err)
+			c.Abort()
+			return
+		}
+		c.Set("manipulatorId", executorId)
+		return
+	}
+}
+
+func GetManipulatorId(c *gin.Context) int {
+	return c.MustGet("manipulatorId").(int)
+}
diff --git a/internal/handlers/setups_router.go b/internal/handlers/setups_router.go
index b0a6866..63e32a8 100644
--- a/internal/handlers/setups_router.go
+++ b/internal/handlers/setups_router.go
@@ -16,19 +16,16 @@ func NewSetupRouter(setupService *services.SetupService) *SetupRouter {
 }
 
 func (r *SetupRouter) Post(c *gin.Context) {
-	setupPost := &SetupPost{}
-	if err := c.Bind(setupPost); err != nil {
+	newSetup := &entity.NewSetup{}
+	if err := c.BindJSON(newSetup); err != nil {
 		return
 	}
-	newSetup := &entity.Setup{
-		Name:        setupPost.Name,
-		Description: setupPost.Description,
-	}
-	if err := r.setupService.Add(newSetup); err != nil {
+	setup, err := r.setupService.Add(newSetup)
+	if err != nil {
 		BuildErrResp(c, err)
 		return
 	}
-	c.JSON(http.StatusCreated, newSetup)
+	c.JSON(http.StatusCreated, setup)
 }
 
 func (r *SetupRouter) GetList(c *gin.Context) {
@@ -41,9 +38,7 @@ func (r *SetupRouter) GetList(c *gin.Context) {
 }
 
 func (r *SetupRouter) Get(c *gin.Context) {
-	setup, err := r.setupService.GetById(
-		c.Params.ByName("setup_id"),
-	)
+	setup, err := r.setupService.GetById(getSetupId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
@@ -52,7 +47,7 @@ func (r *SetupRouter) Get(c *gin.Context) {
 }
 
 func (r *SetupRouter) Delete(c *gin.Context) {
-	err := r.setupService.Delete(c.Params.ByName("setup_id"))
+	err := r.setupService.Delete(getSetupId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
@@ -61,15 +56,12 @@ func (r *SetupRouter) Delete(c *gin.Context) {
 }
 
 func (r *SetupRouter) Patch(c *gin.Context) {
-	setupPatch := &SetupPatch{}
-	if err := c.Bind(setupPatch); err != nil {
+	updSetup := &entity.UpdSetup{}
+	if err := c.BindJSON(updSetup); err != nil {
 		return
 	}
-
 	setup, err := r.setupService.Update(
-		c.Params.ByName("setup_id"),
-		setupPatch.Name,
-		setupPatch.Description,
+		getSetupId(c), updSetup,
 	)
 	if err != nil {
 		BuildErrResp(c, err)
diff --git a/internal/repositories/device_descriptor_repository.go b/internal/repositories/device_descriptor_repository.go
new file mode 100644
index 0000000..3f43206
--- /dev/null
+++ b/internal/repositories/device_descriptor_repository.go
@@ -0,0 +1 @@
+package repositories
diff --git a/internal/repositories/device_repository.go b/internal/repositories/device_repository.go
index 604779c..df2f92e 100644
--- a/internal/repositories/device_repository.go
+++ b/internal/repositories/device_repository.go
@@ -8,7 +8,6 @@ import (
 	sq "github.com/Masterminds/squirrel"
 	"github.com/jmoiron/sqlx"
 	"github.com/mattn/go-sqlite3"
-	"log"
 )
 
 type DeviceRepository interface {
@@ -79,7 +78,6 @@ func (r *SqliteDeviceRepository) GetOne(setupId int, dtype entity.DeviceType, id
 	}).MustSql()
 	row := r.db.QueryRowx(query, params...)
 	if row == nil || row.Err() != nil {
-		log.Println(row)
 		return nil, wrapSqliteDeviceError(row.Err())
 	}
 	var device entity.Device
@@ -142,10 +140,10 @@ func (r *SqliteDeviceRepository) Delete(setupId int, dtype entity.DeviceType, id
 }
 
 func wrapSqliteDeviceError(err error) error {
-	var sqliteErr sqlite3.Error
 	if errors.Is(err, sql.ErrNoRows) {
 		return errs.ErrDeviceNotFound
 	}
+	var sqliteErr sqlite3.Error
 	if errors.As(err, &sqliteErr) {
 		if errors.Is(sqliteErr.Code, sqlite3.ErrConstraint) {
 			return errs.ErrDeviceIncorrectParams
diff --git a/internal/repositories/device_status_repository.go b/internal/repositories/device_status_repository.go
new file mode 100644
index 0000000..3f43206
--- /dev/null
+++ b/internal/repositories/device_status_repository.go
@@ -0,0 +1 @@
+package repositories
diff --git a/internal/repositories/manipulator_repository.go b/internal/repositories/manipulator_repository.go
deleted file mode 100644
index 9e55159..0000000
--- a/internal/repositories/manipulator_repository.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package repositories
-
-import (
-	"errors"
-	"git.miem.hse.ru/hubman/configurator/internal/entity"
-	"git.miem.hse.ru/hubman/configurator/pkg/errs"
-	"github.com/jmoiron/sqlx"
-	"github.com/mattn/go-sqlite3"
-)
-
-type ManipulatorRepository interface {
-	GetBySetupId(setupId int) ([]*entity.Manipulator, error)
-	GetById(id int) (*entity.Manipulator, error)
-	Add(manipulator *entity.Manipulator) error
-	//Update(id int, name *string, description *string) error
-	Delete(id int) error
-}
-
-type SqliteManipulatorRepository struct {
-	db *sqlx.DB
-}
-
-func NewSqliteManipulatorRepository(db *sqlx.DB) *SqliteManipulatorRepository {
-	return &SqliteManipulatorRepository{db: db}
-}
-
-func (r SqliteManipulatorRepository) GetBySetupId(setupId int) ([]*entity.Manipulator, error) {
-	manipulators := []*entity.Manipulator{}
-	err := r.db.Select(&manipulators, "SELECT * FROM t_manipulator WHERE setup_id=$1", setupId)
-	if err != nil {
-		return nil, errs.ErrInternalError
-	}
-	return manipulators, nil
-}
-
-func (r SqliteManipulatorRepository) GetById(id int) (*entity.Manipulator, error) {
-	manipulators := []*entity.Manipulator{}
-	err := r.db.Select(&manipulators, "SELECT * FROM t_manipulator WHERE id=$1", id)
-	if err != nil {
-		return nil, errs.ErrInternalError
-	}
-
-	var manipulator *entity.Manipulator
-	if len(manipulators) == 1 {
-		manipulator = manipulators[0]
-	} else {
-		return nil, errs.ErrManipulatorNotFound
-	}
-	return manipulator, nil
-}
-
-func (r SqliteManipulatorRepository) Add(manipulator *entity.Manipulator) error {
-	res, err := r.db.NamedExec(`
-		INSERT INTO t_manipulator(setup_id, name, alias, description, url)
-		VALUES (:setup_id, :name, :alias, :description, :url) RETURNING id`,
-		manipulator,
-	)
-	if err != nil {
-		return wrapSqilteManipulatorError(err)
-	}
-	id, err := res.LastInsertId()
-	if err != nil {
-		return errs.ErrInternalError
-	}
-	manipulator.Id = int(id)
-	return nil
-}
-
-func (r SqliteManipulatorRepository) Delete(id int) error {
-
-	panic("implement me")
-}
-
-func wrapSqilteManipulatorError(err error) error {
-	var sqliteErr sqlite3.Error
-	if errors.As(err, &sqliteErr) {
-		if errors.Is(sqliteErr.Code, sqlite3.ErrConstraint) {
-			return errs.ErrManipulatorIncorrectParams
-		}
-		return errs.ErrInternalError
-	}
-	return err
-}
diff --git a/internal/repositories/rule_repository.go b/internal/repositories/rule_repository.go
new file mode 100644
index 0000000..cdbe341
--- /dev/null
+++ b/internal/repositories/rule_repository.go
@@ -0,0 +1,18 @@
+package repositories
+
+import (
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"github.com/jmoiron/sqlx"
+)
+
+type RuleRepository interface {
+	GetBySetupId(setupId int) ([]*entity.Rule, error)
+	GetById(setupId int, id int) (*entity.Rule, error)
+	Add(setupId int, rule *entity.NewRule) (*entity.Rule, error)
+	Update(setupId int, id int, updSetup *entity.UpdRule) error
+	Delete(setupId int, id int) error
+}
+
+type SqliteRuleRepository struct {
+	db *sqlx.DB
+}
diff --git a/internal/repositories/setup_repository.go b/internal/repositories/setup_repository.go
index ce351cb..65257af 100644
--- a/internal/repositories/setup_repository.go
+++ b/internal/repositories/setup_repository.go
@@ -1,11 +1,11 @@
 package repositories
 
 import (
+	"database/sql"
 	"errors"
-	"fmt"
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
 	"git.miem.hse.ru/hubman/configurator/pkg/errs"
-	querybuilder "git.miem.hse.ru/hubman/configurator/pkg/query-builder"
+	sq "github.com/Masterminds/squirrel"
 	"github.com/jmoiron/sqlx"
 	"github.com/mattn/go-sqlite3"
 )
@@ -13,8 +13,8 @@ import (
 type SetupRepository interface {
 	GetAll() ([]*entity.Setup, error)
 	GetById(id int) (*entity.Setup, error)
-	Add(setup *entity.Setup) error
-	Update(id int, name *string, description *string) error
+	Add(setup *entity.NewSetup) (*entity.Setup, error)
+	Update(id int, updSetup *entity.UpdSetup) error
 	Delete(id int) error
 }
 
@@ -27,8 +27,9 @@ func NewSqliteSetupRepository(db *sqlx.DB) *SqliteSetupRepository {
 }
 
 func (r SqliteSetupRepository) GetAll() ([]*entity.Setup, error) {
-	setups := []*entity.Setup{}
-	err := r.db.Select(&setups, "SELECT * FROM t_setup")
+	query, params := sq.Select("*").From("Setup").MustSql()
+	setups := make([]*entity.Setup, 0, 0)
+	err := r.db.Select(&setups, query, params...)
 	if err != nil {
 		return setups, errs.ErrInternalError
 	}
@@ -36,23 +37,24 @@ func (r SqliteSetupRepository) GetAll() ([]*entity.Setup, error) {
 }
 
 func (r SqliteSetupRepository) GetById(id int) (*entity.Setup, error) {
-	setups := []*entity.Setup{}
-	err := r.db.Select(&setups, "SELECT * FROM t_setup WHERE id=$1", id)
-	if err != nil {
-		return nil, errs.ErrInternalError
+	query, params := sq.Select("*").From("Setup").Where(sq.Eq{"id": id}).MustSql()
+
+	row := r.db.QueryRowx(query, params...)
+	if row == nil || row.Err() != nil {
+		return nil, wrapSqliteSetupError(row.Err())
 	}
 
 	var setup *entity.Setup
-	if len(setups) == 1 {
-		setup = setups[0]
-	} else {
-		return nil, errs.ErrSetupNotFound
+	err := row.StructScan(&setup)
+	if err != nil {
+		return nil, errs.ErrInternalError
 	}
 	return setup, nil
 }
 
 func (r SqliteSetupRepository) Delete(id int) error {
-	res, err := r.db.Exec("DELETE FROM t_setup WHERE id=$1", id)
+	query, params := sq.Delete("Setup").Where(sq.Eq{"id": id}).MustSql()
+	res, err := r.db.Exec(query, params...)
 	if err != nil {
 		return errs.ErrInternalError
 	}
@@ -61,43 +63,45 @@ func (r SqliteSetupRepository) Delete(id int) error {
 		return errs.ErrInternalError
 	}
 	if affected == 0 {
-		return errs.ErrNotFound
+		return errs.ErrSetupNotFound
 	}
 
 	return nil
 }
 
-func (r SqliteSetupRepository) Add(setup *entity.Setup) error {
-	res, err := r.db.NamedExec(
-		"INSERT INTO t_setup(name, description) VALUES (:name, :description) RETURNING id",
-		setup,
-	)
+func (r SqliteSetupRepository) Add(newSetup *entity.NewSetup) (*entity.Setup, error) {
+	query, param := sq.Insert("Setup").SetMap(map[string]any{
+		"name": newSetup.Name, "description": newSetup.Description,
+	}).MustSql()
+	res, err := r.db.Exec(query, param...)
 	if err != nil {
-		return wrapSqilteSetupError(err)
+		return nil, wrapSqliteSetupError(err)
 	}
 	id, err := res.LastInsertId()
 	if err != nil {
-		return errs.ErrInternalError
+		return nil, wrapSqliteSetupError(err)
 	}
-	setup.Id = int(id)
-	return nil
+
+	return &entity.Setup{
+		Id:          int(id),
+		Name:        newSetup.Name,
+		Description: newSetup.Description,
+	}, nil
 }
 
-func (r SqliteSetupRepository) Update(id int, name *string, description *string) error {
-	setClause := querybuilder.BuildUpdateSetClause(
-		querybuilder.AttrValue{"name", name},
-		querybuilder.AttrValue{"description", description},
-	)
-	res, err := r.db.NamedExec(
-		fmt.Sprintf("UPDATE t_setup SET %s WHERE id=:id", setClause),
-		struct {
-			Id          int
-			Name        *string
-			Description *string
-		}{id, name, description},
-	)
+func (r SqliteSetupRepository) Update(id int, updSetup *entity.UpdSetup) error {
+	updMap := make(map[string]any)
+	if updSetup.Name != nil {
+		updMap["name"] = updSetup.Name
+	}
+	if updSetup.Name != nil {
+		updMap["description"] = updSetup.Description
+	}
+
+	query, params := sq.Update("Setup").Where(sq.Eq{"id": id}).SetMap(updMap).MustSql()
+	res, err := r.db.Exec(query, params...)
 	if err != nil {
-		return wrapSqilteSetupError(err)
+		return wrapSqliteSetupError(err)
 	}
 	affected, err := res.RowsAffected()
 	if err != nil {
@@ -109,7 +113,11 @@ func (r SqliteSetupRepository) Update(id int, name *string, description *string)
 	return nil
 }
 
-func wrapSqilteSetupError(err error) error {
+func wrapSqliteSetupError(err error) error {
+	if errors.Is(err, sql.ErrNoRows) {
+		return errs.ErrSetupNotFound
+	}
+
 	var sqliteErr sqlite3.Error
 	if errors.As(err, &sqliteErr) {
 		if errors.Is(sqliteErr.Code, sqlite3.ErrConstraint) {
diff --git a/internal/services/active_setup_service.go b/internal/services/active_setup_service.go
new file mode 100644
index 0000000..76490f2
--- /dev/null
+++ b/internal/services/active_setup_service.go
@@ -0,0 +1,16 @@
+package services
+
+import "context"
+
+type ActiveSetupService struct {
+}
+
+func (s *ActiveSetupService) RunDeviceChecker(ctx *context.Context) error {
+	_ = ctx
+	return nil
+	// DeviceStautsService.GetStatusesOrderByUpdate()
+	//
+	// DeviceStatusService.CheckStatus + manual check alive
+	// RuleService.BuildBindingForExecutor()
+	// Device.UpdateExecutorBinding()
+}
diff --git a/internal/services/manipulator_service.go b/internal/services/manipulator_service.go
new file mode 100644
index 0000000..06ee665
--- /dev/null
+++ b/internal/services/manipulator_service.go
@@ -0,0 +1,43 @@
+package services
+
+import (
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"strconv"
+)
+
+type ManipulatorService struct {
+	deviceStoreService *DeviceStoreService
+}
+
+func NewManipulatorService(deviceStoreService *DeviceStoreService) *ExecutorService {
+	return &ExecutorService{deviceStoreService: deviceStoreService}
+}
+
+func (s *ManipulatorService) GetList(setupId int) ([]*entity.Device, error) {
+	return s.deviceStoreService.GetBySetupAndType(setupId, entity.DeviceTypeManipulator)
+}
+
+func (s *ManipulatorService) CheckId(ExecutorIdStr string) (int, error) {
+	setupId, err := strconv.Atoi(ExecutorIdStr)
+	if err != nil {
+		return 0, errs.ErrDeviceNotFound
+	}
+	return setupId, nil
+}
+
+func (s *ManipulatorService) Add(setupId int, newDevice *entity.NewDevice) (*entity.Device, error) {
+	return s.deviceStoreService.Add(setupId, entity.DeviceTypeManipulator, newDevice)
+}
+
+func (s *ManipulatorService) GetOne(setupId int, id int) (*entity.Device, error) {
+	return s.deviceStoreService.GetOne(setupId, entity.DeviceTypeManipulator, id)
+}
+
+func (s *ManipulatorService) Update(setupId int, id int, updDevice *entity.UpdDevice) (*entity.Device, error) {
+	return s.deviceStoreService.Update(setupId, entity.DeviceTypeManipulator, id, updDevice)
+}
+
+func (s *ManipulatorService) Delete(setupId int, id int) error {
+	return s.deviceStoreService.Delete(setupId, entity.DeviceTypeManipulator, id)
+}
diff --git a/internal/services/rule_service.go b/internal/services/rule_service.go
new file mode 100644
index 0000000..b496493
--- /dev/null
+++ b/internal/services/rule_service.go
@@ -0,0 +1,26 @@
+package services
+
+import (
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/internal/repositories"
+)
+
+type RuleService struct {
+	ruleRepository repositories.RuleRepository
+}
+
+func (s *RuleService) Add(setupId int, newRule *entity.NewRule) (*entity.Rule, error) {
+	return s.ruleRepository.Add(setupId, newRule)
+}
+
+func (s *RuleService) GetBySetupId(setupId int) ([]*entity.Rule, error) {
+	return s.ruleRepository.GetBySetupId(setupId)
+}
+
+func (s *RuleService) GetById(setupId int, id int) (*entity.Rule, error) {
+	return s.ruleRepository.GetById(setupId, id)
+}
+
+func (s *RuleService) Delete(setupId int, id int) error {
+	return s.ruleRepository.Delete(setupId, id)
+}
diff --git a/internal/services/setup_manipulator_service.go b/internal/services/setup_manipulator_service.go
deleted file mode 100644
index a826279..0000000
--- a/internal/services/setup_manipulator_service.go
+++ /dev/null
@@ -1,70 +0,0 @@
-package services
-
-import (
-	"git.miem.hse.ru/hubman/configurator/internal/entity"
-	"git.miem.hse.ru/hubman/configurator/internal/repositories"
-	"git.miem.hse.ru/hubman/configurator/pkg/errs"
-	"strconv"
-)
-
-type SetupManipulatorService struct {
-	setupService          *SetupService
-	setupRepository       repositories.SetupRepository
-	manipulatorRepository repositories.ManipulatorRepository
-}
-
-func NewSetupManipulatorService(
-	setupService *SetupService,
-	manipulatorRepository repositories.ManipulatorRepository,
-) *SetupManipulatorService {
-	return &SetupManipulatorService{
-		setupService:          setupService,
-		manipulatorRepository: manipulatorRepository}
-}
-
-func (s *SetupManipulatorService) GetList(setupIdStr string) ([]*entity.Manipulator, error) {
-	setup, err := s.setupService.GetById(setupIdStr)
-	if err != nil {
-		return nil, err
-	}
-	return s.manipulatorRepository.GetBySetupId(setup.Id)
-}
-
-func (s *SetupManipulatorService) Add(
-	setupIdStr string, manipulator *entity.Manipulator,
-) error {
-	setup, err := s.setupService.GetById(setupIdStr)
-	if err != nil {
-		return err
-	}
-	if manipulator == nil {
-		return errs.ErrInternalError
-	}
-	if err := manipulator.CheckAlias(); err != nil {
-		return err
-	}
-	if err := manipulator.CheckURL(); err != nil {
-		return err
-	}
-	manipulator.SetupId = setup.Id
-	return s.manipulatorRepository.Add(manipulator)
-}
-
-func (s *SetupManipulatorService) GetById(setupIdStr, manipulatorIdStr string) (*entity.Manipulator, error) {
-	setupId, err := strconv.Atoi(setupIdStr)
-	if err != nil {
-		return nil, errs.ErrSetupNotFound
-	}
-	manipulatorId, err := strconv.Atoi(manipulatorIdStr)
-	if err != nil {
-		return nil, errs.ErrManipulatorNotFound
-	}
-	manipulator, err := s.manipulatorRepository.GetById(manipulatorId)
-	if err != nil {
-		return nil, err
-	}
-	if manipulator.SetupId != setupId {
-		return nil, errs.ErrManipulatorNotFound
-	}
-	return manipulator, nil
-}
diff --git a/internal/services/setup_service.go b/internal/services/setup_service.go
index f006e6a..4ff8a25 100644
--- a/internal/services/setup_service.go
+++ b/internal/services/setup_service.go
@@ -19,37 +19,22 @@ func (s *SetupService) GetList() ([]*entity.Setup, error) {
 	return s.r.GetAll()
 }
 
-func (s *SetupService) Delete(setupId string) error {
-	id, err := strconv.Atoi(setupId)
-	if err != nil {
-		return errs.ErrSetupNotFound
-	}
+func (s *SetupService) Delete(id int) error {
 	return s.r.Delete(id)
 }
 
-func (s *SetupService) GetById(setupId string) (*entity.Setup, error) {
-	id, err := strconv.Atoi(setupId)
-	if err != nil {
-		return nil, errs.ErrSetupNotFound
-	}
+func (s *SetupService) GetById(id int) (*entity.Setup, error) {
 	return s.r.GetById(id)
 }
 
-func (s *SetupService) Add(setup *entity.Setup) error {
-	return s.r.Add(setup)
+func (s *SetupService) Add(newSetup *entity.NewSetup) (*entity.Setup, error) {
+	return s.r.Add(newSetup)
 }
 
-func (s *SetupService) Update(setupId string, name *string, description *string) (*entity.Setup, error) {
-	id, err := strconv.Atoi(setupId)
+func (s *SetupService) Update(id int, updSetup *entity.UpdSetup) (*entity.Setup, error) {
+	err := s.r.Update(id, updSetup)
 	if err != nil {
-		return nil, errs.ErrSetupNotFound
-	}
-
-	if description != nil && name != nil {
-		err := s.r.Update(id, name, description)
-		if err != nil {
-			return nil, err
-		}
+		return nil, err
 	}
 	return s.r.GetById(id)
 }
diff --git a/pkg/query-builder/builder.go b/pkg/query-builder/builder.go
deleted file mode 100644
index bb5ebbb..0000000
--- a/pkg/query-builder/builder.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package querybuilder
-
-import (
-	"fmt"
-	"strings"
-)
-
-type AttrValue struct {
-	Attr  string
-	Value *string
-}
-
-func BuildUpdateSetClause(pairs ...AttrValue) string {
-	var usedClauses []string
-	for _, pair := range pairs {
-		if pair.Value != nil {
-			usedClauses = append(usedClauses, fmt.Sprintf("%s=:%s", pair.Attr, pair.Attr))
-		}
-	}
-	return strings.Join(usedClauses, ", ")
-}
-- 
GitLab


From ab409aafb9ad961d121f08285078df9c00c30db3 Mon Sep 17 00:00:00 2001
From: Sergei Loshkarev <saloshkarev@miem.hse.ru>
Date: Sat, 21 Oct 2023 15:48:14 +0300
Subject: [PATCH 04/12] add tx logic

---
 config.yaml                                   |   6 +-
 internal/adapters/device_adapter.go           |  14 ---
 internal/app/app.go                           |  58 +++------
 internal/config/config.go                     |  12 +-
 internal/entity/device.go                     |  38 ++----
 internal/entity/device_descriptor.go          |   7 ++
 internal/entity/manipulator.go                |  49 --------
 internal/entity/rule.go                       |  55 ++++----
 internal/entity/setup.go                      |  14 +--
 internal/handlers/setup_executor_router.go    |  20 ++-
 internal/handlers/setup_manipulator_router.go |  25 +++-
 internal/handlers/setup_util.go               |   6 +-
 internal/handlers/setups_router.go            |  43 ++++++-
 .../device_descriptor_repository.go           |  78 ++++++++++++
 internal/repositories/device_repository.go    |  31 ++---
 internal/repositories/setup_repository.go     | 117 +++++++++++++-----
 internal/repositories/utils.go                |  45 +++++++
 internal/services/device_store_service.go     |   3 +-
 internal/services/manipulator_service.go      |   4 +-
 internal/services/setup_service.go            |  23 ++--
 pkg/errs/errors.go                            |   4 +-
 pkg/transactions/transactions.go              |  33 +++++
 22 files changed, 424 insertions(+), 261 deletions(-)
 delete mode 100644 internal/adapters/device_adapter.go
 delete mode 100644 internal/entity/manipulator.go
 create mode 100644 pkg/transactions/transactions.go

diff --git a/config.yaml b/config.yaml
index 40a16e5..8219879 100644
--- a/config.yaml
+++ b/config.yaml
@@ -1,4 +1,2 @@
-db:
-  path: "db.sqlite"
-http:
-  port: 8080
\ No newline at end of file
+db_path: "db.sqlite"
+http_port: 8080
\ No newline at end of file
diff --git a/internal/adapters/device_adapter.go b/internal/adapters/device_adapter.go
deleted file mode 100644
index 19c9627..0000000
--- a/internal/adapters/device_adapter.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package adapters
-
-import (
-	"net/url"
-)
-
-type DeviceAdapter struct {
-	deviceUrl url.URL
-}
-
-//func (a *DeviceAdapter) CheckStatus() (Status, err) {
-//	a.deviceUrl.JoinPath("/")
-//	//resp, err := http.Get(a.deviceUrl)
-//}
diff --git a/internal/app/app.go b/internal/app/app.go
index 1f0a319..569004a 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -7,6 +7,7 @@ import (
 	"git.miem.hse.ru/hubman/configurator/internal/handlers"
 	"git.miem.hse.ru/hubman/configurator/internal/repositories"
 	"git.miem.hse.ru/hubman/configurator/internal/services"
+	"git.miem.hse.ru/hubman/configurator/pkg/transactions"
 	"github.com/gin-gonic/gin"
 	"github.com/jmoiron/sqlx"
 	_ "github.com/mattn/go-sqlite3"
@@ -18,58 +19,34 @@ type App struct {
 	conf       *config.Config
 }
 
+type IApplyRouter interface {
+	ApplyTo(httpEngine gin.IRouter)
+}
+
 func NewApp(conf *config.Config) *App {
-	httpEngine := gin.Default()
 	db, err := initDB(conf)
 	if err != nil {
 		log.Fatalln("Can't init DB: ", err)
 	}
 	setupRepository := repositories.NewSqliteSetupRepository(db)
 	deviceRepository := repositories.NewSqliteDeviceRepository(db)
+	//deviceDescriptorRepository := repositories.NewSqliteDeviceDescriptorRepository(db)
 
 	setupService := services.NewSetupService(setupRepository)
 	deviceStoreService := services.NewDeviceStoreService(deviceRepository)
 	manipulatorService := services.NewManipulatorService(deviceStoreService)
 	executorService := services.NewExecutorService(deviceStoreService)
 
-	setupRouter := handlers.NewSetupRouter(setupService)
-	httpEngine.GET("/setups", setupRouter.GetList)
-	httpEngine.POST("/setups", setupRouter.Post)
-
-	setupedNS := httpEngine.Group("/setups/:setup_id")
-	setupedNS.Use(handlers.CheckSetupId(setupService))
-	{
-		setupedNS.GET("/", setupRouter.Get)
-		setupedNS.PATCH("/", setupRouter.Patch)
-		setupedNS.DELETE("/", setupRouter.Delete)
-	}
-
-	setupManipulatorRouter := handlers.NewSetupManipulatorRouter(manipulatorService)
-	setupManipulatorNS := httpEngine.Group("/setups/:setup_id/manipulators")
-	setupManipulatorNS.Use(handlers.CheckSetupId(setupService))
-	{
-		setupManipulatorNS.GET("/", setupManipulatorRouter.GetList)
-		setupManipulatorNS.POST("/", setupManipulatorRouter.Post)
+	httpEngine := gin.Default()
+	httpEngine.Use(transactions.InjectStorage(db))
 
-		manipulatoredNS := setupManipulatorNS.Group("/:manipuator_id")
-		manipulatoredNS.Use(handlers.CheckManipulatorId(executorService))
-		manipulatoredNS.GET("/", setupManipulatorRouter.Get)
-		manipulatoredNS.PATCH("/", setupManipulatorRouter.Patch)
-		manipulatoredNS.DELETE("/", setupManipulatorRouter.Delete)
+	routerToApply := []IApplyRouter{
+		handlers.NewSetupRouter(setupService),
+		handlers.NewSetupManipulatorRouter(setupService, manipulatorService),
+		handlers.NewSetupExecutorRouter(setupService, executorService),
 	}
-
-	setupExecutorRouter := handlers.NewSetupExecutorRouter(executorService)
-	setupExecutorNS := httpEngine.Group("/setups/:setup_id/executors")
-	setupExecutorNS.Use(handlers.CheckSetupId(setupService))
-	{
-		setupExecutorNS.GET("/", setupExecutorRouter.GetList)
-		setupExecutorNS.POST("/", setupExecutorRouter.Post)
-
-		executoredNS := setupExecutorNS.Group("/:executor_id")
-		executoredNS.Use(handlers.CheckExecutorId(executorService))
-		executoredNS.GET("/", setupExecutorRouter.Get)
-		executoredNS.PATCH("/", setupExecutorRouter.Patch)
-		executoredNS.DELETE("/", setupExecutorRouter.Delete)
+	for _, router := range routerToApply {
+		router.ApplyTo(httpEngine)
 	}
 
 	return &App{
@@ -81,7 +58,7 @@ func NewApp(conf *config.Config) *App {
 func (a *App) Run() {
 	log.Println("Server started")
 	err := a.httpEngine.Run(
-		fmt.Sprintf(":%d", a.conf.Http.Port),
+		fmt.Sprintf(":%d", a.conf.HTTPPort),
 	)
 	if err != nil {
 		log.Fatalln("Error in htpEngine: ", err)
@@ -89,9 +66,10 @@ func (a *App) Run() {
 }
 
 func initDB(conf *config.Config) (*sqlx.DB, error) {
-	db, err := sqlx.Connect("sqlite3", conf.Db.Path)
+	db, err := sqlx.Connect("sqlite3", conf.DBPath)
 	for _, dmlString := range []string{
-		entity.SetupDML, entity.DeviceDML, entity.RuleDML,
+		entity.SetupDML, entity.DeviceDML, entity.DeviceDescriptorDML,
+		entity.RuleDML,
 	} {
 		_, err = db.Exec(dmlString)
 		if err != nil {
diff --git a/internal/config/config.go b/internal/config/config.go
index fc5eb8a..bce22d6 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -6,16 +6,8 @@ import (
 )
 
 type Config struct {
-	Http `yaml:"http"`
-	Db   `yaml:"db""`
-}
-
-type Http struct {
-	Port int `yaml:"port"`
-}
-
-type Db struct {
-	Path string `yaml:"path"`
+	HTTPPort int    `yaml:"http_port"`
+	DBPath   string `yaml:"db_path"`
 }
 
 func NewConfig(path string) (*Config, error) {
diff --git a/internal/entity/device.go b/internal/entity/device.go
index 1736e28..022c754 100644
--- a/internal/entity/device.go
+++ b/internal/entity/device.go
@@ -3,7 +3,6 @@ package entity
 import (
 	"git.miem.hse.ru/hubman/configurator/pkg/errs"
 	"net/url"
-	"regexp"
 )
 
 type DeviceType string
@@ -12,41 +11,27 @@ const DeviceTypeManipulator DeviceType = "manipulator"
 const DeviceTypeExecutor DeviceType = "executor"
 
 type Device struct {
-	Id          int        `db:"id" json:"id"`
-	DeviceType  DeviceType `db:"device_type" json:"-"`
-	SetupId     int        `db:"setup_id" json:"setup_id"`
-	Name        string     `db:"name" json:"name"`
-	Alias       string     `db:"alias" json:"alias"`
-	Description *string    `db:"description" json:"description"`
-	URL         string     `db:"url" json:"url"`
+	Id         int        `db:"id" json:"id"`
+	DeviceType DeviceType `db:"device_type" json:"-"`
+	SetupId    int        `db:"setup_id" json:"setup_id"`
+	Name       string     `db:"name" json:"name"`
+	URL        string     `db:"url" json:"url"`
 }
 
 type NewDevice struct {
-	Name        string  `db:"name" json:"name"`
-	Alias       string  `db:"alias" json:"alias"`
-	Description *string `db:"description" json:"description"`
-	URL         string  `db:"url" json:"url"`
+	Name string `db:"name" json:"name"`
+	URL  string `db:"url" json:"url"`
 }
 
 type UpdDevice struct {
-	Name        *string `db:"name" json:"name"`
-	Alias       *string `db:"alias" json:"alias"`
-	Description *string `db:"description" json:"description"`
-	URL         *string `db:"url" json:"url"`
-}
-
-func (m *Device) CheckAlias() error {
-	match, err := regexp.MatchString(`^[A-Za-z_][0-9A-Za-z_].*$`, m.Alias)
-	if err != nil || !match {
-		return errs.ErrManipulatorIncorrectParams
-	}
-	return nil
+	Name *string `db:"name" json:"name"`
+	URL  *string `db:"url" json:"url"`
 }
 
 func (m *Device) CheckURL() error {
 	_, err := url.ParseRequestURI(m.URL)
 	if err != nil {
-		return errs.ErrManipulatorIncorrectParams
+		return errs.ErrDeviceIncorrectParams
 	}
 	return nil
 }
@@ -57,13 +42,10 @@ CREATE TABLE IF NOT EXISTS Device (
 	   device_type TEXT NOT NULL CHECK (device_type IN ('manipulator', 'executor')),
 	   setup_id INTEGER,
 	   name TEXT NOT NULL,
-	   alias TEXT NOT NULL,
-	   description TEXT,
 	   url TEXT NOT NULL,
 
 	   FOREIGN KEY (setup_id) REFERENCES t_setup(id),
 	   UNIQUE (setup_id, name),
-       UNIQUE (setup_id, alias),
        UNIQUE (setup_id, device_type, url)
 )
 `
diff --git a/internal/entity/device_descriptor.go b/internal/entity/device_descriptor.go
index a534366..f15fc98 100644
--- a/internal/entity/device_descriptor.go
+++ b/internal/entity/device_descriptor.go
@@ -8,6 +8,13 @@ type DeviceDescriptor struct {
 	Schema      string `db:"descriptor_schema" json:"schema"`
 }
 
+type NewDeviceDescriptor struct {
+	DeviceId    int    `db:"device_id" json:"device_id"`
+	Code        string `db:"code" json:"code"`
+	Description string `db:"description" json:"description"`
+	Schema      string `db:"descriptor_schema" json:"schema"`
+}
+
 const DeviceDescriptorDML = `
 	CREATE TABLE IF NOT EXISTS DeviceDescriptor (
 	   id INTEGER PRIMARY KEY AUTOINCREMENT,
diff --git a/internal/entity/manipulator.go b/internal/entity/manipulator.go
deleted file mode 100644
index 318f88c..0000000
--- a/internal/entity/manipulator.go
+++ /dev/null
@@ -1,49 +0,0 @@
-package entity
-
-import (
-	"git.miem.hse.ru/hubman/configurator/pkg/errs"
-	"net/url"
-	"regexp"
-)
-
-type Manipulator struct {
-	Id          int     `db:"id" json:"id"`
-	SetupId     int     `db:"setup_id" json:"setup_id"`
-	Name        string  `db:"name" json:"name"`
-	Alias       string  `db:"alias" json:"alias"`
-	Description *string `db:"description" json:"description"`
-	URL         string  `db:"url" json:"url"`
-}
-
-func (m *Manipulator) CheckAlias() error {
-	match, err := regexp.MatchString(`^[A-Za-z_][0-9A-Za-z_].*$`, m.Alias)
-
-	if err != nil || !match {
-		return errs.ErrManipulatorIncorrectParams
-	}
-	return nil
-}
-
-func (m *Manipulator) CheckURL() error {
-	_, err := url.ParseRequestURI(m.URL)
-	if err != nil {
-		return errs.ErrManipulatorIncorrectParams
-	}
-	return nil
-}
-
-const ManipulatorDML = `
-CREATE TABLE IF NOT EXISTS t_manipulator (
- 	   id INTEGER PRIMARY KEY AUTOINCREMENT,
- 	   setup_id INTEGER, 
- 	   name TEXT NOT NULL,
- 	   alias TEXT NOT NULL,
- 	   description TEXT,
- 	   url TEXT,
- 	   
- 	   FOREIGN KEY (setup_id) REFERENCES t_setup(id),
- 	   UNIQUE (setup_id, name),
-       UNIQUE (setup_id, alias),
-       UNIQUE (setup_id, url)
-)
-`
diff --git a/internal/entity/rule.go b/internal/entity/rule.go
index 88e8a8a..72e7921 100644
--- a/internal/entity/rule.go
+++ b/internal/entity/rule.go
@@ -1,54 +1,51 @@
 package entity
 
 type Rule struct {
-	Id                  int      `db:"id" json:"id"`
-	Name                string   `db:"name" json:"name"`
-	SetupId             int      `db:"setup_id" json:"setup_id"`
-	ManipulatorId       int      `db:"manipulator_id" json:"manipulator_id"`
-	SignalDescriptorId  int      `db:"signal_descriptor_id" json:"signal_descriptor_id"`
-	ExecutorId          int      `db:"executor_id" json:"executor_id"`
-	CommandDescriptorId int      `db:"command_descriptor_id" json:"command_descriptor_id"`
-	Trigger             string   `db:"trigger" json:"trigger"`
-	Transofrms          []string `db:"transforms" json:"transforms"`
+	Id                  int            `db:"id" json:"id"`
+	SetupId             int            `db:"setup_id" json:"setup_id"`
+	ManipulatorId       int            `db:"manipulator_id" json:"manipulator_id"`
+	SignalDescriptorId  int            `db:"signal_descriptor_id" json:"signal_descriptor_id"`
+	ExecutorId          int            `db:"executor_id" json:"executor_id"`
+	CommandDescriptorId int            `db:"command_descriptor_id" json:"command_descriptor_id"`
+	Trigger             string         `db:"trigger" json:"trigger"`
+	Logic               map[string]any `db:"logic" json:"logic"`
 }
 
 type NewRule struct {
-	Name                string   `db:"name" json:"name"`
-	ManipulatorId       int      `db:"manipulator_id" json:"manipulator_id"`
-	SignalDescriptorId  int      `db:"signal_descriptor_id" json:"signal_descriptor_id"`
-	ExecutorId          int      `db:"executor_id" json:"executor_id"`
-	CommandDescriptorId int      `db:"command_descriptor_id" json:"command_descriptor_id"`
-	Trigger             string   `db:"trigger" json:"trigger"`
-	Transofrms          []string `db:"transforms" json:"transforms"`
+	ManipulatorId       int            `db:"manipulator_id" json:"manipulator_id"`
+	SignalDescriptorId  int            `db:"signal_descriptor_id" json:"signal_descriptor_id"`
+	ExecutorId          int            `db:"executor_id" json:"executor_id"`
+	CommandDescriptorId int            `db:"command_descriptor_id" json:"command_descriptor_id"`
+	Trigger             string         `db:"trigger" json:"trigger"`
+	Logic               map[string]any `db:"logic" json:"logic"`
 }
 
 type UpdRule struct {
-	Name                *string   `db:"name" json:"name"`
-	ManipulatorId       *int      `db:"manipulator_id" json:"manipulator_id"`
-	SignalDescriptorId  *int      `db:"signal_descriptor_id" json:"signal_descriptor_id"`
-	ExecutorId          *int      `db:"executor_id" json:"executor_id"`
-	CommandDescriptorId *int      `db:"command_descriptor_id" json:"command_descriptor_id"`
-	Trigger             *string   `db:"trigger" json:"trigger"`
-	Transofrms          *[]string `db:"transforms" json:"transforms"`
+	ManipulatorId       *int            `db:"manipulator_id" json:"manipulator_id"`
+	SignalDescriptorId  *int            `db:"signal_descriptor_id" json:"signal_descriptor_id"`
+	ExecutorId          *int            `db:"executor_id" json:"executor_id"`
+	CommandDescriptorId *int            `db:"command_descriptor_id" json:"command_descriptor_id"`
+	Trigger             *string         `db:"trigger" json:"trigger"`
+	Logic               *map[string]any `db:"logic" json:"logic"`
 }
 
 const RuleDML = `
 	CREATE TABLE IF NOT EXISTS Rule (
 	   id INTEGER PRIMARY KEY AUTOINCREMENT,
 	   name TEXT NOT NULL,
+	   description TEXT NOT NULL,
 	   setup_id INTEGER NOT NULL,
 	   manipulator_id INTEGER NOT NULL,
 	   signal_descriptor_id INTEGER NOT NULL,
 	   executor_id INTEGER NOT NULL,
 	   command_descriptor_id INTEGER NOT NULL,
 	   trigger TEXT NOT NULL CHECK ( json(trigger) ),
-	   transofrms TEXT NOT NULL CHECK ( json_array(transofrms) ),
+	   logic TEXT NOT NULL CHECK ( json(logic) ),
 
-	   FOREIGN KEY (setup_id) REFERENCES t_setup(id),
-	   FOREIGN KEY (manipulator_id) REFERENCES Device(id),
-	   FOREIGN KEY (executor_id) REFERENCES Device(id),
+	   FOREIGN KEY (setup_id) REFERENCES Setup(id),
+	   FOREIGN KEY (manipulator_id) REFERENCES Setup(id),
+	   FOREIGN KEY (executor_id) REFERENCES Setup(id),
 	   FOREIGN KEY (executor_id, command_descriptor_id) REFERENCES DeviceDescriptor(device_id, id),
-	   FOREIGN KEY (manipulator_id, signal_descriptor_id) REFERENCES DeviceDescriptor(device_id, id),
-	   UNIQUE (setup_id, name)
+	   FOREIGN KEY (manipulator_id, signal_descriptor_id) REFERENCES DeviceDescriptor(device_id, id)
 )
 `
diff --git a/internal/entity/setup.go b/internal/entity/setup.go
index 722e320..7fa4ca9 100644
--- a/internal/entity/setup.go
+++ b/internal/entity/setup.go
@@ -1,25 +1,21 @@
 package entity
 
 type Setup struct {
-	Id          int     `db:"id" json:"id"`
-	Name        string  `db:"name" json:"name"`
-	Description *string `db:"description" json:"description"`
+	Id   int    `db:"id" json:"id"`
+	Name string `db:"name" json:"name"`
 }
 
 type NewSetup struct {
-	Name        string  `db:"name" json:"name"`
-	Description *string `db:"description" json:"description"`
+	Name string `db:"name" json:"name"`
 }
 
 type UpdSetup struct {
-	Name        *string `db:"name" json:"name"`
-	Description *string `db:"description" json:"description"`
+	Name *string `db:"name" json:"name"`
 }
 
 const SetupDML = `
 CREATE TABLE IF NOT EXISTS Setup (
  	   id INTEGER PRIMARY KEY AUTOINCREMENT,
- 	   name TEXT NOT NULL UNIQUE,
- 	   description TEXT
+ 	   name TEXT NOT NULL UNIQUE
 )
 `
diff --git a/internal/handlers/setup_executor_router.go b/internal/handlers/setup_executor_router.go
index dca9b86..a6ca8d1 100644
--- a/internal/handlers/setup_executor_router.go
+++ b/internal/handlers/setup_executor_router.go
@@ -8,11 +8,27 @@ import (
 )
 
 type SetupExecutorRouter struct {
+	setupService    *services.SetupService
 	executorService *services.ExecutorService
 }
 
-func NewSetupExecutorRouter(executorService *services.ExecutorService) *SetupExecutorRouter {
-	return &SetupExecutorRouter{executorService}
+func NewSetupExecutorRouter(setupService *services.SetupService, executorService *services.ExecutorService) *SetupExecutorRouter {
+	return &SetupExecutorRouter{setupService, executorService}
+}
+
+func (r *SetupExecutorRouter) ApplyTo(httpEngine gin.IRouter) {
+	setupExecutorNS := httpEngine.Group("/setups/:setup_id/executors")
+	setupExecutorNS.Use(CheckSetupId(r.setupService))
+	{
+		setupExecutorNS.GET("/", r.GetList)
+		setupExecutorNS.POST("/", r.Post)
+
+		executoredNS := setupExecutorNS.Group("/:executor_id")
+		executoredNS.Use(CheckExecutorId(r.executorService))
+		executoredNS.GET("/", r.Get)
+		executoredNS.PATCH("/", r.Patch)
+		executoredNS.DELETE("/", r.Delete)
+	}
 }
 
 func (r *SetupExecutorRouter) GetList(c *gin.Context) {
diff --git a/internal/handlers/setup_manipulator_router.go b/internal/handlers/setup_manipulator_router.go
index c8706db..1f41126 100644
--- a/internal/handlers/setup_manipulator_router.go
+++ b/internal/handlers/setup_manipulator_router.go
@@ -8,11 +8,30 @@ import (
 )
 
 type SetupManipulatorRouter struct {
-	manipulatorService *services.ExecutorService
+	setupService       *services.SetupService
+	manipulatorService *services.ManipulatorService
 }
 
-func NewSetupManipulatorRouter(executorService *services.ExecutorService) *SetupManipulatorRouter {
-	return &SetupManipulatorRouter{executorService}
+func (r *SetupManipulatorRouter) ApplyTo(httpEngine gin.IRouter) {
+	setupManipulatorNS := httpEngine.Group("/setups/:setup_id/manipulators")
+	setupManipulatorNS.Use(CheckSetupId(r.setupService))
+	{
+		setupManipulatorNS.GET("/", r.GetList)
+		setupManipulatorNS.POST("/", r.Post)
+
+		manipulatoredNS := setupManipulatorNS.Group("/:manipulator_id")
+		manipulatoredNS.Use(CheckManipulatorId(r.manipulatorService))
+		manipulatoredNS.GET("/", r.Get)
+		manipulatoredNS.PATCH("/", r.Patch)
+		manipulatoredNS.DELETE("/", r.Delete)
+	}
+}
+
+func NewSetupManipulatorRouter(setupService *services.SetupService, manipulatorService *services.ManipulatorService) *SetupManipulatorRouter {
+	return &SetupManipulatorRouter{
+		setupService,
+		manipulatorService,
+	}
 }
 
 func (r *SetupManipulatorRouter) GetList(c *gin.Context) {
diff --git a/internal/handlers/setup_util.go b/internal/handlers/setup_util.go
index da519dd..587bc01 100644
--- a/internal/handlers/setup_util.go
+++ b/internal/handlers/setup_util.go
@@ -41,15 +41,15 @@ func getExecutorId(c *gin.Context) int {
 	return c.MustGet("executorId").(int)
 }
 
-func CheckManipulatorId(executorService *services.ExecutorService) gin.HandlerFunc {
+func CheckManipulatorId(manipulatorService *services.ManipulatorService) gin.HandlerFunc {
 	return func(c *gin.Context) {
-		executorId, err := executorService.CheckId(c.Params.ByName("manipulator_id"))
+		manipulatorId, err := manipulatorService.CheckId(c.Params.ByName("manipulator_id"))
 		if err != nil {
 			BuildErrResp(c, err)
 			c.Abort()
 			return
 		}
-		c.Set("manipulatorId", executorId)
+		c.Set("manipulatorId", manipulatorId)
 		return
 	}
 }
diff --git a/internal/handlers/setups_router.go b/internal/handlers/setups_router.go
index 63e32a8..dcd45c9 100644
--- a/internal/handlers/setups_router.go
+++ b/internal/handlers/setups_router.go
@@ -3,6 +3,8 @@ package handlers
 import (
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
 	"git.miem.hse.ru/hubman/configurator/internal/services"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"git.miem.hse.ru/hubman/configurator/pkg/transactions"
 	"github.com/gin-gonic/gin"
 	"net/http"
 )
@@ -15,12 +17,23 @@ func NewSetupRouter(setupService *services.SetupService) *SetupRouter {
 	return &SetupRouter{setupService: setupService}
 }
 
+func (r *SetupRouter) ApplyTo(httpEngine gin.IRouter) {
+	httpEngine.GET("/setups", r.GetList)
+	httpEngine.POST("/setups", r.Post)
+	setupedNS := httpEngine.Group("/setups/:setup_id")
+	setupedNS.Use(CheckSetupId(r.setupService))
+	setupedNS.GET("/", r.Get)
+	setupedNS.PATCH("/", r.Patch)
+	setupedNS.DELETE("/", r.Delete)
+}
+
 func (r *SetupRouter) Post(c *gin.Context) {
+	ctx := c.Request.Context()
 	newSetup := &entity.NewSetup{}
 	if err := c.BindJSON(newSetup); err != nil {
 		return
 	}
-	setup, err := r.setupService.Add(newSetup)
+	setup, err := r.setupService.Add(ctx, newSetup)
 	if err != nil {
 		BuildErrResp(c, err)
 		return
@@ -29,7 +42,8 @@ func (r *SetupRouter) Post(c *gin.Context) {
 }
 
 func (r *SetupRouter) GetList(c *gin.Context) {
-	setups, err := r.setupService.GetList()
+	ctx := c.Request.Context()
+	setups, err := r.setupService.GetList(ctx)
 	if err != nil {
 		BuildErrResp(c, err)
 		return
@@ -38,7 +52,8 @@ func (r *SetupRouter) GetList(c *gin.Context) {
 }
 
 func (r *SetupRouter) Get(c *gin.Context) {
-	setup, err := r.setupService.GetById(getSetupId(c))
+	ctx := c.Request.Context()
+	setup, err := r.setupService.GetById(ctx, getSetupId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
@@ -47,7 +62,8 @@ func (r *SetupRouter) Get(c *gin.Context) {
 }
 
 func (r *SetupRouter) Delete(c *gin.Context) {
-	err := r.setupService.Delete(getSetupId(c))
+	ctx := c.Request.Context()
+	err := r.setupService.Delete(ctx, getSetupId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
@@ -56,16 +72,33 @@ func (r *SetupRouter) Delete(c *gin.Context) {
 }
 
 func (r *SetupRouter) Patch(c *gin.Context) {
+	ctx := c.Request.Context()
 	updSetup := &entity.UpdSetup{}
 	if err := c.BindJSON(updSetup); err != nil {
 		return
 	}
+
+	tx, err := transactions.ExtractStorage(c).Beginx()
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	ctx = transactions.InjectTx(ctx, tx)
+
 	setup, err := r.setupService.Update(
-		getSetupId(c), updSetup,
+		ctx, getSetupId(c), updSetup,
 	)
+
 	if err != nil {
+		if err2 := tx.Rollback(); err2 != nil {
+			err = errs.ErrInternalError
+		}
 		BuildErrResp(c, err)
 		return
 	}
+
+	if err2 := tx.Commit(); err2 != nil {
+		BuildErrResp(c, errs.ErrInternalError)
+	}
 	c.JSON(http.StatusOK, setup)
 }
diff --git a/internal/repositories/device_descriptor_repository.go b/internal/repositories/device_descriptor_repository.go
index 3f43206..bcf4612 100644
--- a/internal/repositories/device_descriptor_repository.go
+++ b/internal/repositories/device_descriptor_repository.go
@@ -1 +1,79 @@
 package repositories
+
+import (
+	"database/sql"
+	"errors"
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	sq "github.com/Masterminds/squirrel"
+	"github.com/jmoiron/sqlx"
+	"github.com/mattn/go-sqlite3"
+)
+
+type DeviceDescriptorRepository interface {
+	Add(newDescriptor *entity.NewDeviceDescriptor) (*entity.DeviceDescriptor, error)
+	DeleteByDeviceId(deviceId int) error
+}
+
+type SqliteDeviceDescriptorRepository struct {
+	db *sqlx.DB
+}
+
+func NewSqliteDeviceDescriptorRepository(db *sqlx.DB) *SqliteDeviceDescriptorRepository {
+	return &SqliteDeviceDescriptorRepository{db: db}
+}
+
+func (r *SqliteDeviceDescriptorRepository) Add(newDescriptor *entity.NewDeviceDescriptor) (*entity.DeviceDescriptor, error) {
+	res, err := sq.Insert("DeviceDescriptor").SetMap(map[string]any{
+		"device_id":   newDescriptor.DeviceId,
+		"code":        newDescriptor.Code,
+		"description": newDescriptor.Description,
+	}).RunWith(r.db).Exec()
+	if err != nil {
+		return nil, wrapSqliteDeviceDescriptorError(err)
+	}
+
+	id, err := res.LastInsertId()
+	if err != nil {
+		return nil, errs.ErrInternalError
+	}
+	return &entity.DeviceDescriptor{
+		Id:          int(id),
+		DeviceId:    newDescriptor.DeviceId,
+		Code:        newDescriptor.Code,
+		Description: newDescriptor.Description,
+		Schema:      newDescriptor.Schema,
+	}, nil
+}
+
+func (r *SqliteDeviceDescriptorRepository) DeleteByDeviceId(deviceId int) error {
+	query, params := sq.Delete("DeviceDescriptor").Where(sq.Eq{
+		"device_id": deviceId,
+	}).MustSql()
+	res, err := r.db.Exec(query, params...)
+	if err != nil {
+		return wrapSqliteDeviceDescriptorError(err)
+	}
+	affected, err := res.RowsAffected()
+	if err != nil {
+		return errs.ErrInternalError
+	}
+	if affected == 0 {
+		return errs.ErrDeviceNotFound
+	}
+	return nil
+}
+
+func wrapSqliteDeviceDescriptorError(err error) error {
+	if errors.Is(err, sql.ErrNoRows) {
+		return errs.ErrDeviceDescriptorNotFound
+	}
+	var sqliteErr sqlite3.Error
+	if errors.As(err, &sqliteErr) {
+		if errors.Is(sqliteErr.Code, sqlite3.ErrConstraint) {
+			return errs.ErrDeviceDescriptorIncorrectParams
+		}
+		return errs.ErrInternalError
+	}
+	return err
+}
diff --git a/internal/repositories/device_repository.go b/internal/repositories/device_repository.go
index df2f92e..69c18c2 100644
--- a/internal/repositories/device_repository.go
+++ b/internal/repositories/device_repository.go
@@ -8,6 +8,7 @@ import (
 	sq "github.com/Masterminds/squirrel"
 	"github.com/jmoiron/sqlx"
 	"github.com/mattn/go-sqlite3"
+	"log"
 )
 
 type DeviceRepository interface {
@@ -47,8 +48,6 @@ func (r *SqliteDeviceRepository) Add(
 		"setup_id":    setupId,
 		"device_type": deviceType,
 		"name":        newDevice.Name,
-		"alias":       newDevice.Alias,
-		"description": newDevice.Description,
 		"url":         newDevice.URL,
 	}).RunWith(r.db).Exec()
 	if err != nil {
@@ -60,13 +59,11 @@ func (r *SqliteDeviceRepository) Add(
 		return nil, errs.ErrInternalError
 	}
 	return &entity.Device{
-		Id:          int(id),
-		SetupId:     setupId,
-		DeviceType:  deviceType,
-		Name:        newDevice.Name,
-		Alias:       newDevice.Alias,
-		Description: newDevice.Description,
-		URL:         newDevice.URL,
+		Id:         int(id),
+		SetupId:    setupId,
+		DeviceType: deviceType,
+		Name:       newDevice.Name,
+		URL:        newDevice.URL,
 	}, nil
 }
 
@@ -80,28 +77,26 @@ func (r *SqliteDeviceRepository) GetOne(setupId int, dtype entity.DeviceType, id
 	if row == nil || row.Err() != nil {
 		return nil, wrapSqliteDeviceError(row.Err())
 	}
-	var device entity.Device
-	err := row.StructScan(&device)
+	device := new(entity.Device)
+	err := row.StructScan(device)
 	if err != nil {
 		return nil, wrapSqliteDeviceError(err)
 	}
-	return &device, err
+	return device, err
 }
 
 func (r *SqliteDeviceRepository) Update(setupId int, dtype entity.DeviceType, id int, updDevice *entity.UpdDevice) error {
+	log.Println("updDevice", updDevice)
+	if (updDevice == nil) || (*updDevice == entity.UpdDevice{}) {
+		return errs.ErrDeviceIncorrectParams
+	}
 	setMap := make(map[string]any)
 	if updDevice.Name != nil {
 		setMap["name"] = updDevice.Name
 	}
-	if updDevice.Description != nil {
-		setMap["description"] = updDevice.Description
-	}
 	if updDevice.URL != nil {
 		setMap["url"] = updDevice.URL
 	}
-	if updDevice.Alias != nil {
-		setMap["alias"] = updDevice.Alias
-	}
 	query, params := sq.Update("Device").SetMap(setMap).Where(sq.Eq{
 		"setup_id": setupId, "device_type": dtype, "id": id,
 	}).MustSql()
diff --git a/internal/repositories/setup_repository.go b/internal/repositories/setup_repository.go
index 65257af..eac88d2 100644
--- a/internal/repositories/setup_repository.go
+++ b/internal/repositories/setup_repository.go
@@ -1,6 +1,7 @@
 package repositories
 
 import (
+	"context"
 	"database/sql"
 	"errors"
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
@@ -8,14 +9,15 @@ import (
 	sq "github.com/Masterminds/squirrel"
 	"github.com/jmoiron/sqlx"
 	"github.com/mattn/go-sqlite3"
+	"log"
 )
 
 type SetupRepository interface {
-	GetAll() ([]*entity.Setup, error)
-	GetById(id int) (*entity.Setup, error)
-	Add(setup *entity.NewSetup) (*entity.Setup, error)
-	Update(id int, updSetup *entity.UpdSetup) error
-	Delete(id int) error
+	GetAll(ctx context.Context) ([]*entity.Setup, error)
+	GetById(ctx context.Context, id int) (*entity.Setup, error)
+	Add(ctx context.Context, setup *entity.NewSetup) (*entity.Setup, error)
+	Update(ctx context.Context, id int, updSetup *entity.UpdSetup) error
+	Delete(ctx context.Context, id int) error
 }
 
 type SqliteSetupRepository struct {
@@ -26,91 +28,144 @@ func NewSqliteSetupRepository(db *sqlx.DB) *SqliteSetupRepository {
 	return &SqliteSetupRepository{db: db}
 }
 
-func (r SqliteSetupRepository) GetAll() ([]*entity.Setup, error) {
+func (r SqliteSetupRepository) GetAll(ctx context.Context) ([]*entity.Setup, error) {
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return nil, rtx.RollbackTxOrIgnore()
+	}
 	query, params := sq.Select("*").From("Setup").MustSql()
 	setups := make([]*entity.Setup, 0, 0)
-	err := r.db.Select(&setups, query, params...)
+	err = rtx.tx.Select(&setups, query, params...)
+
 	if err != nil {
-		return setups, errs.ErrInternalError
+		if err := rtx.RollbackTxOrIgnore(); err != nil {
+			return nil, err
+		}
+		return setups, err
+	}
+
+	if err := rtx.RollbackTxOrIgnore(); err != nil {
+		return nil, err
 	}
 	return setups, nil
 }
 
-func (r SqliteSetupRepository) GetById(id int) (*entity.Setup, error) {
+func (r SqliteSetupRepository) GetById(ctx context.Context, id int) (*entity.Setup, error) {
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return nil, err
+	}
+
 	query, params := sq.Select("*").From("Setup").Where(sq.Eq{"id": id}).MustSql()
 
-	row := r.db.QueryRowx(query, params...)
+	row := rtx.tx.QueryRowx(query, params...)
 	if row == nil || row.Err() != nil {
+		if err := rtx.RollbackTxOrIgnore(); err != nil {
+			return nil, err
+		}
 		return nil, wrapSqliteSetupError(row.Err())
 	}
 
-	var setup *entity.Setup
-	err := row.StructScan(&setup)
+	setup := new(entity.Setup)
+	err = row.StructScan(setup)
 	if err != nil {
-		return nil, errs.ErrInternalError
+		if err := rtx.RollbackTxOrIgnore(); err != nil {
+			return nil, err
+		}
+		return nil, wrapSqliteSetupError(err)
 	}
-	return setup, nil
+
+	return setup, rtx.RollbackTxOrIgnore()
 }
 
-func (r SqliteSetupRepository) Delete(id int) error {
+func (r SqliteSetupRepository) Delete(ctx context.Context, id int) error {
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
 	query, params := sq.Delete("Setup").Where(sq.Eq{"id": id}).MustSql()
-	res, err := r.db.Exec(query, params...)
+	res, err := rtx.tx.Exec(query, params...)
 	if err != nil {
+		if err := rtx.RollbackTxOrIgnore(); err != nil {
+			return err
+		}
 		return errs.ErrInternalError
 	}
 	affected, err := res.RowsAffected()
 	if err != nil {
+		if err := rtx.RollbackTxOrIgnore(); err != nil {
+			return err
+		}
 		return errs.ErrInternalError
 	}
 	if affected == 0 {
+		if err := rtx.RollbackTxOrIgnore(); err != nil {
+			return err
+		}
 		return errs.ErrSetupNotFound
 	}
 
-	return nil
+	return rtx.CommitTxOrIgnore()
 }
 
-func (r SqliteSetupRepository) Add(newSetup *entity.NewSetup) (*entity.Setup, error) {
+func (r SqliteSetupRepository) Add(ctx context.Context, newSetup *entity.NewSetup) (*entity.Setup, error) {
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return nil, err
+	}
 	query, param := sq.Insert("Setup").SetMap(map[string]any{
-		"name": newSetup.Name, "description": newSetup.Description,
+		"name": newSetup.Name,
 	}).MustSql()
-	res, err := r.db.Exec(query, param...)
+	res, err := rtx.tx.Exec(query, param...)
 	if err != nil {
+		if err := rtx.RollbackTxOrIgnore(); err != nil {
+			return nil, err
+		}
 		return nil, wrapSqliteSetupError(err)
 	}
 	id, err := res.LastInsertId()
 	if err != nil {
+		if err := rtx.RollbackTxOrIgnore(); err != nil {
+			return nil, err
+		}
 		return nil, wrapSqliteSetupError(err)
 	}
-
+	log.Println(err)
 	return &entity.Setup{
-		Id:          int(id),
-		Name:        newSetup.Name,
-		Description: newSetup.Description,
-	}, nil
+		Id:   int(id),
+		Name: newSetup.Name,
+	}, rtx.CommitTxOrIgnore()
 }
 
-func (r SqliteSetupRepository) Update(id int, updSetup *entity.UpdSetup) error {
+func (r SqliteSetupRepository) Update(ctx context.Context, id int, updSetup *entity.UpdSetup) error {
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
 	updMap := make(map[string]any)
-	if updSetup.Name != nil {
-		updMap["name"] = updSetup.Name
+	if (updSetup == nil) || (*updSetup == entity.UpdSetup{}) {
+		return errs.ErrDeviceIncorrectParams
 	}
 	if updSetup.Name != nil {
-		updMap["description"] = updSetup.Description
+		updMap["name"] = updSetup.Name
 	}
 
 	query, params := sq.Update("Setup").Where(sq.Eq{"id": id}).SetMap(updMap).MustSql()
-	res, err := r.db.Exec(query, params...)
+	res, err := rtx.tx.Exec(query, params...)
 	if err != nil {
+		if err := rtx.RollbackTxOrIgnore(); err != nil {
+			return err
+		}
 		return wrapSqliteSetupError(err)
 	}
 	affected, err := res.RowsAffected()
 	if err != nil {
+		if err := rtx.RollbackTxOrIgnore(); err != nil {
+			return err
+		}
 		return errs.ErrInternalError
 	}
 	if affected == 0 {
+		if err := rtx.RollbackTxOrIgnore(); err != nil {
+			return err
+		}
 		return errs.ErrSetupNotFound
 	}
-	return nil
+	return rtx.CommitTxOrIgnore()
 }
 
 func wrapSqliteSetupError(err error) error {
diff --git a/internal/repositories/utils.go b/internal/repositories/utils.go
index 3f43206..b5002b8 100644
--- a/internal/repositories/utils.go
+++ b/internal/repositories/utils.go
@@ -1 +1,46 @@
 package repositories
+
+import (
+	"context"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"git.miem.hse.ru/hubman/configurator/pkg/transactions"
+	"github.com/jmoiron/sqlx"
+	"log"
+)
+
+func extractSqlxTxOrNew(ctx context.Context, db *sqlx.DB) (*RTx, error) {
+	tx := transactions.ExtractTx(ctx)
+	if tx != nil {
+		return &RTx{tx, false}, nil
+	}
+	tx, err := db.Beginx()
+	if err != nil {
+		return &RTx{tx, false}, errs.ErrInternalError
+	}
+	return &RTx{tx, true}, nil
+}
+
+type RTx struct {
+	tx      *sqlx.Tx
+	isNewTx bool
+}
+
+func (rtx *RTx) CommitTxOrIgnore() error {
+	if !rtx.isNewTx {
+		return nil
+	}
+	return rtx.tx.Commit()
+}
+
+func (rtx *RTx) RollbackTxOrIgnore() error {
+	if !rtx.isNewTx {
+		return nil
+	}
+	err := rtx.tx.Rollback()
+	if err != nil {
+		log.Println(err)
+		return errs.ErrInternalError
+	}
+	log.Println("rollback")
+	return nil
+}
diff --git a/internal/services/device_store_service.go b/internal/services/device_store_service.go
index de5a019..c8dc1ea 100644
--- a/internal/services/device_store_service.go
+++ b/internal/services/device_store_service.go
@@ -6,7 +6,8 @@ import (
 )
 
 type DeviceStoreService struct {
-	deviceRepository repositories.DeviceRepository
+	deviceRepository           repositories.DeviceRepository
+	deviceDescriptorRepository repositories.DeviceDescriptorRepository
 }
 
 func NewDeviceStoreService(deviceRepository repositories.DeviceRepository) *DeviceStoreService {
diff --git a/internal/services/manipulator_service.go b/internal/services/manipulator_service.go
index 06ee665..a89f961 100644
--- a/internal/services/manipulator_service.go
+++ b/internal/services/manipulator_service.go
@@ -10,8 +10,8 @@ type ManipulatorService struct {
 	deviceStoreService *DeviceStoreService
 }
 
-func NewManipulatorService(deviceStoreService *DeviceStoreService) *ExecutorService {
-	return &ExecutorService{deviceStoreService: deviceStoreService}
+func NewManipulatorService(deviceStoreService *DeviceStoreService) *ManipulatorService {
+	return &ManipulatorService{deviceStoreService: deviceStoreService}
 }
 
 func (s *ManipulatorService) GetList(setupId int) ([]*entity.Device, error) {
diff --git a/internal/services/setup_service.go b/internal/services/setup_service.go
index 4ff8a25..58dcdbd 100644
--- a/internal/services/setup_service.go
+++ b/internal/services/setup_service.go
@@ -1,6 +1,7 @@
 package services
 
 import (
+	"context"
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
 	"git.miem.hse.ru/hubman/configurator/internal/repositories"
 	"git.miem.hse.ru/hubman/configurator/pkg/errs"
@@ -15,28 +16,28 @@ func NewSetupService(r repositories.SetupRepository) *SetupService {
 	return &SetupService{r: r}
 }
 
-func (s *SetupService) GetList() ([]*entity.Setup, error) {
-	return s.r.GetAll()
+func (s *SetupService) GetList(ctx context.Context) ([]*entity.Setup, error) {
+	return s.r.GetAll(ctx)
 }
 
-func (s *SetupService) Delete(id int) error {
-	return s.r.Delete(id)
+func (s *SetupService) Delete(ctx context.Context, id int) error {
+	return s.r.Delete(ctx, id)
 }
 
-func (s *SetupService) GetById(id int) (*entity.Setup, error) {
-	return s.r.GetById(id)
+func (s *SetupService) GetById(ctx context.Context, id int) (*entity.Setup, error) {
+	return s.r.GetById(ctx, id)
 }
 
-func (s *SetupService) Add(newSetup *entity.NewSetup) (*entity.Setup, error) {
-	return s.r.Add(newSetup)
+func (s *SetupService) Add(ctx context.Context, newSetup *entity.NewSetup) (*entity.Setup, error) {
+	return s.r.Add(ctx, newSetup)
 }
 
-func (s *SetupService) Update(id int, updSetup *entity.UpdSetup) (*entity.Setup, error) {
-	err := s.r.Update(id, updSetup)
+func (s *SetupService) Update(ctx context.Context, id int, updSetup *entity.UpdSetup) (*entity.Setup, error) {
+	err := s.r.Update(ctx, id, updSetup)
 	if err != nil {
 		return nil, err
 	}
-	return s.r.GetById(id)
+	return s.r.GetById(ctx, id)
 }
 
 func (s *SetupService) CheckId(setupIdStr string) (int, error) {
diff --git a/pkg/errs/errors.go b/pkg/errs/errors.go
index 0fdbe4a..b8e38c0 100644
--- a/pkg/errs/errors.go
+++ b/pkg/errs/errors.go
@@ -17,6 +17,6 @@ var (
 	ErrDeviceIncorrectParams = fmt.Errorf("DEVICE_%w", ErrIncorrectParams)
 	ErrDeviceNotFound        = fmt.Errorf("DEVICE_%w", ErrNotFound)
 
-	ErrManipulatorIncorrectParams = fmt.Errorf("MANIPULATOR_%w", ErrIncorrectParams)
-	ErrManipulatorNotFound        = fmt.Errorf("MANIPULATOR_%w", ErrNotFound)
+	ErrDeviceDescriptorIncorrectParams = fmt.Errorf("DEVICE_DESCRIPTOR_%w", ErrIncorrectParams)
+	ErrDeviceDescriptorNotFound        = fmt.Errorf("DEVICE_DESCRIPTOR_%w", ErrNotFound)
 )
diff --git a/pkg/transactions/transactions.go b/pkg/transactions/transactions.go
new file mode 100644
index 0000000..6aec3b3
--- /dev/null
+++ b/pkg/transactions/transactions.go
@@ -0,0 +1,33 @@
+package transactions
+
+import (
+	"github.com/gin-gonic/gin"
+	"github.com/jmoiron/sqlx"
+	"golang.org/x/net/context"
+)
+
+type txTypeKey struct{}
+
+const GinCtxStorage = "Storage"
+
+func InjectStorage(db *sqlx.DB) gin.HandlerFunc {
+	return func(c *gin.Context) {
+		c.Set(GinCtxStorage, db)
+	}
+}
+
+func InjectTx(ctx context.Context, tx *sqlx.Tx) context.Context {
+	return context.WithValue(ctx, txTypeKey{}, tx)
+}
+
+func ExtractTx(ctx context.Context) *sqlx.Tx {
+	tx := ctx.Value(txTypeKey{})
+	if tx == nil {
+		return nil
+	}
+	return tx.(*sqlx.Tx)
+}
+
+func ExtractStorage(c *gin.Context) *sqlx.DB {
+	return c.MustGet(GinCtxStorage).(*sqlx.DB)
+}
-- 
GitLab


From d3230ed619533ef76937acff6431163caa84ca2d Mon Sep 17 00:00:00 2001
From: Sergei Loshkarev <saloshkarev@miem.hse.ru>
Date: Mon, 23 Oct 2023 02:56:04 +0300
Subject: [PATCH 05/12] oh no....

---
 go.mod                                        | 18 ++++-
 go.sum                                        | 38 +++++++++
 internal/app/app.go                           | 12 ++-
 internal/entity/device.go                     |  5 ++
 internal/entity/device_descriptor.go          | 17 +++-
 internal/handlers/setup_executor_router.go    | 42 ++++++++--
 internal/handlers/setup_manipulator_router.go |  8 +-
 .../device_descriptor_repository.go           | 62 ++++++++++++---
 internal/repositories/device_repository.go    | 78 ++++++++++++++-----
 internal/repositories/setup_repository.go     | 10 +--
 internal/repositories/utils.go                | 17 ++--
 internal/services/device_store_service.go     | 56 ++++++++++---
 internal/services/executor_service.go         | 44 ++++++++---
 internal/services/manipulator_service.go      | 12 +--
 14 files changed, 333 insertions(+), 86 deletions(-)

diff --git a/go.mod b/go.mod
index 25a39b2..83b0523 100644
--- a/go.mod
+++ b/go.mod
@@ -3,11 +3,17 @@ module git.miem.hse.ru/hubman/configurator
 go 1.20
 
 require (
-	github.com/BurntSushi/toml v1.2.1 // indirect
+	git.miem.hse.ru/hubman/hubman-lib v0.0.12 // indirect
+	github.com/BurntSushi/toml v1.3.2 // indirect
 	github.com/Masterminds/squirrel v1.5.4 // indirect
+	github.com/bahlo/generic-list-go v0.2.0 // indirect
+	github.com/buger/jsonparser v1.1.1 // indirect
 	github.com/bytedance/sonic v1.10.1 // indirect
+	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
 	github.com/chenzhuoyu/iasm v0.9.0 // indirect
+	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+	github.com/diegoholiveira/jsonlogic/v3 v3.3.0 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
 	github.com/gin-contrib/sse v0.1.0 // indirect
 	github.com/gin-gonic/gin v1.9.1 // indirect
@@ -15,7 +21,9 @@ require (
 	github.com/go-playground/universal-translator v0.18.1 // indirect
 	github.com/go-playground/validator/v10 v10.15.5 // indirect
 	github.com/goccy/go-json v0.10.2 // indirect
+	github.com/google/uuid v1.3.1 // indirect
 	github.com/ilyakaznacheev/cleanenv v1.5.0 // indirect
+	github.com/invopop/jsonschema v0.12.0 // indirect
 	github.com/jmoiron/sqlx v1.3.5 // indirect
 	github.com/joho/godotenv v1.5.1 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
@@ -23,13 +31,21 @@ require (
 	github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
 	github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
 	github.com/leodido/go-urn v1.2.4 // indirect
+	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/mattn/go-isatty v0.0.19 // indirect
 	github.com/mattn/go-sqlite3 v1.14.17 // indirect
+	github.com/mitchellh/copystructure v1.0.0 // indirect
+	github.com/mitchellh/reflectwalk v1.0.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/oapi-codegen/runtime v1.0.0 // indirect
 	github.com/pelletier/go-toml/v2 v2.1.0 // indirect
+	github.com/redis/go-redis/v9 v9.1.0 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/ugorji/go/codec v1.2.11 // indirect
+	github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
+	go.uber.org/multierr v1.11.0 // indirect
+	go.uber.org/zap v1.26.0 // indirect
 	golang.org/x/arch v0.5.0 // indirect
 	golang.org/x/crypto v0.14.0 // indirect
 	golang.org/x/net v0.16.0 // indirect
diff --git a/go.sum b/go.sum
index 84908a2..9e08dd7 100644
--- a/go.sum
+++ b/go.sum
@@ -1,11 +1,23 @@
+git.miem.hse.ru/hubman/hubman-lib v0.0.11 h1:xXUBFnPOtmeFhfyp0m2pXy2d8vJ0fnqLQ3QfG0M3GA4=
+git.miem.hse.ru/hubman/hubman-lib v0.0.11/go.mod h1:tH+GFZj6eQtZTqIcT4j0rExq5EVFpCZz+9uMFEqZkhg=
+git.miem.hse.ru/hubman/hubman-lib v0.0.12 h1:XMOaIAxLtdZxJBL45BTMfPCKJv3OCR2gk4A/osM3gZY=
+git.miem.hse.ru/hubman/hubman-lib v0.0.12/go.mod h1:tH+GFZj6eQtZTqIcT4j0rExq5EVFpCZz+9uMFEqZkhg=
 github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
 github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
+github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM=
 github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
+github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
+github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
+github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
+github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
 github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
 github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
 github.com/bytedance/sonic v1.10.1 h1:7a1wuFXL1cMy7a3f7/VFcEtriuXQnUBhtoVfOZiaysc=
 github.com/bytedance/sonic v1.10.1/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
 github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
 github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
@@ -14,6 +26,10 @@ github.com/chenzhuoyu/iasm v0.9.0 h1:9fhXjVzq5hUy2gkhhgHl95zG2cEAhw9OSGs8toWWAwo
 github.com/chenzhuoyu/iasm v0.9.0/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLIrkAmYog=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/diegoholiveira/jsonlogic/v3 v3.3.0 h1:XdIxQ+ICFcQB9tVf46cmiCkc5K9MN8Sh/x+XDHL+iXM=
+github.com/diegoholiveira/jsonlogic/v3 v3.3.0/go.mod h1:9oE8z9G+0OMxOoLHF3fhek3KuqD5CBqM0B6XFL08MSg=
 github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
 github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
 github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@@ -32,12 +48,17 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 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.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
+github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4=
 github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk=
+github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI=
+github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
 github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g=
 github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ=
 github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
 github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 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=
@@ -51,24 +72,35 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6Fm
 github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
 github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
 github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
+github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
 github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM=
 github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
+github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
+github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
 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/oapi-codegen/runtime v1.0.0 h1:P4rqFX5fMFWqRzY9M/3YF9+aPSPPB06IzP2P7oOxrWo=
+github.com/oapi-codegen/runtime v1.0.0/go.mod h1:LmCUMQuPB4M/nLXilQXhHw+BLZdDb18B34OO356yJ/A=
 github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
 github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/redis/go-redis/v9 v9.1.0 h1:137FnGdk+EQdCbye1FW+qOEcY5S+SpY9T0NiuqvtfMY=
+github.com/redis/go-redis/v9 v9.1.0/go.mod h1:urWj3He21Dj5k4TK1y59xH8Uj6ATueP8AH1cY3lZl4c=
 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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 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=
@@ -79,6 +111,12 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
 github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
 github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
 github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
+github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
+github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
+go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
 golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 golang.org/x/arch v0.5.0 h1:jpGode6huXQxcskEIpOCvrU+tzo81b6+oFLUYXWtH/Y=
 golang.org/x/arch v0.5.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
diff --git a/internal/app/app.go b/internal/app/app.go
index 569004a..1d71ce9 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -30,10 +30,10 @@ func NewApp(conf *config.Config) *App {
 	}
 	setupRepository := repositories.NewSqliteSetupRepository(db)
 	deviceRepository := repositories.NewSqliteDeviceRepository(db)
-	//deviceDescriptorRepository := repositories.NewSqliteDeviceDescriptorRepository(db)
+	deviceDescriptorRepository := repositories.NewSqliteDeviceDescriptorRepository(db)
 
 	setupService := services.NewSetupService(setupRepository)
-	deviceStoreService := services.NewDeviceStoreService(deviceRepository)
+	deviceStoreService := services.NewDeviceStoreService(deviceRepository, deviceDescriptorRepository)
 	manipulatorService := services.NewManipulatorService(deviceStoreService)
 	executorService := services.NewExecutorService(deviceStoreService)
 
@@ -66,7 +66,13 @@ func (a *App) Run() {
 }
 
 func initDB(conf *config.Config) (*sqlx.DB, error) {
-	db, err := sqlx.Connect("sqlite3", conf.DBPath)
+	db, err := sqlx.Connect(
+		"sqlite3",
+		fmt.Sprintf("%s?%s", conf.DBPath, "_txlock=DEFERRED"),
+	)
+	if err != nil {
+		return nil, err
+	}
 	for _, dmlString := range []string{
 		entity.SetupDML, entity.DeviceDML, entity.DeviceDescriptorDML,
 		entity.RuleDML,
diff --git a/internal/entity/device.go b/internal/entity/device.go
index 022c754..4dccf29 100644
--- a/internal/entity/device.go
+++ b/internal/entity/device.go
@@ -28,6 +28,11 @@ type UpdDevice struct {
 	URL  *string `db:"url" json:"url"`
 }
 
+type ExtendedDeviceModel struct {
+	*Device
+	Signals []*DeviceDescriptor `json:"signals"`
+}
+
 func (m *Device) CheckURL() error {
 	_, err := url.ParseRequestURI(m.URL)
 	if err != nil {
diff --git a/internal/entity/device_descriptor.go b/internal/entity/device_descriptor.go
index f15fc98..aba5ba7 100644
--- a/internal/entity/device_descriptor.go
+++ b/internal/entity/device_descriptor.go
@@ -1,5 +1,10 @@
 package entity
 
+import (
+	"encoding/json"
+	"git.miem.hse.ru/hubman/hubman-lib/client"
+)
+
 type DeviceDescriptor struct {
 	Id          int    `db:"id" json:"id"`
 	DeviceId    int    `db:"device_id" json:"device_id"`
@@ -9,19 +14,27 @@ type DeviceDescriptor struct {
 }
 
 type NewDeviceDescriptor struct {
-	DeviceId    int    `db:"device_id" json:"device_id"`
 	Code        string `db:"code" json:"code"`
 	Description string `db:"description" json:"description"`
 	Schema      string `db:"descriptor_schema" json:"schema"`
 }
 
+func BuildNewCommandDescriptor(data client.CommandDescriptor) *NewDeviceDescriptor {
+	jsonBytes, _ := json.Marshal(data.Args)
+	return &NewDeviceDescriptor{
+		data.Code,
+		data.Description,
+		string(jsonBytes),
+	}
+}
+
 const DeviceDescriptorDML = `
 	CREATE TABLE IF NOT EXISTS DeviceDescriptor (
 	   id INTEGER PRIMARY KEY AUTOINCREMENT,
 	   device_id INTEGER NOT NULL,
 	   code TEXT NOT NULL,
 	   description TEXT NOT NULL,	   
-	   descriptor_schema TEXT NOT NULL CHECK ( json(descriptor_schema) ),
+	   descriptor_schema TEXT NOT NULL,
 
 	   FOREIGN KEY (device_id) REFERENCES Device(id),
 	   UNIQUE (code, device_id)
diff --git a/internal/handlers/setup_executor_router.go b/internal/handlers/setup_executor_router.go
index a6ca8d1..d9440ce 100644
--- a/internal/handlers/setup_executor_router.go
+++ b/internal/handlers/setup_executor_router.go
@@ -3,6 +3,8 @@ package handlers
 import (
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
 	"git.miem.hse.ru/hubman/configurator/internal/services"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"git.miem.hse.ru/hubman/configurator/pkg/transactions"
 	"github.com/gin-gonic/gin"
 	"net/http"
 )
@@ -32,7 +34,8 @@ func (r *SetupExecutorRouter) ApplyTo(httpEngine gin.IRouter) {
 }
 
 func (r *SetupExecutorRouter) GetList(c *gin.Context) {
-	executors, err := r.executorService.GetList(getSetupId(c))
+	ctx := c.Request.Context()
+	executors, err := r.executorService.GetList(ctx, getSetupId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
@@ -41,20 +44,34 @@ func (r *SetupExecutorRouter) GetList(c *gin.Context) {
 }
 
 func (r *SetupExecutorRouter) Post(c *gin.Context) {
+	ctx := c.Request.Context()
 	newDevice := &entity.NewDevice{}
 	if err := c.BindJSON(newDevice); err != nil {
 		return
 	}
-	executor, err := r.executorService.Add(getSetupId(c), newDevice)
+	tx, err := transactions.ExtractStorage(c).Beginx()
 	if err != nil {
 		BuildErrResp(c, err)
 		return
 	}
+	ctx = transactions.InjectTx(ctx, tx)
+	defer tx.Rollback()
+
+	executor, err := r.executorService.Add(ctx, getSetupId(c), newDevice)
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	if err := tx.Commit(); err != nil {
+		BuildErrResp(c, errs.ErrInternalError)
+		return
+	}
 	c.JSON(http.StatusOK, executor)
 }
 
 func (r *SetupExecutorRouter) Get(c *gin.Context) {
-	executor, err := r.executorService.GetOne(getSetupId(c), getExecutorId(c))
+	ctx := c.Request.Context()
+	executor, err := r.executorService.GetOne(ctx, getSetupId(c), getExecutorId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
@@ -63,20 +80,35 @@ func (r *SetupExecutorRouter) Get(c *gin.Context) {
 }
 
 func (r *SetupExecutorRouter) Patch(c *gin.Context) {
+	ctx := c.Request.Context()
+
 	updDevice := &entity.UpdDevice{}
 	if err := c.BindJSON(updDevice); err != nil {
 		return
 	}
-	executor, err := r.executorService.Update(getSetupId(c), getExecutorId(c), updDevice)
+	tx, err := transactions.ExtractStorage(c).Beginx()
 	if err != nil {
 		BuildErrResp(c, err)
 		return
 	}
+	defer tx.Rollback()
+
+	executor, err := r.executorService.Update(ctx, getSetupId(c), getExecutorId(c), updDevice)
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+
+	if err := tx.Commit(); err != nil {
+		BuildErrResp(c, errs.ErrInternalError)
+		return
+	}
 	c.JSON(http.StatusOK, executor)
 }
 
 func (r *SetupExecutorRouter) Delete(c *gin.Context) {
-	err := r.executorService.Delete(getSetupId(c), getExecutorId(c))
+	ctx := c.Request.Context()
+	err := r.executorService.Delete(ctx, getSetupId(c), getExecutorId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
diff --git a/internal/handlers/setup_manipulator_router.go b/internal/handlers/setup_manipulator_router.go
index 1f41126..36444c8 100644
--- a/internal/handlers/setup_manipulator_router.go
+++ b/internal/handlers/setup_manipulator_router.go
@@ -57,12 +57,12 @@ func (r *SetupManipulatorRouter) Post(c *gin.Context) {
 }
 
 func (r *SetupManipulatorRouter) Get(c *gin.Context) {
-	executor, err := r.manipulatorService.GetOne(getSetupId(c), GetManipulatorId(c))
+	manipulator, err := r.manipulatorService.GetOne(getSetupId(c), GetManipulatorId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
 	}
-	c.JSON(http.StatusOK, executor)
+	c.JSON(http.StatusOK, manipulator)
 }
 
 func (r *SetupManipulatorRouter) Patch(c *gin.Context) {
@@ -70,12 +70,12 @@ func (r *SetupManipulatorRouter) Patch(c *gin.Context) {
 	if err := c.BindJSON(updDevice); err != nil {
 		return
 	}
-	executor, err := r.manipulatorService.Update(getSetupId(c), GetManipulatorId(c), updDevice)
+	manipulator, err := r.manipulatorService.Update(getSetupId(c), GetManipulatorId(c), updDevice)
 	if err != nil {
 		BuildErrResp(c, err)
 		return
 	}
-	c.JSON(http.StatusOK, executor)
+	c.JSON(http.StatusOK, manipulator)
 }
 
 func (r *SetupManipulatorRouter) Delete(c *gin.Context) {
diff --git a/internal/repositories/device_descriptor_repository.go b/internal/repositories/device_descriptor_repository.go
index bcf4612..aa13dda 100644
--- a/internal/repositories/device_descriptor_repository.go
+++ b/internal/repositories/device_descriptor_repository.go
@@ -1,6 +1,7 @@
 package repositories
 
 import (
+	"context"
 	"database/sql"
 	"errors"
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
@@ -11,8 +12,9 @@ import (
 )
 
 type DeviceDescriptorRepository interface {
-	Add(newDescriptor *entity.NewDeviceDescriptor) (*entity.DeviceDescriptor, error)
-	DeleteByDeviceId(deviceId int) error
+	Add(ctx context.Context, deviceId int, newDescriptor *entity.NewDeviceDescriptor) (*entity.DeviceDescriptor, error)
+	DeleteByDeviceId(ctx context.Context, deviceId int) error
+	GetByDeviceId(ctx context.Context, deviceId int) ([]*entity.DeviceDescriptor, error)
 }
 
 type SqliteDeviceDescriptorRepository struct {
@@ -23,34 +25,50 @@ func NewSqliteDeviceDescriptorRepository(db *sqlx.DB) *SqliteDeviceDescriptorRep
 	return &SqliteDeviceDescriptorRepository{db: db}
 }
 
-func (r *SqliteDeviceDescriptorRepository) Add(newDescriptor *entity.NewDeviceDescriptor) (*entity.DeviceDescriptor, error) {
+func (r *SqliteDeviceDescriptorRepository) Add(ctx context.Context, deviceId int, newDescriptor *entity.NewDeviceDescriptor) (*entity.DeviceDescriptor, error) {
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return nil, err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
 	res, err := sq.Insert("DeviceDescriptor").SetMap(map[string]any{
-		"device_id":   newDescriptor.DeviceId,
-		"code":        newDescriptor.Code,
-		"description": newDescriptor.Description,
-	}).RunWith(r.db).Exec()
+		"device_id":         deviceId,
+		"code":              newDescriptor.Code,
+		"description":       newDescriptor.Description,
+		"descriptor_schema": newDescriptor.Schema,
+	}).RunWith(rtx.tx).ExecContext(ctx)
 	if err != nil {
 		return nil, wrapSqliteDeviceDescriptorError(err)
 	}
-
 	id, err := res.LastInsertId()
 	if err != nil {
 		return nil, errs.ErrInternalError
 	}
+
+	if err := rtx.CommitTxOrIgnore(); err != nil {
+		return nil, errs.ErrInternalError
+	}
 	return &entity.DeviceDescriptor{
 		Id:          int(id),
-		DeviceId:    newDescriptor.DeviceId,
+		DeviceId:    deviceId,
 		Code:        newDescriptor.Code,
 		Description: newDescriptor.Description,
 		Schema:      newDescriptor.Schema,
 	}, nil
 }
 
-func (r *SqliteDeviceDescriptorRepository) DeleteByDeviceId(deviceId int) error {
+func (r *SqliteDeviceDescriptorRepository) DeleteByDeviceId(ctx context.Context, deviceId int) error {
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
 	query, params := sq.Delete("DeviceDescriptor").Where(sq.Eq{
 		"device_id": deviceId,
 	}).MustSql()
-	res, err := r.db.Exec(query, params...)
+	res, err := rtx.tx.ExecContext(ctx, query, params...)
 	if err != nil {
 		return wrapSqliteDeviceDescriptorError(err)
 	}
@@ -61,9 +79,31 @@ func (r *SqliteDeviceDescriptorRepository) DeleteByDeviceId(deviceId int) error
 	if affected == 0 {
 		return errs.ErrDeviceNotFound
 	}
+
+	if err := rtx.CommitTxOrIgnore(); err != nil {
+		return errs.ErrInternalError
+	}
 	return nil
 }
 
+func (r *SqliteDeviceDescriptorRepository) GetByDeviceId(ctx context.Context, deviceId int) ([]*entity.DeviceDescriptor, error) {
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return nil, err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
+	query, params := sq.Select("*").From("DeviceDescriptor").Where(sq.Eq{
+		"device_id": deviceId,
+	}).MustSql()
+	var descriptors []*entity.DeviceDescriptor
+	err = rtx.tx.SelectContext(ctx, &descriptors, query, params...)
+	if err != nil {
+		return descriptors, wrapSqliteDeviceDescriptorError(err)
+	}
+	return descriptors, nil
+}
+
 func wrapSqliteDeviceDescriptorError(err error) error {
 	if errors.Is(err, sql.ErrNoRows) {
 		return errs.ErrDeviceDescriptorNotFound
diff --git a/internal/repositories/device_repository.go b/internal/repositories/device_repository.go
index 69c18c2..3fc5e2c 100644
--- a/internal/repositories/device_repository.go
+++ b/internal/repositories/device_repository.go
@@ -1,6 +1,7 @@
 package repositories
 
 import (
+	"context"
 	"database/sql"
 	"errors"
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
@@ -8,15 +9,14 @@ import (
 	sq "github.com/Masterminds/squirrel"
 	"github.com/jmoiron/sqlx"
 	"github.com/mattn/go-sqlite3"
-	"log"
 )
 
 type DeviceRepository interface {
-	GetBySetupAndType(setupId int, dtype entity.DeviceType) ([]*entity.Device, error)
-	GetOne(setupId int, deviceType entity.DeviceType, id int) (*entity.Device, error)
-	Add(setupId int, deviceType entity.DeviceType, newDevice *entity.NewDevice) (*entity.Device, error)
-	Delete(setupId int, dtype entity.DeviceType, id int) error
-	Update(setupId int, dtype entity.DeviceType, id int, updDevice *entity.UpdDevice) error
+	GetBySetupAndType(ctx context.Context, setupId int, dtype entity.DeviceType) ([]*entity.Device, error)
+	GetOne(ctx context.Context, setupId int, deviceType entity.DeviceType, id int) (*entity.Device, error)
+	Add(ctx context.Context, setupId int, deviceType entity.DeviceType, newDevice *entity.NewDevice) (*entity.Device, error)
+	Delete(ctx context.Context, setupId int, dtype entity.DeviceType, id int) error
+	Update(ctx context.Context, setupId int, dtype entity.DeviceType, id int, updDevice *entity.UpdDevice) error
 }
 
 type SqliteDeviceRepository struct {
@@ -27,14 +27,20 @@ func NewSqliteDeviceRepository(db *sqlx.DB) *SqliteDeviceRepository {
 	return &SqliteDeviceRepository{db: db}
 }
 
-func (r *SqliteDeviceRepository) GetBySetupAndType(setupId int, dtype entity.DeviceType) ([]*entity.Device, error) {
+func (r *SqliteDeviceRepository) GetBySetupAndType(ctx context.Context, setupId int, dtype entity.DeviceType) ([]*entity.Device, error) {
 	query, params := sq.Select("*").From("Device").Where(sq.Eq{
 		"setup_id":    setupId,
 		"device_type": dtype,
 	}).MustSql()
 
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return nil, err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
 	devices := make([]*entity.Device, 0, 0)
-	err := r.db.Select(&devices, query, params...)
+	err = rtx.tx.SelectContext(ctx, &devices, query, params...)
 	if err != nil {
 		return nil, errs.ErrInternalError
 	}
@@ -42,22 +48,31 @@ func (r *SqliteDeviceRepository) GetBySetupAndType(setupId int, dtype entity.Dev
 }
 
 func (r *SqliteDeviceRepository) Add(
-	setupId int, deviceType entity.DeviceType, newDevice *entity.NewDevice,
+	ctx context.Context, setupId int, deviceType entity.DeviceType, newDevice *entity.NewDevice,
 ) (*entity.Device, error) {
+
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return nil, err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
 	res, err := sq.Insert("Device").SetMap(map[string]any{
 		"setup_id":    setupId,
 		"device_type": deviceType,
 		"name":        newDevice.Name,
 		"url":         newDevice.URL,
-	}).RunWith(r.db).Exec()
+	}).RunWith(rtx.tx).ExecContext(ctx)
 	if err != nil {
 		return nil, wrapSqliteDeviceError(err)
 	}
-
 	id, err := res.LastInsertId()
 	if err != nil {
 		return nil, errs.ErrInternalError
 	}
+	if err := rtx.CommitTxOrIgnore(); err != nil {
+		return nil, errs.ErrInternalError
+	}
 	return &entity.Device{
 		Id:         int(id),
 		SetupId:    setupId,
@@ -67,26 +82,39 @@ func (r *SqliteDeviceRepository) Add(
 	}, nil
 }
 
-func (r *SqliteDeviceRepository) GetOne(setupId int, dtype entity.DeviceType, id int) (*entity.Device, error) {
+func (r *SqliteDeviceRepository) GetOne(ctx context.Context, setupId int, dtype entity.DeviceType, id int) (*entity.Device, error) {
 	query, params := sq.Select("*").From("Device").Where(sq.Eq{
 		"id":          id,
 		"setup_id":    setupId,
 		"device_type": dtype,
 	}).MustSql()
-	row := r.db.QueryRowx(query, params...)
+
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return nil, err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
+	row := rtx.tx.QueryRowx(query, params...)
 	if row == nil || row.Err() != nil {
 		return nil, wrapSqliteDeviceError(row.Err())
 	}
 	device := new(entity.Device)
-	err := row.StructScan(device)
+	err = row.StructScan(device)
 	if err != nil {
 		return nil, wrapSqliteDeviceError(err)
 	}
 	return device, err
 }
 
-func (r *SqliteDeviceRepository) Update(setupId int, dtype entity.DeviceType, id int, updDevice *entity.UpdDevice) error {
-	log.Println("updDevice", updDevice)
+func (r *SqliteDeviceRepository) Update(ctx context.Context, setupId int, dtype entity.DeviceType, id int, updDevice *entity.UpdDevice) error {
+
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
 	if (updDevice == nil) || (*updDevice == entity.UpdDevice{}) {
 		return errs.ErrDeviceIncorrectParams
 	}
@@ -100,7 +128,7 @@ func (r *SqliteDeviceRepository) Update(setupId int, dtype entity.DeviceType, id
 	query, params := sq.Update("Device").SetMap(setMap).Where(sq.Eq{
 		"setup_id": setupId, "device_type": dtype, "id": id,
 	}).MustSql()
-	res, err := r.db.Exec(query, params...)
+	res, err := rtx.tx.Exec(query, params...)
 	if err != nil {
 		return wrapSqliteDeviceError(err)
 	}
@@ -111,16 +139,25 @@ func (r *SqliteDeviceRepository) Update(setupId int, dtype entity.DeviceType, id
 	if affected == 0 {
 		return errs.ErrDeviceNotFound
 	}
+	if err := rtx.CommitTxOrIgnore(); err != nil {
+		return errs.ErrInternalError
+	}
 	return nil
 }
 
-func (r *SqliteDeviceRepository) Delete(setupId int, dtype entity.DeviceType, id int) error {
+func (r *SqliteDeviceRepository) Delete(ctx context.Context, setupId int, dtype entity.DeviceType, id int) error {
 	query, params := sq.Delete("Device").Where(sq.Eq{
 		"setup_id":    setupId,
 		"device_type": dtype,
 		"id":          id,
 	}).MustSql()
-	res, err := r.db.Exec(query, params...)
+
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return err
+	}
+	defer rtx.RollbackTxOrIgnore()
+	res, err := rtx.tx.ExecContext(ctx, query, params...)
 	if err != nil {
 		return wrapSqliteDeviceError(err)
 	}
@@ -131,6 +168,9 @@ func (r *SqliteDeviceRepository) Delete(setupId int, dtype entity.DeviceType, id
 	if affected == 0 {
 		return errs.ErrDeviceNotFound
 	}
+	if err := rtx.CommitTxOrIgnore(); err != nil {
+		return errs.ErrInternalError
+	}
 	return nil
 }
 
diff --git a/internal/repositories/setup_repository.go b/internal/repositories/setup_repository.go
index eac88d2..19520da 100644
--- a/internal/repositories/setup_repository.go
+++ b/internal/repositories/setup_repository.go
@@ -35,7 +35,7 @@ func (r SqliteSetupRepository) GetAll(ctx context.Context) ([]*entity.Setup, err
 	}
 	query, params := sq.Select("*").From("Setup").MustSql()
 	setups := make([]*entity.Setup, 0, 0)
-	err = rtx.tx.Select(&setups, query, params...)
+	err = rtx.tx.SelectContext(ctx, &setups, query, params...)
 
 	if err != nil {
 		if err := rtx.RollbackTxOrIgnore(); err != nil {
@@ -58,7 +58,7 @@ func (r SqliteSetupRepository) GetById(ctx context.Context, id int) (*entity.Set
 
 	query, params := sq.Select("*").From("Setup").Where(sq.Eq{"id": id}).MustSql()
 
-	row := rtx.tx.QueryRowx(query, params...)
+	row := rtx.tx.QueryRowxContext(ctx, query, params...)
 	if row == nil || row.Err() != nil {
 		if err := rtx.RollbackTxOrIgnore(); err != nil {
 			return nil, err
@@ -81,7 +81,7 @@ func (r SqliteSetupRepository) GetById(ctx context.Context, id int) (*entity.Set
 func (r SqliteSetupRepository) Delete(ctx context.Context, id int) error {
 	rtx, err := extractSqlxTxOrNew(ctx, r.db)
 	query, params := sq.Delete("Setup").Where(sq.Eq{"id": id}).MustSql()
-	res, err := rtx.tx.Exec(query, params...)
+	res, err := rtx.tx.ExecContext(ctx, query, params...)
 	if err != nil {
 		if err := rtx.RollbackTxOrIgnore(); err != nil {
 			return err
@@ -113,7 +113,7 @@ func (r SqliteSetupRepository) Add(ctx context.Context, newSetup *entity.NewSetu
 	query, param := sq.Insert("Setup").SetMap(map[string]any{
 		"name": newSetup.Name,
 	}).MustSql()
-	res, err := rtx.tx.Exec(query, param...)
+	res, err := rtx.tx.ExecContext(ctx, query, param...)
 	if err != nil {
 		if err := rtx.RollbackTxOrIgnore(); err != nil {
 			return nil, err
@@ -145,7 +145,7 @@ func (r SqliteSetupRepository) Update(ctx context.Context, id int, updSetup *ent
 	}
 
 	query, params := sq.Update("Setup").Where(sq.Eq{"id": id}).SetMap(updMap).MustSql()
-	res, err := rtx.tx.Exec(query, params...)
+	res, err := rtx.tx.ExecContext(ctx, query, params...)
 	if err != nil {
 		if err := rtx.RollbackTxOrIgnore(); err != nil {
 			return err
diff --git a/internal/repositories/utils.go b/internal/repositories/utils.go
index b5002b8..aeac243 100644
--- a/internal/repositories/utils.go
+++ b/internal/repositories/utils.go
@@ -5,7 +5,6 @@ import (
 	"git.miem.hse.ru/hubman/configurator/pkg/errs"
 	"git.miem.hse.ru/hubman/configurator/pkg/transactions"
 	"github.com/jmoiron/sqlx"
-	"log"
 )
 
 func extractSqlxTxOrNew(ctx context.Context, db *sqlx.DB) (*RTx, error) {
@@ -15,7 +14,7 @@ func extractSqlxTxOrNew(ctx context.Context, db *sqlx.DB) (*RTx, error) {
 	}
 	tx, err := db.Beginx()
 	if err != nil {
-		return &RTx{tx, false}, errs.ErrInternalError
+		return nil, errs.ErrInternalError
 	}
 	return &RTx{tx, true}, nil
 }
@@ -25,22 +24,20 @@ type RTx struct {
 	isNewTx bool
 }
 
-func (rtx *RTx) CommitTxOrIgnore() error {
-	if !rtx.isNewTx {
+func (r *RTx) CommitTxOrIgnore() error {
+	if !r.isNewTx {
 		return nil
 	}
-	return rtx.tx.Commit()
+	return r.tx.Commit()
 }
 
-func (rtx *RTx) RollbackTxOrIgnore() error {
-	if !rtx.isNewTx {
+func (r *RTx) RollbackTxOrIgnore() error {
+	if !r.isNewTx {
 		return nil
 	}
-	err := rtx.tx.Rollback()
+	err := r.tx.Rollback()
 	if err != nil {
-		log.Println(err)
 		return errs.ErrInternalError
 	}
-	log.Println("rollback")
 	return nil
 }
diff --git a/internal/services/device_store_service.go b/internal/services/device_store_service.go
index c8dc1ea..5aee100 100644
--- a/internal/services/device_store_service.go
+++ b/internal/services/device_store_service.go
@@ -1,6 +1,7 @@
 package services
 
 import (
+	"context"
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
 	"git.miem.hse.ru/hubman/configurator/internal/repositories"
 )
@@ -10,37 +11,72 @@ type DeviceStoreService struct {
 	deviceDescriptorRepository repositories.DeviceDescriptorRepository
 }
 
-func NewDeviceStoreService(deviceRepository repositories.DeviceRepository) *DeviceStoreService {
-	return &DeviceStoreService{deviceRepository: deviceRepository}
+func NewDeviceStoreService(
+	deviceRepository repositories.DeviceRepository,
+	descriptorRepository repositories.DeviceDescriptorRepository,
+) *DeviceStoreService {
+	return &DeviceStoreService{
+		deviceRepository:           deviceRepository,
+		deviceDescriptorRepository: descriptorRepository,
+	}
 }
 
-func (s *DeviceStoreService) GetBySetupAndType(setupId int, dtype entity.DeviceType) ([]*entity.Device, error) {
-	return s.deviceRepository.GetBySetupAndType(setupId, dtype)
+func (s *DeviceStoreService) GetBySetupAndType(ctx context.Context, setupId int, dtype entity.DeviceType) ([]*entity.Device, error) {
+	return s.deviceRepository.GetBySetupAndType(ctx, setupId, dtype)
 }
 
 func (s *DeviceStoreService) Add(
+	ctx context.Context,
 	setupId int, dtype entity.DeviceType, newDevice *entity.NewDevice,
+	commands []*entity.NewDeviceDescriptor,
 ) (*entity.Device, error) {
-	return s.deviceRepository.Add(setupId, dtype, newDevice)
+	device, err := s.deviceRepository.Add(ctx, setupId, dtype, newDevice)
+	if err != nil {
+		return nil, err
+	}
+	for _, cmd := range commands {
+		_, err := s.deviceDescriptorRepository.Add(ctx, device.Id, cmd)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return device, nil
 }
 
 func (s *DeviceStoreService) GetOne(
+	ctx context.Context,
 	setupId int, dtype entity.DeviceType, id int,
-) (*entity.Device, error) {
-	return s.deviceRepository.GetOne(setupId, dtype, id)
+) (*entity.ExtendedDeviceModel, error) {
+	device, err := s.deviceRepository.GetOne(ctx, setupId, dtype, id)
+	if err != nil {
+		return nil, err
+	}
+	descriptors, err := s.deviceDescriptorRepository.GetByDeviceId(ctx, id)
+	if err != nil {
+		return nil, err
+	}
+	return &entity.ExtendedDeviceModel{
+		Device: device, Signals: descriptors,
+	}, nil
 }
 
 func (s *DeviceStoreService) Update(
+	ctx context.Context,
 	setupId int, dtype entity.DeviceType, id int, updDevice *entity.UpdDevice,
 ) (*entity.Device, error) {
-	if err := s.deviceRepository.Update(setupId, dtype, id, updDevice); err != nil {
+	if err := s.deviceRepository.Update(ctx, setupId, dtype, id, updDevice); err != nil {
 		return nil, err
 	}
-	return s.deviceRepository.GetOne(setupId, dtype, id)
+	return s.deviceRepository.GetOne(ctx, setupId, dtype, id)
 }
 
 func (s *DeviceStoreService) Delete(
+	ctx context.Context,
 	setupId int, dtype entity.DeviceType, id int,
 ) error {
-	return s.deviceRepository.Delete(setupId, dtype, id)
+	err := s.deviceDescriptorRepository.DeleteByDeviceId(ctx, id)
+	if err != nil {
+		return err
+	}
+	return s.deviceRepository.Delete(ctx, setupId, dtype, id)
 }
diff --git a/internal/services/executor_service.go b/internal/services/executor_service.go
index 488812b..2b2a5c2 100644
--- a/internal/services/executor_service.go
+++ b/internal/services/executor_service.go
@@ -1,8 +1,11 @@
 package services
 
 import (
+	"context"
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
 	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	hubmanclient "git.miem.hse.ru/hubman/hubman-lib/client"
+	"log"
 	"strconv"
 )
 
@@ -14,8 +17,8 @@ func NewExecutorService(deviceStoreService *DeviceStoreService) *ExecutorService
 	return &ExecutorService{deviceStoreService: deviceStoreService}
 }
 
-func (s *ExecutorService) GetList(setupId int) ([]*entity.Device, error) {
-	return s.deviceStoreService.GetBySetupAndType(setupId, entity.DeviceTypeExecutor)
+func (s *ExecutorService) GetList(ctx context.Context, setupId int) ([]*entity.Device, error) {
+	return s.deviceStoreService.GetBySetupAndType(ctx, setupId, entity.DeviceTypeExecutor)
 }
 
 func (s *ExecutorService) CheckId(ExecutorIdStr string) (int, error) {
@@ -26,18 +29,39 @@ func (s *ExecutorService) CheckId(ExecutorIdStr string) (int, error) {
 	return setupId, nil
 }
 
-func (s *ExecutorService) Add(setupId int, newDevice *entity.NewDevice) (*entity.Device, error) {
-	return s.deviceStoreService.Add(setupId, entity.DeviceTypeExecutor, newDevice)
+func (s *ExecutorService) Add(ctx context.Context, setupId int, newDevice *entity.NewDevice) (*entity.Device, error) {
+	client, _ := hubmanclient.NewClientWithResponses(newDevice.URL)
+	statusResp, err := client.StatusWithResponse(ctx)
+	//rsp := statusResp.HTTPResponse
+	//log.Println("gp:", rsp.Header, strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200)
+	//var gg interface{}
+	//_ = json.Unmarshal(statusResp.Body, &gg)
+	//log.Println(statusResp.JSON200, statusResp.StatusCode(), gg)
+	if err != nil || statusResp.JSON200 == nil || !statusResp.JSON200.IsExecutor {
+		return nil, errs.ErrIncorrectParams
+	}
+
+	cmdResp, err := client.GetCommandsWithResponse(ctx)
+	if err != nil || cmdResp.JSON200 == nil {
+		log.Println(err)
+		return nil, errs.ErrIncorrectParams
+	}
+	commands := make([]*entity.NewDeviceDescriptor, len(*cmdResp.JSON200))
+	for i, cmd := range *cmdResp.JSON200 {
+		commands[i] = entity.BuildNewCommandDescriptor(cmd)
+	}
+
+	return s.deviceStoreService.Add(ctx, setupId, entity.DeviceTypeExecutor, newDevice, commands)
 }
 
-func (s *ExecutorService) GetOne(setupId int, id int) (*entity.Device, error) {
-	return s.deviceStoreService.GetOne(setupId, entity.DeviceTypeExecutor, id)
+func (s *ExecutorService) GetOne(ctx context.Context, setupId int, id int) (*entity.ExtendedDeviceModel, error) {
+	return s.deviceStoreService.GetOne(ctx, setupId, entity.DeviceTypeExecutor, id)
 }
 
-func (s *ExecutorService) Update(setupId int, id int, updDevice *entity.UpdDevice) (*entity.Device, error) {
-	return s.deviceStoreService.Update(setupId, entity.DeviceTypeExecutor, id, updDevice)
+func (s *ExecutorService) Update(ctx context.Context, setupId int, id int, updDevice *entity.UpdDevice) (*entity.Device, error) {
+	return s.deviceStoreService.Update(ctx, setupId, entity.DeviceTypeExecutor, id, updDevice)
 }
 
-func (s *ExecutorService) Delete(setupId int, id int) error {
-	return s.deviceStoreService.Delete(setupId, entity.DeviceTypeExecutor, id)
+func (s *ExecutorService) Delete(ctx context.Context, setupId int, id int) error {
+	return s.deviceStoreService.Delete(ctx, setupId, entity.DeviceTypeExecutor, id)
 }
diff --git a/internal/services/manipulator_service.go b/internal/services/manipulator_service.go
index a89f961..9096061 100644
--- a/internal/services/manipulator_service.go
+++ b/internal/services/manipulator_service.go
@@ -15,7 +15,7 @@ func NewManipulatorService(deviceStoreService *DeviceStoreService) *ManipulatorS
 }
 
 func (s *ManipulatorService) GetList(setupId int) ([]*entity.Device, error) {
-	return s.deviceStoreService.GetBySetupAndType(setupId, entity.DeviceTypeManipulator)
+	return s.deviceStoreService.GetBySetupAndType(nil, setupId, entity.DeviceTypeManipulator)
 }
 
 func (s *ManipulatorService) CheckId(ExecutorIdStr string) (int, error) {
@@ -27,17 +27,17 @@ func (s *ManipulatorService) CheckId(ExecutorIdStr string) (int, error) {
 }
 
 func (s *ManipulatorService) Add(setupId int, newDevice *entity.NewDevice) (*entity.Device, error) {
-	return s.deviceStoreService.Add(setupId, entity.DeviceTypeManipulator, newDevice)
+	return s.deviceStoreService.Add(nil, setupId, entity.DeviceTypeManipulator, newDevice, nil)
 }
 
-func (s *ManipulatorService) GetOne(setupId int, id int) (*entity.Device, error) {
-	return s.deviceStoreService.GetOne(setupId, entity.DeviceTypeManipulator, id)
+func (s *ManipulatorService) GetOne(setupId int, id int) (*entity.ExtendedDeviceModel, error) {
+	return s.deviceStoreService.GetOne(nil, setupId, entity.DeviceTypeManipulator, id)
 }
 
 func (s *ManipulatorService) Update(setupId int, id int, updDevice *entity.UpdDevice) (*entity.Device, error) {
-	return s.deviceStoreService.Update(setupId, entity.DeviceTypeManipulator, id, updDevice)
+	return s.deviceStoreService.Update(nil, setupId, entity.DeviceTypeManipulator, id, updDevice)
 }
 
 func (s *ManipulatorService) Delete(setupId int, id int) error {
-	return s.deviceStoreService.Delete(setupId, entity.DeviceTypeManipulator, id)
+	return s.deviceStoreService.Delete(nil, setupId, entity.DeviceTypeManipulator, id)
 }
-- 
GitLab


From 8b9b94c97040a2004140d9c45ad6ba8706ac6ac0 Mon Sep 17 00:00:00 2001
From: Sergei Loshkarev <saloshkarev@miem.hse.ru>
Date: Sun, 29 Oct 2023 23:29:17 +0300
Subject: [PATCH 06/12] oh no....

---
 internal/app/app.go                           |  10 +-
 internal/entity/device.go                     |   4 +-
 internal/entity/device_descriptor.go          |  54 ++++--
 internal/entity/rule.go                       |  98 +++++++---
 internal/handlers/setup_executor_router.go    |  15 +-
 internal/handlers/setup_manipulator_router.go |  56 +++++-
 internal/handlers/setup_rule_router.go        | 132 +++++++++++++
 internal/handlers/setup_util.go               |  17 ++
 internal/handlers/setups_router.go            |  23 ++-
 .../device_descriptor_repository.go           |  38 ++--
 internal/repositories/device_repository.go    |  20 ++
 internal/repositories/rule_repository.go      | 176 +++++++++++++++++-
 internal/services/device_store_service.go     |  51 ++++-
 internal/services/executor_service.go         |  52 +++++-
 internal/services/manipulator_service.go      |  53 +++++-
 internal/services/rule_service.go             |  47 ++++-
 internal/services/setup_service.go            |  23 +++
 pkg/errs/errors.go                            |   3 +
 18 files changed, 759 insertions(+), 113 deletions(-)
 create mode 100644 internal/handlers/setup_rule_router.go

diff --git a/internal/app/app.go b/internal/app/app.go
index 1d71ce9..f6a6136 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -31,21 +31,25 @@ func NewApp(conf *config.Config) *App {
 	setupRepository := repositories.NewSqliteSetupRepository(db)
 	deviceRepository := repositories.NewSqliteDeviceRepository(db)
 	deviceDescriptorRepository := repositories.NewSqliteDeviceDescriptorRepository(db)
+	ruleRepository := repositories.NewSqliteRuleRepository(db)
 
 	setupService := services.NewSetupService(setupRepository)
 	deviceStoreService := services.NewDeviceStoreService(deviceRepository, deviceDescriptorRepository)
 	manipulatorService := services.NewManipulatorService(deviceStoreService)
 	executorService := services.NewExecutorService(deviceStoreService)
+	ruleService := services.NewRuleService(ruleRepository, manipulatorService, executorService)
+	deleteSetupService := services.NewDeleteSetupService(setupService, executorService, manipulatorService)
 
 	httpEngine := gin.Default()
 	httpEngine.Use(transactions.InjectStorage(db))
 
-	routerToApply := []IApplyRouter{
-		handlers.NewSetupRouter(setupService),
+	routersToApply := []IApplyRouter{
+		handlers.NewSetupRouter(setupService, deleteSetupService),
 		handlers.NewSetupManipulatorRouter(setupService, manipulatorService),
 		handlers.NewSetupExecutorRouter(setupService, executorService),
+		handlers.NewSetupRuleRouter(setupService, ruleService),
 	}
-	for _, router := range routerToApply {
+	for _, router := range routersToApply {
 		router.ApplyTo(httpEngine)
 	}
 
diff --git a/internal/entity/device.go b/internal/entity/device.go
index 4dccf29..4509235 100644
--- a/internal/entity/device.go
+++ b/internal/entity/device.go
@@ -30,7 +30,7 @@ type UpdDevice struct {
 
 type ExtendedDeviceModel struct {
 	*Device
-	Signals []*DeviceDescriptor `json:"signals"`
+	Descriptors []*DeviceDescriptor `json:"descriptors"`
 }
 
 func (m *Device) CheckURL() error {
@@ -49,7 +49,7 @@ CREATE TABLE IF NOT EXISTS Device (
 	   name TEXT NOT NULL,
 	   url TEXT NOT NULL,
 
-	   FOREIGN KEY (setup_id) REFERENCES t_setup(id),
+	   FOREIGN KEY (setup_id) REFERENCES Setup(id) ON DELETE CASCADE,
 	   UNIQUE (setup_id, name),
        UNIQUE (setup_id, device_type, url)
 )
diff --git a/internal/entity/device_descriptor.go b/internal/entity/device_descriptor.go
index aba5ba7..f81c5ce 100644
--- a/internal/entity/device_descriptor.go
+++ b/internal/entity/device_descriptor.go
@@ -5,26 +5,58 @@ import (
 	"git.miem.hse.ru/hubman/hubman-lib/client"
 )
 
+type DescriptorArgs map[string]any
+
 type DeviceDescriptor struct {
+	Id          int            `db:"id" json:"id"`
+	DeviceId    int            `db:"device_id" json:"device_id"`
+	Code        string         `db:"code" json:"code"`
+	Description string         `db:"description" json:"description"`
+	Args        DescriptorArgs `json:"args"`
+}
+
+type RawDeviceDescriptor struct {
 	Id          int    `db:"id" json:"id"`
 	DeviceId    int    `db:"device_id" json:"device_id"`
 	Code        string `db:"code" json:"code"`
 	Description string `db:"description" json:"description"`
-	Schema      string `db:"descriptor_schema" json:"schema"`
+	Args        string `db:"args" json:"args"`
 }
 
-type NewDeviceDescriptor struct {
-	Code        string `db:"code" json:"code"`
-	Description string `db:"description" json:"description"`
-	Schema      string `db:"descriptor_schema" json:"schema"`
+func BuildDeviceDescriptorFrom(rawDescriptor *RawDeviceDescriptor) (*DeviceDescriptor, error) {
+	var parsedMap map[string]any
+	err := json.Unmarshal([]byte(rawDescriptor.Args), &parsedMap)
+	if err != nil {
+		return nil, err
+	}
+	return &DeviceDescriptor{
+		Id:          rawDescriptor.Id,
+		DeviceId:    rawDescriptor.DeviceId,
+		Code:        rawDescriptor.Code,
+		Description: rawDescriptor.Description,
+		Args:        parsedMap,
+	}, nil
+}
+
+type DeviceMessageDescriptor struct {
+	Code        string         `db:"code" json:"code"`
+	Description string         `db:"description" json:"description"`
+	Args        DescriptorArgs `db:"args" json:"args"`
+}
+
+func BuildNewCommandDescriptor(data client.CommandDescriptor) *DeviceMessageDescriptor {
+	return &DeviceMessageDescriptor{
+		data.Code,
+		data.Description,
+		data.Args,
+	}
 }
 
-func BuildNewCommandDescriptor(data client.CommandDescriptor) *NewDeviceDescriptor {
-	jsonBytes, _ := json.Marshal(data.Args)
-	return &NewDeviceDescriptor{
+func BuildNewSignalDescriptor(data client.SignalDescriptor) *DeviceMessageDescriptor {
+	return &DeviceMessageDescriptor{
 		data.Code,
 		data.Description,
-		string(jsonBytes),
+		data.Vars,
 	}
 }
 
@@ -34,9 +66,9 @@ const DeviceDescriptorDML = `
 	   device_id INTEGER NOT NULL,
 	   code TEXT NOT NULL,
 	   description TEXT NOT NULL,	   
-	   descriptor_schema TEXT NOT NULL,
+	   args TEXT NOT NULL,
 
-	   FOREIGN KEY (device_id) REFERENCES Device(id),
+	   FOREIGN KEY (device_id) REFERENCES Device(id) ON DELETE CASCADE,
 	   UNIQUE (code, device_id)
 )
 `
diff --git a/internal/entity/rule.go b/internal/entity/rule.go
index 72e7921..88f8775 100644
--- a/internal/entity/rule.go
+++ b/internal/entity/rule.go
@@ -1,50 +1,98 @@
 package entity
 
+import "encoding/json"
+
+type RuleLogic map[string]any
+
 type Rule struct {
-	Id                  int            `db:"id" json:"id"`
-	SetupId             int            `db:"setup_id" json:"setup_id"`
-	ManipulatorId       int            `db:"manipulator_id" json:"manipulator_id"`
-	SignalDescriptorId  int            `db:"signal_descriptor_id" json:"signal_descriptor_id"`
-	ExecutorId          int            `db:"executor_id" json:"executor_id"`
-	CommandDescriptorId int            `db:"command_descriptor_id" json:"command_descriptor_id"`
-	Trigger             string         `db:"trigger" json:"trigger"`
-	Logic               map[string]any `db:"logic" json:"logic"`
+	Id                  int       `db:"id" json:"id"`
+	Name                string    `db:"name" json:"name"`
+	Description         *string   `db:"description" json:"description"`
+	SetupId             int       `db:"setup_id" json:"setup_id"`
+	ManipulatorId       int       `db:"manipulator_id" json:"manipulator_id"`
+	SignalDescriptorId  int       `db:"signal_descriptor_id" json:"signal_descriptor_id"`
+	ExecutorId          int       `db:"executor_id" json:"executor_id"`
+	CommandDescriptorId int       `db:"command_descriptor_id" json:"command_descriptor_id"`
+	Trigger             any       `db:"trigger" json:"trigger"`
+	Logic               RuleLogic `db:"logic" json:"logic"`
+}
+
+type RawRule struct {
+	Id                  int     `db:"id" json:"id"`
+	Name                string  `db:"name" json:"name"`
+	Description         *string `db:"description" json:"description"`
+	SetupId             int     `db:"setup_id" json:"setup_id"`
+	ManipulatorId       int     `db:"manipulator_id" json:"manipulator_id"`
+	SignalDescriptorId  int     `db:"signal_descriptor_id" json:"signal_descriptor_id"`
+	ExecutorId          int     `db:"executor_id" json:"executor_id"`
+	CommandDescriptorId int     `db:"command_descriptor_id" json:"command_descriptor_id"`
+	Trigger             string  `db:"trigger" json:"trigger"`
+	Logic               string  `db:"logic" json:"logic"`
+}
+
+func BuildRuleFrom(rawRule *RawRule) (*Rule, error) {
+	var parsedTrigger any
+	err := json.Unmarshal([]byte(rawRule.Trigger), &parsedTrigger)
+	if err != nil {
+		return nil, err
+	}
+	var parsedLogic RuleLogic
+	err = json.Unmarshal([]byte(rawRule.Logic), &parsedLogic)
+	if err != nil {
+		return nil, err
+	}
+	return &Rule{
+		Id:                  rawRule.Id,
+		Name:                rawRule.Name,
+		Description:         rawRule.Description,
+		SetupId:             rawRule.SetupId,
+		ManipulatorId:       rawRule.ManipulatorId,
+		SignalDescriptorId:  rawRule.SignalDescriptorId,
+		ExecutorId:          rawRule.ExecutorId,
+		CommandDescriptorId: rawRule.CommandDescriptorId,
+		Trigger:             parsedTrigger,
+		Logic:               parsedLogic,
+	}, nil
 }
 
 type NewRule struct {
-	ManipulatorId       int            `db:"manipulator_id" json:"manipulator_id"`
-	SignalDescriptorId  int            `db:"signal_descriptor_id" json:"signal_descriptor_id"`
-	ExecutorId          int            `db:"executor_id" json:"executor_id"`
-	CommandDescriptorId int            `db:"command_descriptor_id" json:"command_descriptor_id"`
-	Trigger             string         `db:"trigger" json:"trigger"`
-	Logic               map[string]any `db:"logic" json:"logic"`
+	Name                string    `db:"name" json:"name"`
+	Description         *string   `db:"description" json:"description"`
+	ManipulatorId       int       `db:"manipulator_id" json:"manipulator_id"`
+	SignalDescriptorId  int       `db:"signal_descriptor_id" json:"signal_descriptor_id"`
+	ExecutorId          int       `db:"executor_id" json:"executor_id"`
+	CommandDescriptorId int       `db:"command_descriptor_id" json:"command_descriptor_id"`
+	Trigger             any       `db:"trigger" json:"trigger"`
+	Logic               RuleLogic `db:"logic" json:"logic"`
 }
 
 type UpdRule struct {
-	ManipulatorId       *int            `db:"manipulator_id" json:"manipulator_id"`
-	SignalDescriptorId  *int            `db:"signal_descriptor_id" json:"signal_descriptor_id"`
-	ExecutorId          *int            `db:"executor_id" json:"executor_id"`
-	CommandDescriptorId *int            `db:"command_descriptor_id" json:"command_descriptor_id"`
-	Trigger             *string         `db:"trigger" json:"trigger"`
-	Logic               *map[string]any `db:"logic" json:"logic"`
+	Name                *string    `db:"name" json:"name"`
+	Description         *string    `db:"description" json:"description"`
+	ManipulatorId       *int       `db:"manipulator_id" json:"manipulator_id"`
+	SignalDescriptorId  *int       `db:"signal_descriptor_id" json:"signal_descriptor_id"`
+	ExecutorId          *int       `db:"executor_id" json:"executor_id"`
+	CommandDescriptorId *int       `db:"command_descriptor_id" json:"command_descriptor_id"`
+	Trigger             *string    `db:"trigger" json:"trigger"`
+	Logic               *RuleLogic `db:"logic" json:"logic"`
 }
 
 const RuleDML = `
 	CREATE TABLE IF NOT EXISTS Rule (
 	   id INTEGER PRIMARY KEY AUTOINCREMENT,
 	   name TEXT NOT NULL,
-	   description TEXT NOT NULL,
+	   description TEXT,
 	   setup_id INTEGER NOT NULL,
 	   manipulator_id INTEGER NOT NULL,
 	   signal_descriptor_id INTEGER NOT NULL,
 	   executor_id INTEGER NOT NULL,
 	   command_descriptor_id INTEGER NOT NULL,
-	   trigger TEXT NOT NULL CHECK ( json(trigger) ),
-	   logic TEXT NOT NULL CHECK ( json(logic) ),
+	   trigger TEXT NOT NULL,
+	   logic TEXT NOT NULL,
 
 	   FOREIGN KEY (setup_id) REFERENCES Setup(id),
-	   FOREIGN KEY (manipulator_id) REFERENCES Setup(id),
-	   FOREIGN KEY (executor_id) REFERENCES Setup(id),
+	   FOREIGN KEY (manipulator_id) REFERENCES Device(id),
+	   FOREIGN KEY (executor_id) REFERENCES Device(id),
 	   FOREIGN KEY (executor_id, command_descriptor_id) REFERENCES DeviceDescriptor(device_id, id),
 	   FOREIGN KEY (manipulator_id, signal_descriptor_id) REFERENCES DeviceDescriptor(device_id, id)
 )
diff --git a/internal/handlers/setup_executor_router.go b/internal/handlers/setup_executor_router.go
index d9440ce..c587686 100644
--- a/internal/handlers/setup_executor_router.go
+++ b/internal/handlers/setup_executor_router.go
@@ -91,6 +91,7 @@ func (r *SetupExecutorRouter) Patch(c *gin.Context) {
 		BuildErrResp(c, err)
 		return
 	}
+	ctx = transactions.InjectTx(ctx, tx)
 	defer tx.Rollback()
 
 	executor, err := r.executorService.Update(ctx, getSetupId(c), getExecutorId(c), updDevice)
@@ -108,10 +109,22 @@ func (r *SetupExecutorRouter) Patch(c *gin.Context) {
 
 func (r *SetupExecutorRouter) Delete(c *gin.Context) {
 	ctx := c.Request.Context()
-	err := r.executorService.Delete(ctx, getSetupId(c), getExecutorId(c))
+	tx, err := transactions.ExtractStorage(c).Beginx()
 	if err != nil {
 		BuildErrResp(c, err)
 		return
 	}
+	ctx = transactions.InjectTx(ctx, tx)
+	defer tx.Rollback()
+
+	err = r.executorService.Delete(ctx, getSetupId(c), getExecutorId(c))
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	if err := tx.Commit(); err != nil {
+		BuildErrResp(c, errs.ErrInternalError)
+		return
+	}
 	c.Status(http.StatusOK)
 }
diff --git a/internal/handlers/setup_manipulator_router.go b/internal/handlers/setup_manipulator_router.go
index 36444c8..042481e 100644
--- a/internal/handlers/setup_manipulator_router.go
+++ b/internal/handlers/setup_manipulator_router.go
@@ -3,6 +3,8 @@ package handlers
 import (
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
 	"git.miem.hse.ru/hubman/configurator/internal/services"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"git.miem.hse.ru/hubman/configurator/pkg/transactions"
 	"github.com/gin-gonic/gin"
 	"net/http"
 )
@@ -35,7 +37,8 @@ func NewSetupManipulatorRouter(setupService *services.SetupService, manipulatorS
 }
 
 func (r *SetupManipulatorRouter) GetList(c *gin.Context) {
-	executors, err := r.manipulatorService.GetList(getSetupId(c))
+	ctx := c.Request.Context()
+	executors, err := r.manipulatorService.GetList(ctx, getSetupId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
@@ -44,20 +47,34 @@ func (r *SetupManipulatorRouter) GetList(c *gin.Context) {
 }
 
 func (r *SetupManipulatorRouter) Post(c *gin.Context) {
+	ctx := c.Request.Context()
 	newDevice := &entity.NewDevice{}
 	if err := c.BindJSON(newDevice); err != nil {
 		return
 	}
-	executor, err := r.manipulatorService.Add(getSetupId(c), newDevice)
+	tx, err := transactions.ExtractStorage(c).Beginx()
 	if err != nil {
 		BuildErrResp(c, err)
 		return
 	}
+	ctx = transactions.InjectTx(ctx, tx)
+	defer tx.Rollback()
+	executor, err := r.manipulatorService.Add(ctx, getSetupId(c), newDevice)
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+
+	if err := tx.Commit(); err != nil {
+		BuildErrResp(c, errs.ErrInternalError)
+		return
+	}
 	c.JSON(http.StatusOK, executor)
 }
 
 func (r *SetupManipulatorRouter) Get(c *gin.Context) {
-	manipulator, err := r.manipulatorService.GetOne(getSetupId(c), GetManipulatorId(c))
+	ctx := c.Request.Context()
+	manipulator, err := r.manipulatorService.GetOne(ctx, getSetupId(c), GetManipulatorId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
@@ -66,23 +83,52 @@ func (r *SetupManipulatorRouter) Get(c *gin.Context) {
 }
 
 func (r *SetupManipulatorRouter) Patch(c *gin.Context) {
+	ctx := c.Request.Context()
+
 	updDevice := &entity.UpdDevice{}
 	if err := c.BindJSON(updDevice); err != nil {
 		return
 	}
-	manipulator, err := r.manipulatorService.Update(getSetupId(c), GetManipulatorId(c), updDevice)
+	tx, err := transactions.ExtractStorage(c).Beginx()
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	ctx = transactions.InjectTx(ctx, tx)
+	defer tx.Rollback()
+
+	manipulator, err := r.manipulatorService.Update(ctx, getSetupId(c), GetManipulatorId(c), updDevice)
 	if err != nil {
 		BuildErrResp(c, err)
 		return
 	}
+
+	if err := tx.Commit(); err != nil {
+		BuildErrResp(c, errs.ErrInternalError)
+		return
+	}
 	c.JSON(http.StatusOK, manipulator)
 }
 
 func (r *SetupManipulatorRouter) Delete(c *gin.Context) {
-	err := r.manipulatorService.Delete(getSetupId(c), GetManipulatorId(c))
+	ctx := c.Request.Context()
+	tx, err := transactions.ExtractStorage(c).Beginx()
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	ctx = transactions.InjectTx(ctx, tx)
+	defer tx.Rollback()
+
+	err = r.manipulatorService.Delete(ctx, getSetupId(c), GetManipulatorId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
 	}
+
+	if err := tx.Commit(); err != nil {
+		BuildErrResp(c, errs.ErrInternalError)
+		return
+	}
 	c.Status(http.StatusOK)
 }
diff --git a/internal/handlers/setup_rule_router.go b/internal/handlers/setup_rule_router.go
new file mode 100644
index 0000000..d6c1c98
--- /dev/null
+++ b/internal/handlers/setup_rule_router.go
@@ -0,0 +1,132 @@
+package handlers
+
+import (
+	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/internal/services"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"git.miem.hse.ru/hubman/configurator/pkg/transactions"
+	"github.com/gin-gonic/gin"
+	"net/http"
+)
+
+type SetupRuleRouter struct {
+	setupService *services.SetupService
+	ruleService  *services.RuleService
+}
+
+func NewSetupRuleRouter(
+	setupService *services.SetupService,
+	ruleService *services.RuleService,
+) *SetupRuleRouter {
+	return &SetupRuleRouter{setupService, ruleService}
+}
+
+func (r *SetupRuleRouter) ApplyTo(httpEngine gin.IRouter) {
+	setupExecutorNS := httpEngine.Group("/setups/:setup_id/rules")
+	setupExecutorNS.Use(CheckSetupId(r.setupService))
+	{
+		setupExecutorNS.GET("/", r.GetList)
+		setupExecutorNS.POST("/", r.Post)
+
+		executoredNS := setupExecutorNS.Group("/:rule_id")
+		executoredNS.Use(CheckRuleId(r.ruleService))
+		executoredNS.GET("/", r.Get)
+		executoredNS.PATCH("/", r.Patch)
+		executoredNS.DELETE("/", r.Delete)
+	}
+}
+
+func (r *SetupRuleRouter) GetList(c *gin.Context) {
+	ctx := c.Request.Context()
+	rules, err := r.ruleService.GetBySetupId(ctx, getSetupId(c))
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, rules)
+}
+
+func (r *SetupRuleRouter) Post(c *gin.Context) {
+	ctx := c.Request.Context()
+	newRule := &entity.NewRule{}
+	if err := c.BindJSON(newRule); err != nil {
+		return
+	}
+	tx, err := transactions.ExtractStorage(c).Beginx()
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	ctx = transactions.InjectTx(ctx, tx)
+	defer tx.Rollback()
+	rule, err := r.ruleService.Add(ctx, getSetupId(c), newRule)
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	if err := tx.Commit(); err != nil {
+		BuildErrResp(c, errs.ErrInternalError)
+		return
+	}
+	c.JSON(http.StatusOK, rule)
+}
+
+func (r *SetupRuleRouter) Get(c *gin.Context) {
+	ctx := c.Request.Context()
+	rule, err := r.ruleService.GetOne(ctx, getSetupId(c), getRuleId(c))
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	c.JSON(http.StatusOK, rule)
+}
+
+func (r *SetupRuleRouter) Patch(c *gin.Context) {
+	ctx := c.Request.Context()
+
+	updRule := &entity.UpdRule{}
+	if err := c.BindJSON(updRule); err != nil {
+		return
+	}
+	tx, err := transactions.ExtractStorage(c).Beginx()
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	ctx = transactions.InjectTx(ctx, tx)
+	defer tx.Rollback()
+
+	rule, err := r.ruleService.Update(ctx, getSetupId(c), getRuleId(c), updRule)
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+
+	if err := tx.Commit(); err != nil {
+		BuildErrResp(c, errs.ErrInternalError)
+		return
+	}
+	c.JSON(http.StatusOK, rule)
+}
+
+func (r *SetupRuleRouter) Delete(c *gin.Context) {
+	ctx := c.Request.Context()
+	tx, err := transactions.ExtractStorage(c).Beginx()
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	ctx = transactions.InjectTx(ctx, tx)
+	defer tx.Rollback()
+
+	err = r.ruleService.Delete(ctx, getSetupId(c), getRuleId(c))
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	if err := tx.Commit(); err != nil {
+		BuildErrResp(c, errs.ErrInternalError)
+		return
+	}
+	c.Status(http.StatusOK)
+}
diff --git a/internal/handlers/setup_util.go b/internal/handlers/setup_util.go
index 587bc01..b1418de 100644
--- a/internal/handlers/setup_util.go
+++ b/internal/handlers/setup_util.go
@@ -41,6 +41,23 @@ func getExecutorId(c *gin.Context) int {
 	return c.MustGet("executorId").(int)
 }
 
+func CheckRuleId(ruleService *services.RuleService) gin.HandlerFunc {
+	return func(c *gin.Context) {
+		executorId, err := ruleService.CheckId(c.Params.ByName("rule_id"))
+		if err != nil {
+			BuildErrResp(c, err)
+			c.Abort()
+			return
+		}
+		c.Set("ruleId", executorId)
+		return
+	}
+}
+
+func getRuleId(c *gin.Context) int {
+	return c.MustGet("ruleId").(int)
+}
+
 func CheckManipulatorId(manipulatorService *services.ManipulatorService) gin.HandlerFunc {
 	return func(c *gin.Context) {
 		manipulatorId, err := manipulatorService.CheckId(c.Params.ByName("manipulator_id"))
diff --git a/internal/handlers/setups_router.go b/internal/handlers/setups_router.go
index dcd45c9..f023512 100644
--- a/internal/handlers/setups_router.go
+++ b/internal/handlers/setups_router.go
@@ -10,11 +10,12 @@ import (
 )
 
 type SetupRouter struct {
-	setupService *services.SetupService
+	setupService       *services.SetupService
+	deleteSetupService *services.DeleteSetupService
 }
 
-func NewSetupRouter(setupService *services.SetupService) *SetupRouter {
-	return &SetupRouter{setupService: setupService}
+func NewSetupRouter(setupService *services.SetupService, deleteSetupService *services.DeleteSetupService) *SetupRouter {
+	return &SetupRouter{setupService, deleteSetupService}
 }
 
 func (r *SetupRouter) ApplyTo(httpEngine gin.IRouter) {
@@ -63,11 +64,23 @@ func (r *SetupRouter) Get(c *gin.Context) {
 
 func (r *SetupRouter) Delete(c *gin.Context) {
 	ctx := c.Request.Context()
-	err := r.setupService.Delete(ctx, getSetupId(c))
+	tx, err := transactions.ExtractStorage(c).Beginx()
+	if err != nil {
+		BuildErrResp(c, err)
+		return
+	}
+	ctx = transactions.InjectTx(ctx, tx)
+	defer tx.Rollback()
+
+	err = r.deleteSetupService.Delete(ctx, getSetupId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
 	}
+	if err := tx.Commit(); err != nil {
+		BuildErrResp(c, errs.ErrInternalError)
+		return
+	}
 	c.Status(http.StatusOK)
 }
 
@@ -97,7 +110,7 @@ func (r *SetupRouter) Patch(c *gin.Context) {
 		return
 	}
 
-	if err2 := tx.Commit(); err2 != nil {
+	if err := tx.Commit(); err != nil {
 		BuildErrResp(c, errs.ErrInternalError)
 	}
 	c.JSON(http.StatusOK, setup)
diff --git a/internal/repositories/device_descriptor_repository.go b/internal/repositories/device_descriptor_repository.go
index aa13dda..ce6d9c0 100644
--- a/internal/repositories/device_descriptor_repository.go
+++ b/internal/repositories/device_descriptor_repository.go
@@ -3,6 +3,7 @@ package repositories
 import (
 	"context"
 	"database/sql"
+	"encoding/json"
 	"errors"
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
 	"git.miem.hse.ru/hubman/configurator/pkg/errs"
@@ -12,7 +13,7 @@ import (
 )
 
 type DeviceDescriptorRepository interface {
-	Add(ctx context.Context, deviceId int, newDescriptor *entity.NewDeviceDescriptor) (*entity.DeviceDescriptor, error)
+	Add(ctx context.Context, deviceId int, newDescriptor *entity.DeviceMessageDescriptor) (*entity.DeviceDescriptor, error)
 	DeleteByDeviceId(ctx context.Context, deviceId int) error
 	GetByDeviceId(ctx context.Context, deviceId int) ([]*entity.DeviceDescriptor, error)
 }
@@ -25,18 +26,19 @@ func NewSqliteDeviceDescriptorRepository(db *sqlx.DB) *SqliteDeviceDescriptorRep
 	return &SqliteDeviceDescriptorRepository{db: db}
 }
 
-func (r *SqliteDeviceDescriptorRepository) Add(ctx context.Context, deviceId int, newDescriptor *entity.NewDeviceDescriptor) (*entity.DeviceDescriptor, error) {
+func (r *SqliteDeviceDescriptorRepository) Add(ctx context.Context, deviceId int, newDescriptor *entity.DeviceMessageDescriptor) (*entity.DeviceDescriptor, error) {
 	rtx, err := extractSqlxTxOrNew(ctx, r.db)
 	if err != nil {
 		return nil, err
 	}
 	defer rtx.RollbackTxOrIgnore()
 
+	argsLine, _ := json.Marshal(newDescriptor.Args)
 	res, err := sq.Insert("DeviceDescriptor").SetMap(map[string]any{
-		"device_id":         deviceId,
-		"code":              newDescriptor.Code,
-		"description":       newDescriptor.Description,
-		"descriptor_schema": newDescriptor.Schema,
+		"device_id":   deviceId,
+		"code":        newDescriptor.Code,
+		"description": newDescriptor.Description,
+		"args":        string(argsLine),
 	}).RunWith(rtx.tx).ExecContext(ctx)
 	if err != nil {
 		return nil, wrapSqliteDeviceDescriptorError(err)
@@ -54,7 +56,7 @@ func (r *SqliteDeviceDescriptorRepository) Add(ctx context.Context, deviceId int
 		DeviceId:    deviceId,
 		Code:        newDescriptor.Code,
 		Description: newDescriptor.Description,
-		Schema:      newDescriptor.Schema,
+		Args:        newDescriptor.Args,
 	}, nil
 }
 
@@ -68,17 +70,10 @@ func (r *SqliteDeviceDescriptorRepository) DeleteByDeviceId(ctx context.Context,
 	query, params := sq.Delete("DeviceDescriptor").Where(sq.Eq{
 		"device_id": deviceId,
 	}).MustSql()
-	res, err := rtx.tx.ExecContext(ctx, query, params...)
+	_, err = rtx.tx.ExecContext(ctx, query, params...)
 	if err != nil {
 		return wrapSqliteDeviceDescriptorError(err)
 	}
-	affected, err := res.RowsAffected()
-	if err != nil {
-		return errs.ErrInternalError
-	}
-	if affected == 0 {
-		return errs.ErrDeviceNotFound
-	}
 
 	if err := rtx.CommitTxOrIgnore(); err != nil {
 		return errs.ErrInternalError
@@ -96,10 +91,17 @@ func (r *SqliteDeviceDescriptorRepository) GetByDeviceId(ctx context.Context, de
 	query, params := sq.Select("*").From("DeviceDescriptor").Where(sq.Eq{
 		"device_id": deviceId,
 	}).MustSql()
-	var descriptors []*entity.DeviceDescriptor
-	err = rtx.tx.SelectContext(ctx, &descriptors, query, params...)
+	var rawDescriptors []*entity.RawDeviceDescriptor
+	err = rtx.tx.SelectContext(ctx, &rawDescriptors, query, params...)
 	if err != nil {
-		return descriptors, wrapSqliteDeviceDescriptorError(err)
+		return nil, wrapSqliteDeviceDescriptorError(err)
+	}
+	descriptors := make([]*entity.DeviceDescriptor, len(rawDescriptors))
+	for i, d := range rawDescriptors {
+		descriptors[i], err = entity.BuildDeviceDescriptorFrom(d)
+		if err != nil {
+			return nil, errs.ErrInternalError
+		}
 	}
 	return descriptors, nil
 }
diff --git a/internal/repositories/device_repository.go b/internal/repositories/device_repository.go
index 3fc5e2c..a3c8318 100644
--- a/internal/repositories/device_repository.go
+++ b/internal/repositories/device_repository.go
@@ -13,6 +13,7 @@ import (
 
 type DeviceRepository interface {
 	GetBySetupAndType(ctx context.Context, setupId int, dtype entity.DeviceType) ([]*entity.Device, error)
+	DeleteBySetupId(ctx context.Context, setupId int, dtype entity.DeviceType) error
 	GetOne(ctx context.Context, setupId int, deviceType entity.DeviceType, id int) (*entity.Device, error)
 	Add(ctx context.Context, setupId int, deviceType entity.DeviceType, newDevice *entity.NewDevice) (*entity.Device, error)
 	Delete(ctx context.Context, setupId int, dtype entity.DeviceType, id int) error
@@ -82,6 +83,25 @@ func (r *SqliteDeviceRepository) Add(
 	}, nil
 }
 
+func (r *SqliteDeviceRepository) DeleteBySetupId(ctx context.Context, setupId int, dtype entity.DeviceType) error {
+	query, params := sq.Delete("Device").Where(sq.Eq{
+		"setup_id":    setupId,
+		"device_type": dtype,
+	}).MustSql()
+
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
+	_, err = rtx.tx.ExecContext(ctx, query, params...)
+	if err != nil {
+		return wrapSqliteDeviceError(err)
+	}
+	return nil
+}
+
 func (r *SqliteDeviceRepository) GetOne(ctx context.Context, setupId int, dtype entity.DeviceType, id int) (*entity.Device, error) {
 	query, params := sq.Select("*").From("Device").Where(sq.Eq{
 		"id":          id,
diff --git a/internal/repositories/rule_repository.go b/internal/repositories/rule_repository.go
index cdbe341..80637d1 100644
--- a/internal/repositories/rule_repository.go
+++ b/internal/repositories/rule_repository.go
@@ -1,18 +1,184 @@
 package repositories
 
 import (
+	"context"
+	"database/sql"
+	"encoding/json"
+	"errors"
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	sq "github.com/Masterminds/squirrel"
 	"github.com/jmoiron/sqlx"
+	"github.com/mattn/go-sqlite3"
+	"log"
 )
 
 type RuleRepository interface {
-	GetBySetupId(setupId int) ([]*entity.Rule, error)
-	GetById(setupId int, id int) (*entity.Rule, error)
-	Add(setupId int, rule *entity.NewRule) (*entity.Rule, error)
-	Update(setupId int, id int, updSetup *entity.UpdRule) error
-	Delete(setupId int, id int) error
+	GetBySetupId(ctx context.Context, setupId int) ([]*entity.Rule, error)
+	GetOne(ctx context.Context, setupId int, id int) (*entity.Rule, error)
+	Add(ctx context.Context, setupId int, rule *entity.NewRule) (*entity.Rule, error)
+	Update(ctx context.Context, setupId int, id int, updSetup *entity.UpdRule) error
+	Delete(ctx context.Context, setupId int, id int) error
 }
 
 type SqliteRuleRepository struct {
 	db *sqlx.DB
 }
+
+func NewSqliteRuleRepository(db *sqlx.DB) *SqliteRuleRepository {
+	return &SqliteRuleRepository{db: db}
+}
+
+func (r *SqliteRuleRepository) GetBySetupId(ctx context.Context, setupId int) ([]*entity.Rule, error) {
+	query, params := sq.Select("*").From("Rule").Where(sq.Eq{
+		"setup_id": setupId,
+	}).MustSql()
+
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return nil, err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
+	rawRules := make([]*entity.RawRule, 0, 0)
+	err = rtx.tx.SelectContext(ctx, &rawRules, query, params...)
+	log.Println(err)
+	if err != nil {
+		return nil, errs.ErrInternalError
+	}
+	rules := make([]*entity.Rule, len(rawRules))
+	for i, rawRule := range rawRules {
+		rules[i], err = entity.BuildRuleFrom(rawRule)
+		if err != nil {
+			return nil, errs.ErrInternalError
+		}
+
+	}
+	return rules, nil
+}
+
+func (r *SqliteRuleRepository) GetOne(ctx context.Context, setupId int, id int) (*entity.Rule, error) {
+	query, params := sq.Select("*").From("Rule").Where(sq.Eq{
+		"id":       id,
+		"setup_id": setupId,
+	}).MustSql()
+
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return nil, err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
+	row := rtx.tx.QueryRowx(query, params...)
+	if row == nil || row.Err() != nil {
+		return nil, wrapSqliteRuleError(row.Err())
+	}
+	rawRule := new(entity.RawRule)
+	err = row.StructScan(rawRule)
+	if err != nil {
+		return nil, wrapSqliteRuleError(err)
+	}
+	rule, err := entity.BuildRuleFrom(rawRule)
+	if err != nil {
+		return nil, errs.ErrInternalError
+	}
+	return rule, nil
+}
+
+func (r *SqliteRuleRepository) Add(ctx context.Context, setupId int, rule *entity.NewRule) (*entity.Rule, error) {
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return nil, err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
+	trigger, err := json.Marshal(rule.Trigger)
+	if err != nil {
+		return nil, errs.ErrInternalError
+	}
+	logic, err := json.Marshal(rule.Logic)
+	if err != nil {
+		return nil, errs.ErrInternalError
+	}
+	res, err := sq.Insert("Rule").SetMap(map[string]any{
+		"setup_id":              setupId,
+		"name":                  rule.Name,
+		"description":           rule.Description,
+		"manipulator_id":        rule.ManipulatorId,
+		"signal_descriptor_id":  rule.SignalDescriptorId,
+		"executor_id":           rule.ExecutorId,
+		"command_descriptor_id": rule.CommandDescriptorId,
+		"trigger":               string(trigger),
+		"logic":                 string(logic),
+	}).RunWith(rtx.tx).ExecContext(ctx)
+	if err != nil {
+		return nil, wrapSqliteRuleError(err)
+	}
+	id, err := res.LastInsertId()
+	if err != nil {
+		return nil, errs.ErrInternalError
+	}
+	if err := rtx.CommitTxOrIgnore(); err != nil {
+		return nil, errs.ErrInternalError
+	}
+	return &entity.Rule{
+		Id:                  int(id),
+		SetupId:             setupId,
+		Name:                rule.Name,
+		Description:         rule.Description,
+		ManipulatorId:       rule.ManipulatorId,
+		SignalDescriptorId:  rule.SignalDescriptorId,
+		ExecutorId:          rule.ExecutorId,
+		CommandDescriptorId: rule.CommandDescriptorId,
+		Trigger:             rule.Trigger,
+		Logic:               rule.Logic,
+	}, nil
+}
+
+func (r *SqliteRuleRepository) Update(ctx context.Context, setupId int, id int, updSetup *entity.UpdRule) error {
+	//TODO implement me
+	panic("implement me")
+}
+
+func (r *SqliteRuleRepository) Delete(ctx context.Context, setupId int, id int) error {
+	query, params := sq.Delete("Rule").Where(sq.Eq{
+		"setup_id": setupId,
+		"id":       id,
+	}).MustSql()
+
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
+	res, err := rtx.tx.ExecContext(ctx, query, params...)
+	if err != nil {
+		return wrapSqliteRuleError(err)
+	}
+	affected, err := res.RowsAffected()
+	if err != nil {
+		return errs.ErrInternalError
+	}
+	if affected == 0 {
+		return errs.ErrDeviceNotFound
+	}
+	if err := rtx.CommitTxOrIgnore(); err != nil {
+		return errs.ErrInternalError
+	}
+	return nil
+}
+
+func wrapSqliteRuleError(err error) error {
+	if errors.Is(err, sql.ErrNoRows) {
+		return errs.ErrRuleNotFound
+	}
+	var sqliteErr sqlite3.Error
+	if errors.As(err, &sqliteErr) {
+		if errors.Is(sqliteErr.Code, sqlite3.ErrConstraint) {
+			return errs.ErrRuleIncorrectParams
+		}
+		return errs.ErrInternalError
+	}
+	return err
+}
diff --git a/internal/services/device_store_service.go b/internal/services/device_store_service.go
index 5aee100..fe4ff7b 100644
--- a/internal/services/device_store_service.go
+++ b/internal/services/device_store_service.go
@@ -25,22 +25,35 @@ func (s *DeviceStoreService) GetBySetupAndType(ctx context.Context, setupId int,
 	return s.deviceRepository.GetBySetupAndType(ctx, setupId, dtype)
 }
 
+func (s *DeviceStoreService) DeleteBySetupId(ctx context.Context, setupId int, dtype entity.DeviceType) error {
+	devices, err := s.deviceRepository.GetBySetupAndType(ctx, setupId, dtype)
+	if err != nil {
+		return nil
+	}
+	for _, device := range devices {
+		if err := s.deviceDescriptorRepository.DeleteByDeviceId(ctx, device.Id); err != nil {
+			return err
+		}
+	}
+	return s.deviceRepository.DeleteBySetupId(ctx, setupId, dtype)
+}
+
 func (s *DeviceStoreService) Add(
 	ctx context.Context,
 	setupId int, dtype entity.DeviceType, newDevice *entity.NewDevice,
-	commands []*entity.NewDeviceDescriptor,
-) (*entity.Device, error) {
+	newDescriptors []*entity.DeviceMessageDescriptor,
+) (*entity.ExtendedDeviceModel, error) {
 	device, err := s.deviceRepository.Add(ctx, setupId, dtype, newDevice)
 	if err != nil {
 		return nil, err
 	}
-	for _, cmd := range commands {
-		_, err := s.deviceDescriptorRepository.Add(ctx, device.Id, cmd)
-		if err != nil {
-			return nil, err
-		}
+	descriptors, err := s.UpdateDescriptors(ctx, device.Id, newDescriptors)
+	if err != nil {
+		return nil, err
 	}
-	return device, nil
+	return &entity.ExtendedDeviceModel{
+		Device: device, Descriptors: descriptors,
+	}, nil
 }
 
 func (s *DeviceStoreService) GetOne(
@@ -56,14 +69,34 @@ func (s *DeviceStoreService) GetOne(
 		return nil, err
 	}
 	return &entity.ExtendedDeviceModel{
-		Device: device, Signals: descriptors,
+		Device: device, Descriptors: descriptors,
 	}, nil
 }
 
+func (s *DeviceStoreService) UpdateDescriptors(
+	ctx context.Context,
+	deviceId int, newDescriptors []*entity.DeviceMessageDescriptor,
+) ([]*entity.DeviceDescriptor, error) {
+	err := s.deviceDescriptorRepository.DeleteByDeviceId(ctx, deviceId)
+	if err != nil {
+		return nil, err
+	}
+	descriptors := make([]*entity.DeviceDescriptor, len(newDescriptors))
+	for i, cmd := range newDescriptors {
+		d, err := s.deviceDescriptorRepository.Add(ctx, deviceId, cmd)
+		if err != nil {
+			return nil, err
+		}
+		descriptors[i] = d
+	}
+	return descriptors, nil
+}
+
 func (s *DeviceStoreService) Update(
 	ctx context.Context,
 	setupId int, dtype entity.DeviceType, id int, updDevice *entity.UpdDevice,
 ) (*entity.Device, error) {
+
 	if err := s.deviceRepository.Update(ctx, setupId, dtype, id, updDevice); err != nil {
 		return nil, err
 	}
diff --git a/internal/services/executor_service.go b/internal/services/executor_service.go
index 2b2a5c2..7dcab4b 100644
--- a/internal/services/executor_service.go
+++ b/internal/services/executor_service.go
@@ -21,6 +21,10 @@ func (s *ExecutorService) GetList(ctx context.Context, setupId int) ([]*entity.D
 	return s.deviceStoreService.GetBySetupAndType(ctx, setupId, entity.DeviceTypeExecutor)
 }
 
+func (s *ExecutorService) DeleteBySetupId(ctx context.Context, setupId int) error {
+	return s.deviceStoreService.DeleteBySetupId(ctx, setupId, entity.DeviceTypeExecutor)
+}
+
 func (s *ExecutorService) CheckId(ExecutorIdStr string) (int, error) {
 	setupId, err := strconv.Atoi(ExecutorIdStr)
 	if err != nil {
@@ -29,28 +33,37 @@ func (s *ExecutorService) CheckId(ExecutorIdStr string) (int, error) {
 	return setupId, nil
 }
 
-func (s *ExecutorService) Add(ctx context.Context, setupId int, newDevice *entity.NewDevice) (*entity.Device, error) {
-	client, _ := hubmanclient.NewClientWithResponses(newDevice.URL)
+func (s *ExecutorService) getClient(ctx context.Context, url string) (*hubmanclient.ClientWithResponses, error) {
+	client, _ := hubmanclient.NewClientWithResponses(url)
 	statusResp, err := client.StatusWithResponse(ctx)
-	//rsp := statusResp.HTTPResponse
-	//log.Println("gp:", rsp.Header, strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200)
-	//var gg interface{}
-	//_ = json.Unmarshal(statusResp.Body, &gg)
-	//log.Println(statusResp.JSON200, statusResp.StatusCode(), gg)
 	if err != nil || statusResp.JSON200 == nil || !statusResp.JSON200.IsExecutor {
 		return nil, errs.ErrIncorrectParams
 	}
+	return client, nil
+}
 
+func (s *ExecutorService) getCommands(ctx context.Context, client *hubmanclient.ClientWithResponses) ([]*entity.DeviceMessageDescriptor, error) {
 	cmdResp, err := client.GetCommandsWithResponse(ctx)
 	if err != nil || cmdResp.JSON200 == nil {
 		log.Println(err)
 		return nil, errs.ErrIncorrectParams
 	}
-	commands := make([]*entity.NewDeviceDescriptor, len(*cmdResp.JSON200))
+	commands := make([]*entity.DeviceMessageDescriptor, len(*cmdResp.JSON200))
 	for i, cmd := range *cmdResp.JSON200 {
 		commands[i] = entity.BuildNewCommandDescriptor(cmd)
 	}
+	return commands, nil
+}
 
+func (s *ExecutorService) Add(ctx context.Context, setupId int, newDevice *entity.NewDevice) (*entity.ExtendedDeviceModel, error) {
+	client, err := s.getClient(ctx, newDevice.URL)
+	if err != nil {
+		return nil, err
+	}
+	commands, err := s.getCommands(ctx, client)
+	if err != nil {
+		return nil, err
+	}
 	return s.deviceStoreService.Add(ctx, setupId, entity.DeviceTypeExecutor, newDevice, commands)
 }
 
@@ -58,8 +71,27 @@ func (s *ExecutorService) GetOne(ctx context.Context, setupId int, id int) (*ent
 	return s.deviceStoreService.GetOne(ctx, setupId, entity.DeviceTypeExecutor, id)
 }
 
-func (s *ExecutorService) Update(ctx context.Context, setupId int, id int, updDevice *entity.UpdDevice) (*entity.Device, error) {
-	return s.deviceStoreService.Update(ctx, setupId, entity.DeviceTypeExecutor, id, updDevice)
+func (s *ExecutorService) Update(ctx context.Context, setupId, id int, updDevice *entity.UpdDevice) (*entity.ExtendedDeviceModel, error) {
+	executor, err := s.deviceStoreService.GetOne(ctx, setupId, entity.DeviceTypeExecutor, id)
+	if err != nil {
+		return nil, err
+	}
+	if updDevice.URL != nil && executor.URL != *updDevice.URL {
+		client, err := s.getClient(ctx, *updDevice.URL)
+		commands, err := s.getCommands(ctx, client)
+		if err != nil {
+			return nil, err
+		}
+		_, err = s.deviceStoreService.UpdateDescriptors(ctx, id, commands)
+		if err != nil {
+			return nil, err
+		}
+	}
+	device, err := s.deviceStoreService.Update(ctx, setupId, entity.DeviceTypeExecutor, id, updDevice)
+	if err != nil {
+		return nil, err
+	}
+	return s.deviceStoreService.GetOne(ctx, device.SetupId, entity.DeviceTypeExecutor, id)
 }
 
 func (s *ExecutorService) Delete(ctx context.Context, setupId int, id int) error {
diff --git a/internal/services/manipulator_service.go b/internal/services/manipulator_service.go
index 9096061..fb0e65c 100644
--- a/internal/services/manipulator_service.go
+++ b/internal/services/manipulator_service.go
@@ -1,8 +1,11 @@
 package services
 
 import (
+	"context"
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
 	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	hubmanclient "git.miem.hse.ru/hubman/hubman-lib/client"
+	"log"
 	"strconv"
 )
 
@@ -14,8 +17,30 @@ func NewManipulatorService(deviceStoreService *DeviceStoreService) *ManipulatorS
 	return &ManipulatorService{deviceStoreService: deviceStoreService}
 }
 
-func (s *ManipulatorService) GetList(setupId int) ([]*entity.Device, error) {
-	return s.deviceStoreService.GetBySetupAndType(nil, setupId, entity.DeviceTypeManipulator)
+func (s *ManipulatorService) getClient(ctx context.Context, url string) (*hubmanclient.ClientWithResponses, error) {
+	client, _ := hubmanclient.NewClientWithResponses(url)
+	statusResp, err := client.StatusWithResponse(ctx)
+	if err != nil || statusResp.JSON200 == nil || !statusResp.JSON200.IsManipulator {
+		return nil, errs.ErrIncorrectParams
+	}
+	return client, nil
+}
+
+func (s *ManipulatorService) getSignals(ctx context.Context, client *hubmanclient.ClientWithResponses) ([]*entity.DeviceMessageDescriptor, error) {
+	cmdResp, err := client.GetSignalsWithResponse(ctx)
+	if err != nil || cmdResp.JSON200 == nil {
+		log.Println(err)
+		return nil, errs.ErrIncorrectParams
+	}
+	signals := make([]*entity.DeviceMessageDescriptor, len(*cmdResp.JSON200))
+	for i, signal := range *cmdResp.JSON200 {
+		signals[i] = entity.BuildNewSignalDescriptor(signal)
+	}
+	return signals, nil
+}
+
+func (s *ManipulatorService) GetList(ctx context.Context, setupId int) ([]*entity.Device, error) {
+	return s.deviceStoreService.GetBySetupAndType(ctx, setupId, entity.DeviceTypeManipulator)
 }
 
 func (s *ManipulatorService) CheckId(ExecutorIdStr string) (int, error) {
@@ -26,18 +51,26 @@ func (s *ManipulatorService) CheckId(ExecutorIdStr string) (int, error) {
 	return setupId, nil
 }
 
-func (s *ManipulatorService) Add(setupId int, newDevice *entity.NewDevice) (*entity.Device, error) {
-	return s.deviceStoreService.Add(nil, setupId, entity.DeviceTypeManipulator, newDevice, nil)
+func (s *ManipulatorService) Add(ctx context.Context, setupId int, newDevice *entity.NewDevice) (*entity.ExtendedDeviceModel, error) {
+	client, err := s.getClient(ctx, newDevice.URL)
+	if err != nil {
+		return nil, err
+	}
+	signals, err := s.getSignals(ctx, client)
+	if err != nil {
+		return nil, err
+	}
+	return s.deviceStoreService.Add(ctx, setupId, entity.DeviceTypeManipulator, newDevice, signals)
 }
 
-func (s *ManipulatorService) GetOne(setupId int, id int) (*entity.ExtendedDeviceModel, error) {
-	return s.deviceStoreService.GetOne(nil, setupId, entity.DeviceTypeManipulator, id)
+func (s *ManipulatorService) GetOne(ctx context.Context, setupId int, id int) (*entity.ExtendedDeviceModel, error) {
+	return s.deviceStoreService.GetOne(ctx, setupId, entity.DeviceTypeManipulator, id)
 }
 
-func (s *ManipulatorService) Update(setupId int, id int, updDevice *entity.UpdDevice) (*entity.Device, error) {
-	return s.deviceStoreService.Update(nil, setupId, entity.DeviceTypeManipulator, id, updDevice)
+func (s *ManipulatorService) Update(ctx context.Context, setupId int, id int, updDevice *entity.UpdDevice) (*entity.Device, error) {
+	return s.deviceStoreService.Update(ctx, setupId, entity.DeviceTypeManipulator, id, updDevice)
 }
 
-func (s *ManipulatorService) Delete(setupId int, id int) error {
-	return s.deviceStoreService.Delete(nil, setupId, entity.DeviceTypeManipulator, id)
+func (s *ManipulatorService) Delete(ctx context.Context, setupId int, id int) error {
+	return s.deviceStoreService.Delete(ctx, setupId, entity.DeviceTypeManipulator, id)
 }
diff --git a/internal/services/rule_service.go b/internal/services/rule_service.go
index b496493..0e8259b 100644
--- a/internal/services/rule_service.go
+++ b/internal/services/rule_service.go
@@ -1,26 +1,55 @@
 package services
 
 import (
+	"context"
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
 	"git.miem.hse.ru/hubman/configurator/internal/repositories"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"strconv"
 )
 
 type RuleService struct {
-	ruleRepository repositories.RuleRepository
+	ruleRepository     repositories.RuleRepository
+	manipulatorService *ManipulatorService
+	executorService    *ExecutorService
 }
 
-func (s *RuleService) Add(setupId int, newRule *entity.NewRule) (*entity.Rule, error) {
-	return s.ruleRepository.Add(setupId, newRule)
+func NewRuleService(
+	ruleRepository repositories.RuleRepository,
+	manipulatorService *ManipulatorService,
+	executorService *ExecutorService,
+) *RuleService {
+	return &RuleService{ruleRepository: ruleRepository, manipulatorService: manipulatorService, executorService: executorService}
 }
 
-func (s *RuleService) GetBySetupId(setupId int) ([]*entity.Rule, error) {
-	return s.ruleRepository.GetBySetupId(setupId)
+func (s *RuleService) Add(ctx context.Context, setupId int, newRule *entity.NewRule) (*entity.Rule, error) {
+	return s.ruleRepository.Add(ctx, setupId, newRule)
 }
 
-func (s *RuleService) GetById(setupId int, id int) (*entity.Rule, error) {
-	return s.ruleRepository.GetById(setupId, id)
+func (s *RuleService) GetBySetupId(ctx context.Context, setupId int) ([]*entity.Rule, error) {
+	return s.ruleRepository.GetBySetupId(ctx, setupId)
 }
 
-func (s *RuleService) Delete(setupId int, id int) error {
-	return s.ruleRepository.Delete(setupId, id)
+func (s *RuleService) Update(ctx context.Context, setupId int, id int, updRule *entity.UpdRule) (*entity.Rule, error) {
+	err := s.ruleRepository.Update(ctx, setupId, id, updRule)
+	if err != nil {
+		return nil, err
+	}
+	return s.ruleRepository.GetOne(ctx, setupId, id)
+}
+
+func (s *RuleService) GetOne(ctx context.Context, setupId int, id int) (*entity.Rule, error) {
+	return s.ruleRepository.GetOne(ctx, setupId, id)
+}
+
+func (s *RuleService) Delete(ctx context.Context, setupId int, id int) error {
+	return s.ruleRepository.Delete(ctx, setupId, id)
+}
+
+func (s *RuleService) CheckId(RuleIdStr string) (int, error) {
+	ruleId, err := strconv.Atoi(RuleIdStr)
+	if err != nil {
+		return 0, errs.ErrDeviceNotFound
+	}
+	return ruleId, nil
 }
diff --git a/internal/services/setup_service.go b/internal/services/setup_service.go
index 58dcdbd..4917386 100644
--- a/internal/services/setup_service.go
+++ b/internal/services/setup_service.go
@@ -47,3 +47,26 @@ func (s *SetupService) CheckId(setupIdStr string) (int, error) {
 	}
 	return setupId, nil
 }
+
+type DeleteSetupService struct {
+	setupService       *SetupService
+	executorService    *ExecutorService
+	manipulatorService *ManipulatorService
+}
+
+func NewDeleteSetupService(setupService *SetupService, executorService *ExecutorService, manipulatorService *ManipulatorService) *DeleteSetupService {
+	return &DeleteSetupService{setupService: setupService, executorService: executorService, manipulatorService: manipulatorService}
+}
+
+func (s *DeleteSetupService) Delete(ctx context.Context, id int) error {
+	if err := s.executorService.DeleteBySetupId(ctx, id); err != nil {
+		return err
+	}
+	//if err := s.manipulatorService.DeleteBySetupId(ctx, id); err != nil {
+	//	return err
+	//}
+	if err := s.setupService.Delete(ctx, id); err != nil {
+		return err
+	}
+	return nil
+}
diff --git a/pkg/errs/errors.go b/pkg/errs/errors.go
index b8e38c0..6aac47a 100644
--- a/pkg/errs/errors.go
+++ b/pkg/errs/errors.go
@@ -19,4 +19,7 @@ var (
 
 	ErrDeviceDescriptorIncorrectParams = fmt.Errorf("DEVICE_DESCRIPTOR_%w", ErrIncorrectParams)
 	ErrDeviceDescriptorNotFound        = fmt.Errorf("DEVICE_DESCRIPTOR_%w", ErrNotFound)
+
+	ErrRuleIncorrectParams = fmt.Errorf("RULE_%w", ErrIncorrectParams)
+	ErrRuleNotFound        = fmt.Errorf("RULE_%w", ErrNotFound)
 )
-- 
GitLab


From db4df24d2faf5d3f7f797cccb70cc9d35b3c74d7 Mon Sep 17 00:00:00 2001
From: Sergei Loshkarev <saloshkarev@miem.hse.ru>
Date: Mon, 30 Oct 2023 00:57:58 +0300
Subject: [PATCH 07/12] oh no....

---
 go.mod                                        |  1 +
 go.sum                                        |  2 +
 .../device_descriptor_repository.go           | 30 ++++++
 internal/repositories/rule_repository.go      | 97 +++++++++++++++++--
 internal/services/delete_service.go           | 64 ++++++++++++
 internal/services/device_store_service.go     | 14 +++
 internal/services/executor_service.go         |  4 +
 internal/services/manipulator_service.go      |  8 ++
 internal/services/rule_service.go             | 39 ++++++++
 internal/services/setup_service.go            | 23 -----
 10 files changed, 253 insertions(+), 29 deletions(-)
 create mode 100644 internal/services/delete_service.go

diff --git a/go.mod b/go.mod
index 83b0523..857bc00 100644
--- a/go.mod
+++ b/go.mod
@@ -13,6 +13,7 @@ require (
 	github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect
 	github.com/chenzhuoyu/iasm v0.9.0 // indirect
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+	github.com/diegoholiveira/jsonlogic v1.0.0 // indirect
 	github.com/diegoholiveira/jsonlogic/v3 v3.3.0 // indirect
 	github.com/gabriel-vasile/mimetype v1.4.2 // indirect
 	github.com/gin-contrib/sse v0.1.0 // indirect
diff --git a/go.sum b/go.sum
index 9e08dd7..5a6a7e1 100644
--- a/go.sum
+++ b/go.sum
@@ -28,6 +28,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/diegoholiveira/jsonlogic v1.0.0 h1:6ab0Q3563hZj3515knOkrpy8Ui3pgIGoBvy428w1/hA=
+github.com/diegoholiveira/jsonlogic v1.0.0/go.mod h1:jVLve2Xh7aEJep0nNAYlb/Ookjl4qIHcU8TqFPg99lI=
 github.com/diegoholiveira/jsonlogic/v3 v3.3.0 h1:XdIxQ+ICFcQB9tVf46cmiCkc5K9MN8Sh/x+XDHL+iXM=
 github.com/diegoholiveira/jsonlogic/v3 v3.3.0/go.mod h1:9oE8z9G+0OMxOoLHF3fhek3KuqD5CBqM0B6XFL08MSg=
 github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
diff --git a/internal/repositories/device_descriptor_repository.go b/internal/repositories/device_descriptor_repository.go
index ce6d9c0..0f12c76 100644
--- a/internal/repositories/device_descriptor_repository.go
+++ b/internal/repositories/device_descriptor_repository.go
@@ -16,6 +16,7 @@ type DeviceDescriptorRepository interface {
 	Add(ctx context.Context, deviceId int, newDescriptor *entity.DeviceMessageDescriptor) (*entity.DeviceDescriptor, error)
 	DeleteByDeviceId(ctx context.Context, deviceId int) error
 	GetByDeviceId(ctx context.Context, deviceId int) ([]*entity.DeviceDescriptor, error)
+	GetById(ctx context.Context, deviceId, id int) (*entity.DeviceDescriptor, error)
 }
 
 type SqliteDeviceDescriptorRepository struct {
@@ -106,6 +107,35 @@ func (r *SqliteDeviceDescriptorRepository) GetByDeviceId(ctx context.Context, de
 	return descriptors, nil
 }
 
+func (r *SqliteDeviceDescriptorRepository) GetById(ctx context.Context, deviceId, id int) (*entity.DeviceDescriptor, error) {
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return nil, err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
+	query, params := sq.Select("*").From("DeviceDescriptor").Where(sq.Eq{
+		"device_id": deviceId,
+		"id":        id,
+	}).MustSql()
+
+	row := rtx.tx.QueryRowx(query, params...)
+	if row == nil || row.Err() != nil {
+		return nil, wrapSqliteDeviceDescriptorError(row.Err())
+	}
+	deviceDescriptor := new(entity.RawDeviceDescriptor)
+	err = row.StructScan(deviceDescriptor)
+	if err != nil {
+		return nil, wrapSqliteDeviceDescriptorError(err)
+	}
+
+	descriptor, err := entity.BuildDeviceDescriptorFrom(deviceDescriptor)
+	if err != nil {
+		return nil, errs.ErrInternalError
+	}
+	return descriptor, err
+}
+
 func wrapSqliteDeviceDescriptorError(err error) error {
 	if errors.Is(err, sql.ErrNoRows) {
 		return errs.ErrDeviceDescriptorNotFound
diff --git a/internal/repositories/rule_repository.go b/internal/repositories/rule_repository.go
index 80637d1..8baa174 100644
--- a/internal/repositories/rule_repository.go
+++ b/internal/repositories/rule_repository.go
@@ -10,15 +10,17 @@ import (
 	sq "github.com/Masterminds/squirrel"
 	"github.com/jmoiron/sqlx"
 	"github.com/mattn/go-sqlite3"
-	"log"
 )
 
 type RuleRepository interface {
 	GetBySetupId(ctx context.Context, setupId int) ([]*entity.Rule, error)
-	GetOne(ctx context.Context, setupId int, id int) (*entity.Rule, error)
+	GetByManipulatorId(ctx context.Context, setupId, manipulatorId int) ([]*entity.Rule, error)
+	GetByExecutorId(ctx context.Context, setupId, executorId int) ([]*entity.Rule, error)
+	GetOne(ctx context.Context, setupId, id int) (*entity.Rule, error)
 	Add(ctx context.Context, setupId int, rule *entity.NewRule) (*entity.Rule, error)
-	Update(ctx context.Context, setupId int, id int, updSetup *entity.UpdRule) error
-	Delete(ctx context.Context, setupId int, id int) error
+	Update(ctx context.Context, setupId, id int, updSetup *entity.UpdRule) error
+	Delete(ctx context.Context, setupId, id int) error
+	DeleteBySetupId(ctx context.Context, setupId int) error
 }
 
 type SqliteRuleRepository struct {
@@ -29,6 +31,62 @@ func NewSqliteRuleRepository(db *sqlx.DB) *SqliteRuleRepository {
 	return &SqliteRuleRepository{db: db}
 }
 
+func (r *SqliteRuleRepository) GetByManipulatorId(ctx context.Context, setupId, manipulatorId int) ([]*entity.Rule, error) {
+	query, params := sq.Select("*").From("Rule").Where(sq.Eq{
+		"setup_id":       setupId,
+		"manipulator_id": manipulatorId,
+	}).MustSql()
+
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return nil, err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
+	rawRules := make([]*entity.RawRule, 0, 0)
+	err = rtx.tx.SelectContext(ctx, &rawRules, query, params...)
+	if err != nil {
+		return nil, errs.ErrInternalError
+	}
+	rules := make([]*entity.Rule, len(rawRules))
+	for i, rawRule := range rawRules {
+		rules[i], err = entity.BuildRuleFrom(rawRule)
+		if err != nil {
+			return nil, errs.ErrInternalError
+		}
+
+	}
+	return rules, nil
+}
+
+func (r *SqliteRuleRepository) GetByExecutorId(ctx context.Context, setupId, executorId int) ([]*entity.Rule, error) {
+	query, params := sq.Select("*").From("Rule").Where(sq.Eq{
+		"setup_id":    setupId,
+		"executor_id": executorId,
+	}).MustSql()
+
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return nil, err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
+	rawRules := make([]*entity.RawRule, 0, 0)
+	err = rtx.tx.SelectContext(ctx, &rawRules, query, params...)
+	if err != nil {
+		return nil, errs.ErrInternalError
+	}
+	rules := make([]*entity.Rule, len(rawRules))
+	for i, rawRule := range rawRules {
+		rules[i], err = entity.BuildRuleFrom(rawRule)
+		if err != nil {
+			return nil, errs.ErrInternalError
+		}
+
+	}
+	return rules, nil
+}
+
 func (r *SqliteRuleRepository) GetBySetupId(ctx context.Context, setupId int) ([]*entity.Rule, error) {
 	query, params := sq.Select("*").From("Rule").Where(sq.Eq{
 		"setup_id": setupId,
@@ -42,7 +100,6 @@ func (r *SqliteRuleRepository) GetBySetupId(ctx context.Context, setupId int) ([
 
 	rawRules := make([]*entity.RawRule, 0, 0)
 	err = rtx.tx.SelectContext(ctx, &rawRules, query, params...)
-	log.Println(err)
 	if err != nil {
 		return nil, errs.ErrInternalError
 	}
@@ -161,7 +218,35 @@ func (r *SqliteRuleRepository) Delete(ctx context.Context, setupId int, id int)
 		return errs.ErrInternalError
 	}
 	if affected == 0 {
-		return errs.ErrDeviceNotFound
+		return errs.ErrRuleNotFound
+	}
+	if err := rtx.CommitTxOrIgnore(); err != nil {
+		return errs.ErrInternalError
+	}
+	return nil
+}
+
+func (r *SqliteRuleRepository) DeleteBySetupId(ctx context.Context, setupId int) error {
+	query, params := sq.Delete("Rule").Where(sq.Eq{
+		"setup_id": setupId,
+	}).MustSql()
+
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
+	res, err := rtx.tx.ExecContext(ctx, query, params...)
+	if err != nil {
+		return wrapSqliteRuleError(err)
+	}
+	affected, err := res.RowsAffected()
+	if err != nil {
+		return errs.ErrInternalError
+	}
+	if affected == 0 {
+		return errs.ErrRuleNotFound
 	}
 	if err := rtx.CommitTxOrIgnore(); err != nil {
 		return errs.ErrInternalError
diff --git a/internal/services/delete_service.go b/internal/services/delete_service.go
new file mode 100644
index 0000000..ba7c9cd
--- /dev/null
+++ b/internal/services/delete_service.go
@@ -0,0 +1,64 @@
+package services
+
+import (
+	"context"
+	"errors"
+	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+)
+
+type DeleteService struct {
+	setupService       *SetupService
+	executorService    *ExecutorService
+	manipulatorService *ManipulatorService
+	ruleService        *RuleService
+}
+
+func NewDeleteService(
+	setupService *SetupService, executorService *ExecutorService,
+	manipulatorService *ManipulatorService, ruleService *RuleService,
+) *DeleteService {
+	return &DeleteService{
+		setupService:       setupService,
+		executorService:    executorService,
+		manipulatorService: manipulatorService,
+		ruleService:        ruleService,
+	}
+}
+
+func (s *DeleteService) DeleteSetup(ctx context.Context, id int) error {
+	if err := s.ruleService.DeleteBySetupId(ctx, id); err != nil && !errors.Is(err, errs.ErrRuleNotFound) {
+		return err
+	}
+	if err := s.executorService.DeleteBySetupId(ctx, id); err != nil && !errors.Is(err, errs.ErrDeviceNotFound) {
+		return err
+	}
+	if err := s.manipulatorService.DeleteBySetupId(ctx, id); err != nil && !errors.Is(err, errs.ErrDeviceNotFound) {
+		return err
+	}
+	if err := s.setupService.Delete(ctx, id); err != nil {
+		return err
+	}
+	return nil
+}
+
+func (s *DeleteService) DeleteManipulator(ctx context.Context, setupId, id int) error {
+	rules, err := s.ruleService.GetByManipulatorId(ctx, setupId, id)
+	if err != nil {
+		return err
+	}
+	if len(rules) != 0 {
+		return errs.ErrRuleIncorrectParams
+	}
+	return s.manipulatorService.Delete(ctx, setupId, id)
+}
+
+func (s *DeleteService) DeleteExecutor(ctx context.Context, setupId, id int) error {
+	rules, err := s.ruleService.GetByExecutorId(ctx, setupId, id)
+	if err != nil {
+		return err
+	}
+	if len(rules) != 0 {
+		return errs.ErrRuleIncorrectParams
+	}
+	return s.manipulatorService.Delete(ctx, setupId, id)
+}
diff --git a/internal/services/device_store_service.go b/internal/services/device_store_service.go
index fe4ff7b..5bc5f43 100644
--- a/internal/services/device_store_service.go
+++ b/internal/services/device_store_service.go
@@ -73,6 +73,20 @@ func (s *DeviceStoreService) GetOne(
 	}, nil
 }
 
+func (s *DeviceStoreService) GetOneDescriptor(
+	ctx context.Context, setupId, deviceId, id int, dtype entity.DeviceType,
+) (*entity.DeviceDescriptor, error) {
+	_, err := s.deviceRepository.GetOne(ctx, setupId, dtype, id)
+	if err != nil {
+		return nil, err
+	}
+	descriptor, err := s.deviceDescriptorRepository.GetById(ctx, deviceId, id)
+	if err != nil {
+		return nil, err
+	}
+	return descriptor, nil
+}
+
 func (s *DeviceStoreService) UpdateDescriptors(
 	ctx context.Context,
 	deviceId int, newDescriptors []*entity.DeviceMessageDescriptor,
diff --git a/internal/services/executor_service.go b/internal/services/executor_service.go
index 7dcab4b..f820ca4 100644
--- a/internal/services/executor_service.go
+++ b/internal/services/executor_service.go
@@ -42,6 +42,10 @@ func (s *ExecutorService) getClient(ctx context.Context, url string) (*hubmancli
 	return client, nil
 }
 
+func (s *ExecutorService) GetCommandById(ctx context.Context, setupId, deviceId, descriptorId int) (*entity.DeviceDescriptor, error) {
+	return s.deviceStoreService.GetOneDescriptor(ctx, setupId, deviceId, descriptorId, entity.DeviceTypeManipulator)
+}
+
 func (s *ExecutorService) getCommands(ctx context.Context, client *hubmanclient.ClientWithResponses) ([]*entity.DeviceMessageDescriptor, error) {
 	cmdResp, err := client.GetCommandsWithResponse(ctx)
 	if err != nil || cmdResp.JSON200 == nil {
diff --git a/internal/services/manipulator_service.go b/internal/services/manipulator_service.go
index fb0e65c..1f1a4ef 100644
--- a/internal/services/manipulator_service.go
+++ b/internal/services/manipulator_service.go
@@ -39,6 +39,10 @@ func (s *ManipulatorService) getSignals(ctx context.Context, client *hubmanclien
 	return signals, nil
 }
 
+func (s *ManipulatorService) GetSignalById(ctx context.Context, setupId, deviceId, descriptorId int) (*entity.DeviceDescriptor, error) {
+	return s.deviceStoreService.GetOneDescriptor(ctx, setupId, deviceId, descriptorId, entity.DeviceTypeManipulator)
+}
+
 func (s *ManipulatorService) GetList(ctx context.Context, setupId int) ([]*entity.Device, error) {
 	return s.deviceStoreService.GetBySetupAndType(ctx, setupId, entity.DeviceTypeManipulator)
 }
@@ -74,3 +78,7 @@ func (s *ManipulatorService) Update(ctx context.Context, setupId int, id int, up
 func (s *ManipulatorService) Delete(ctx context.Context, setupId int, id int) error {
 	return s.deviceStoreService.Delete(ctx, setupId, entity.DeviceTypeManipulator, id)
 }
+
+func (s *ManipulatorService) DeleteBySetupId(ctx context.Context, setupId int) error {
+	return s.deviceStoreService.DeleteBySetupId(ctx, setupId, entity.DeviceTypeExecutor)
+}
diff --git a/internal/services/rule_service.go b/internal/services/rule_service.go
index 0e8259b..4b5ca80 100644
--- a/internal/services/rule_service.go
+++ b/internal/services/rule_service.go
@@ -5,6 +5,7 @@ import (
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
 	"git.miem.hse.ru/hubman/configurator/internal/repositories"
 	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"github.com/diegoholiveira/jsonlogic"
 	"strconv"
 )
 
@@ -22,7 +23,33 @@ func NewRuleService(
 	return &RuleService{ruleRepository: ruleRepository, manipulatorService: manipulatorService, executorService: executorService}
 }
 
+func (s *RuleService) validLogic(trigger any, logic entity.RuleLogic) error {
+	// TODO: Добавить проверку на использоуемые переменные. Да рефлект, да придется либу патчить/контрибутить
+	// TODO: Добавить проверку того, что прописаны правила для полей внутри logic
+	if !jsonlogic.IsValid(trigger) {
+		return errs.ErrRuleIncorrectParams
+	}
+	for _, logicStatement := range logic {
+		if !jsonlogic.IsValid(logicStatement) {
+			return errs.ErrRuleIncorrectParams
+		}
+	}
+	return nil
+}
+
 func (s *RuleService) Add(ctx context.Context, setupId int, newRule *entity.NewRule) (*entity.Rule, error) {
+	// TODO: Статус-коды неправильные, нужно что-то глобальное придумать
+	_, err := s.manipulatorService.GetSignalById(ctx, setupId, newRule.ManipulatorId, newRule.SignalDescriptorId)
+	if err != nil {
+		return nil, err
+	}
+	_, err = s.executorService.GetCommandById(ctx, setupId, newRule.ManipulatorId, newRule.SignalDescriptorId)
+	if err != nil {
+		return nil, err
+	}
+	if err := s.validLogic(newRule.Trigger, newRule.Logic); err != nil {
+		return nil, err
+	}
 	return s.ruleRepository.Add(ctx, setupId, newRule)
 }
 
@@ -30,6 +57,14 @@ func (s *RuleService) GetBySetupId(ctx context.Context, setupId int) ([]*entity.
 	return s.ruleRepository.GetBySetupId(ctx, setupId)
 }
 
+func (s *RuleService) GetByManipulatorId(ctx context.Context, setupId, manipulatorId int) ([]*entity.Rule, error) {
+	return s.ruleRepository.GetByManipulatorId(ctx, setupId, manipulatorId)
+}
+
+func (s *RuleService) GetByExecutorId(ctx context.Context, setupId, executorId int) ([]*entity.Rule, error) {
+	return s.ruleRepository.GetByExecutorId(ctx, setupId, executorId)
+}
+
 func (s *RuleService) Update(ctx context.Context, setupId int, id int, updRule *entity.UpdRule) (*entity.Rule, error) {
 	err := s.ruleRepository.Update(ctx, setupId, id, updRule)
 	if err != nil {
@@ -46,6 +81,10 @@ func (s *RuleService) Delete(ctx context.Context, setupId int, id int) error {
 	return s.ruleRepository.Delete(ctx, setupId, id)
 }
 
+func (s *RuleService) DeleteBySetupId(ctx context.Context, setupId int) error {
+	return s.ruleRepository.DeleteBySetupId(ctx, setupId)
+}
+
 func (s *RuleService) CheckId(RuleIdStr string) (int, error) {
 	ruleId, err := strconv.Atoi(RuleIdStr)
 	if err != nil {
diff --git a/internal/services/setup_service.go b/internal/services/setup_service.go
index 4917386..58dcdbd 100644
--- a/internal/services/setup_service.go
+++ b/internal/services/setup_service.go
@@ -47,26 +47,3 @@ func (s *SetupService) CheckId(setupIdStr string) (int, error) {
 	}
 	return setupId, nil
 }
-
-type DeleteSetupService struct {
-	setupService       *SetupService
-	executorService    *ExecutorService
-	manipulatorService *ManipulatorService
-}
-
-func NewDeleteSetupService(setupService *SetupService, executorService *ExecutorService, manipulatorService *ManipulatorService) *DeleteSetupService {
-	return &DeleteSetupService{setupService: setupService, executorService: executorService, manipulatorService: manipulatorService}
-}
-
-func (s *DeleteSetupService) Delete(ctx context.Context, id int) error {
-	if err := s.executorService.DeleteBySetupId(ctx, id); err != nil {
-		return err
-	}
-	//if err := s.manipulatorService.DeleteBySetupId(ctx, id); err != nil {
-	//	return err
-	//}
-	if err := s.setupService.Delete(ctx, id); err != nil {
-		return err
-	}
-	return nil
-}
-- 
GitLab


From c6b8a5e05417fe12fa810076d991195a7e167194 Mon Sep 17 00:00:00 2001
From: Sergei Loshkarev <saloshkarev@miem.hse.ru>
Date: Mon, 30 Oct 2023 01:44:07 +0300
Subject: [PATCH 08/12] oh no....

---
 internal/app/app.go                           |  6 +-
 internal/handlers/setup_executor_router.go    | 15 +++-
 internal/handlers/setup_manipulator_router.go | 14 ++--
 internal/handlers/setups_router.go            |  8 +--
 internal/repositories/rule_repository.go      | 68 +++++++++++++++++--
 internal/services/delete_service.go           |  4 +-
 internal/services/rule_service.go             | 23 ++++++-
 7 files changed, 117 insertions(+), 21 deletions(-)

diff --git a/internal/app/app.go b/internal/app/app.go
index f6a6136..fcfcab8 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -38,15 +38,15 @@ func NewApp(conf *config.Config) *App {
 	manipulatorService := services.NewManipulatorService(deviceStoreService)
 	executorService := services.NewExecutorService(deviceStoreService)
 	ruleService := services.NewRuleService(ruleRepository, manipulatorService, executorService)
-	deleteSetupService := services.NewDeleteSetupService(setupService, executorService, manipulatorService)
+	deleteSetupService := services.NewDeleteService(setupService, executorService, manipulatorService, ruleService)
 
 	httpEngine := gin.Default()
 	httpEngine.Use(transactions.InjectStorage(db))
 
 	routersToApply := []IApplyRouter{
 		handlers.NewSetupRouter(setupService, deleteSetupService),
-		handlers.NewSetupManipulatorRouter(setupService, manipulatorService),
-		handlers.NewSetupExecutorRouter(setupService, executorService),
+		handlers.NewSetupManipulatorRouter(setupService, manipulatorService, deleteSetupService),
+		handlers.NewSetupExecutorRouter(setupService, executorService, deleteSetupService),
 		handlers.NewSetupRuleRouter(setupService, ruleService),
 	}
 	for _, router := range routersToApply {
diff --git a/internal/handlers/setup_executor_router.go b/internal/handlers/setup_executor_router.go
index c587686..d3ad37b 100644
--- a/internal/handlers/setup_executor_router.go
+++ b/internal/handlers/setup_executor_router.go
@@ -12,10 +12,19 @@ import (
 type SetupExecutorRouter struct {
 	setupService    *services.SetupService
 	executorService *services.ExecutorService
+	deleteService   *services.DeleteService
 }
 
-func NewSetupExecutorRouter(setupService *services.SetupService, executorService *services.ExecutorService) *SetupExecutorRouter {
-	return &SetupExecutorRouter{setupService, executorService}
+func NewSetupExecutorRouter(
+	setupService *services.SetupService,
+	executorService *services.ExecutorService,
+	deleteService *services.DeleteService,
+) *SetupExecutorRouter {
+	return &SetupExecutorRouter{
+		setupService:    setupService,
+		executorService: executorService,
+		deleteService:   deleteService,
+	}
 }
 
 func (r *SetupExecutorRouter) ApplyTo(httpEngine gin.IRouter) {
@@ -117,7 +126,7 @@ func (r *SetupExecutorRouter) Delete(c *gin.Context) {
 	ctx = transactions.InjectTx(ctx, tx)
 	defer tx.Rollback()
 
-	err = r.executorService.Delete(ctx, getSetupId(c), getExecutorId(c))
+	err = r.deleteService.DeleteExecutor(ctx, getSetupId(c), getExecutorId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
diff --git a/internal/handlers/setup_manipulator_router.go b/internal/handlers/setup_manipulator_router.go
index 042481e..8c907f6 100644
--- a/internal/handlers/setup_manipulator_router.go
+++ b/internal/handlers/setup_manipulator_router.go
@@ -12,6 +12,7 @@ import (
 type SetupManipulatorRouter struct {
 	setupService       *services.SetupService
 	manipulatorService *services.ManipulatorService
+	deleteService      *services.DeleteService
 }
 
 func (r *SetupManipulatorRouter) ApplyTo(httpEngine gin.IRouter) {
@@ -29,10 +30,15 @@ func (r *SetupManipulatorRouter) ApplyTo(httpEngine gin.IRouter) {
 	}
 }
 
-func NewSetupManipulatorRouter(setupService *services.SetupService, manipulatorService *services.ManipulatorService) *SetupManipulatorRouter {
+func NewSetupManipulatorRouter(
+	setupService *services.SetupService,
+	manipulatorService *services.ManipulatorService,
+	deleteService *services.DeleteService,
+) *SetupManipulatorRouter {
 	return &SetupManipulatorRouter{
-		setupService,
-		manipulatorService,
+		setupService:       setupService,
+		manipulatorService: manipulatorService,
+		deleteService:      deleteService,
 	}
 }
 
@@ -120,7 +126,7 @@ func (r *SetupManipulatorRouter) Delete(c *gin.Context) {
 	ctx = transactions.InjectTx(ctx, tx)
 	defer tx.Rollback()
 
-	err = r.manipulatorService.Delete(ctx, getSetupId(c), GetManipulatorId(c))
+	err = r.deleteService.DeleteManipulator(ctx, getSetupId(c), GetManipulatorId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
diff --git a/internal/handlers/setups_router.go b/internal/handlers/setups_router.go
index f023512..e782014 100644
--- a/internal/handlers/setups_router.go
+++ b/internal/handlers/setups_router.go
@@ -11,11 +11,11 @@ import (
 
 type SetupRouter struct {
 	setupService       *services.SetupService
-	deleteSetupService *services.DeleteSetupService
+	deleteSetupService *services.DeleteService
 }
 
-func NewSetupRouter(setupService *services.SetupService, deleteSetupService *services.DeleteSetupService) *SetupRouter {
-	return &SetupRouter{setupService, deleteSetupService}
+func NewSetupRouter(setupService *services.SetupService, deleteService *services.DeleteService) *SetupRouter {
+	return &SetupRouter{setupService, deleteService}
 }
 
 func (r *SetupRouter) ApplyTo(httpEngine gin.IRouter) {
@@ -72,7 +72,7 @@ func (r *SetupRouter) Delete(c *gin.Context) {
 	ctx = transactions.InjectTx(ctx, tx)
 	defer tx.Rollback()
 
-	err = r.deleteSetupService.Delete(ctx, getSetupId(c))
+	err = r.deleteSetupService.DeleteSetup(ctx, getSetupId(c))
 	if err != nil {
 		BuildErrResp(c, err)
 		return
diff --git a/internal/repositories/rule_repository.go b/internal/repositories/rule_repository.go
index 8baa174..588f206 100644
--- a/internal/repositories/rule_repository.go
+++ b/internal/repositories/rule_repository.go
@@ -18,7 +18,7 @@ type RuleRepository interface {
 	GetByExecutorId(ctx context.Context, setupId, executorId int) ([]*entity.Rule, error)
 	GetOne(ctx context.Context, setupId, id int) (*entity.Rule, error)
 	Add(ctx context.Context, setupId int, rule *entity.NewRule) (*entity.Rule, error)
-	Update(ctx context.Context, setupId, id int, updSetup *entity.UpdRule) error
+	Update(ctx context.Context, setupId, id int, updRule *entity.UpdRule) error
 	Delete(ctx context.Context, setupId, id int) error
 	DeleteBySetupId(ctx context.Context, setupId int) error
 }
@@ -192,9 +192,69 @@ func (r *SqliteRuleRepository) Add(ctx context.Context, setupId int, rule *entit
 	}, nil
 }
 
-func (r *SqliteRuleRepository) Update(ctx context.Context, setupId int, id int, updSetup *entity.UpdRule) error {
-	//TODO implement me
-	panic("implement me")
+func (r *SqliteRuleRepository) Update(ctx context.Context, setupId int, id int, updRule *entity.UpdRule) error {
+	rtx, err := extractSqlxTxOrNew(ctx, r.db)
+	if err != nil {
+		return err
+	}
+	defer rtx.RollbackTxOrIgnore()
+
+	if (updRule == nil) || (*updRule == entity.UpdRule{}) {
+		return errs.ErrRuleIncorrectParams
+	}
+
+	setMap := make(map[string]any)
+	if updRule.Name != nil {
+		setMap["name"] = updRule.Name
+	}
+	if updRule.Description != nil {
+		setMap["description"] = updRule.Description
+	}
+	if updRule.ManipulatorId != nil {
+		setMap["manipulator_id"] = updRule.ManipulatorId
+	}
+	if updRule.SignalDescriptorId != nil {
+		setMap["signal_descriptor_id"] = updRule.SignalDescriptorId
+	}
+	if updRule.ExecutorId != nil {
+		setMap["executor_id"] = updRule.ExecutorId
+	}
+	if updRule.CommandDescriptorId != nil {
+		setMap["command_descriptor_id"] = updRule.CommandDescriptorId
+	}
+	if updRule.Trigger != nil {
+		triggerBytes, err := json.Marshal(updRule.Trigger)
+		if err != nil {
+			return errs.ErrInternalError
+		}
+		setMap["trigger"] = string(triggerBytes)
+	}
+	if updRule.Logic != nil {
+		logicBytes, err := json.Marshal(updRule.Logic)
+		if err != nil {
+			return errs.ErrInternalError
+		}
+		setMap["logic"] = string(logicBytes)
+	}
+
+	query, params := sq.Update("Rule").SetMap(setMap).Where(sq.Eq{
+		"setup_id": setupId, "id": id,
+	}).MustSql()
+	res, err := rtx.tx.Exec(query, params...)
+	if err != nil {
+		return wrapSqliteRuleError(err)
+	}
+	affected, err := res.RowsAffected()
+	if err != nil {
+		return errs.ErrInternalError
+	}
+	if affected == 0 {
+		return errs.ErrRuleNotFound
+	}
+	if err := rtx.CommitTxOrIgnore(); err != nil {
+		return errs.ErrInternalError
+	}
+	return nil
 }
 
 func (r *SqliteRuleRepository) Delete(ctx context.Context, setupId int, id int) error {
diff --git a/internal/services/delete_service.go b/internal/services/delete_service.go
index ba7c9cd..2bf3360 100644
--- a/internal/services/delete_service.go
+++ b/internal/services/delete_service.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"errors"
 	"git.miem.hse.ru/hubman/configurator/pkg/errs"
+	"log"
 )
 
 type DeleteService struct {
@@ -54,11 +55,12 @@ func (s *DeleteService) DeleteManipulator(ctx context.Context, setupId, id int)
 
 func (s *DeleteService) DeleteExecutor(ctx context.Context, setupId, id int) error {
 	rules, err := s.ruleService.GetByExecutorId(ctx, setupId, id)
+	log.Println(rules, err)
 	if err != nil {
 		return err
 	}
 	if len(rules) != 0 {
 		return errs.ErrRuleIncorrectParams
 	}
-	return s.manipulatorService.Delete(ctx, setupId, id)
+	return s.executorService.Delete(ctx, setupId, id)
 }
diff --git a/internal/services/rule_service.go b/internal/services/rule_service.go
index 4b5ca80..a0265e5 100644
--- a/internal/services/rule_service.go
+++ b/internal/services/rule_service.go
@@ -23,12 +23,18 @@ func NewRuleService(
 	return &RuleService{ruleRepository: ruleRepository, manipulatorService: manipulatorService, executorService: executorService}
 }
 
-func (s *RuleService) validLogic(trigger any, logic entity.RuleLogic) error {
+func (s *RuleService) validTrigger(trigger any) error {
 	// TODO: Добавить проверку на использоуемые переменные. Да рефлект, да придется либу патчить/контрибутить
 	// TODO: Добавить проверку того, что прописаны правила для полей внутри logic
 	if !jsonlogic.IsValid(trigger) {
 		return errs.ErrRuleIncorrectParams
 	}
+	return nil
+}
+
+func (s *RuleService) validLogic(logic entity.RuleLogic) error {
+	// TODO: Добавить проверку на использоуемые переменные. Да рефлект, да придется либу патчить/контрибутить
+	// TODO: Добавить проверку того, что прописаны правила для полей внутри logic
 	for _, logicStatement := range logic {
 		if !jsonlogic.IsValid(logicStatement) {
 			return errs.ErrRuleIncorrectParams
@@ -47,7 +53,10 @@ func (s *RuleService) Add(ctx context.Context, setupId int, newRule *entity.NewR
 	if err != nil {
 		return nil, err
 	}
-	if err := s.validLogic(newRule.Trigger, newRule.Logic); err != nil {
+	if err := s.validTrigger(newRule.Trigger); err != nil {
+		return nil, err
+	}
+	if err := s.validLogic(newRule.Logic); err != nil {
 		return nil, err
 	}
 	return s.ruleRepository.Add(ctx, setupId, newRule)
@@ -66,6 +75,16 @@ func (s *RuleService) GetByExecutorId(ctx context.Context, setupId, executorId i
 }
 
 func (s *RuleService) Update(ctx context.Context, setupId int, id int, updRule *entity.UpdRule) (*entity.Rule, error) {
+	if updRule.Trigger != nil {
+		if err := s.validTrigger(updRule.Trigger); err != nil {
+			return nil, err
+		}
+	}
+	if updRule.Trigger != nil {
+		if err := s.validLogic(*updRule.Logic); err != nil {
+			return nil, err
+		}
+	}
 	err := s.ruleRepository.Update(ctx, setupId, id, updRule)
 	if err != nil {
 		return nil, err
-- 
GitLab


From 57e2611e237d124a0aa19cecc96ebb759c522d6a Mon Sep 17 00:00:00 2001
From: Sergei Loshkarev <saloshkarev@miem.hse.ru>
Date: Mon, 30 Oct 2023 12:41:58 +0300
Subject: [PATCH 09/12] fix issues

---
 internal/handlers/utils.go                 | 2 +-
 internal/repositories/device_repository.go | 3 +++
 internal/repositories/rule_repository.go   | 9 +--------
 internal/repositories/setup_repository.go  | 2 --
 internal/services/delete_service.go        | 2 --
 internal/services/executor_service.go      | 2 --
 internal/services/manipulator_service.go   | 2 --
 7 files changed, 5 insertions(+), 17 deletions(-)

diff --git a/internal/handlers/utils.go b/internal/handlers/utils.go
index 3dcc6ec..71efabd 100644
--- a/internal/handlers/utils.go
+++ b/internal/handlers/utils.go
@@ -8,7 +8,7 @@ import (
 )
 
 type ErrorSchema struct {
-	Code string `json:"status"`
+	Code string `json:"err_code"`
 }
 
 func BuildErrResp(c *gin.Context, err error) {
diff --git a/internal/repositories/device_repository.go b/internal/repositories/device_repository.go
index a3c8318..5a74a39 100644
--- a/internal/repositories/device_repository.go
+++ b/internal/repositories/device_repository.go
@@ -99,6 +99,9 @@ func (r *SqliteDeviceRepository) DeleteBySetupId(ctx context.Context, setupId in
 	if err != nil {
 		return wrapSqliteDeviceError(err)
 	}
+	if err := rtx.CommitTxOrIgnore(); err != nil {
+		return errs.ErrInternalError
+	}
 	return nil
 }
 
diff --git a/internal/repositories/rule_repository.go b/internal/repositories/rule_repository.go
index 588f206..1733675 100644
--- a/internal/repositories/rule_repository.go
+++ b/internal/repositories/rule_repository.go
@@ -297,17 +297,10 @@ func (r *SqliteRuleRepository) DeleteBySetupId(ctx context.Context, setupId int)
 	}
 	defer rtx.RollbackTxOrIgnore()
 
-	res, err := rtx.tx.ExecContext(ctx, query, params...)
+	_, err = rtx.tx.ExecContext(ctx, query, params...)
 	if err != nil {
 		return wrapSqliteRuleError(err)
 	}
-	affected, err := res.RowsAffected()
-	if err != nil {
-		return errs.ErrInternalError
-	}
-	if affected == 0 {
-		return errs.ErrRuleNotFound
-	}
 	if err := rtx.CommitTxOrIgnore(); err != nil {
 		return errs.ErrInternalError
 	}
diff --git a/internal/repositories/setup_repository.go b/internal/repositories/setup_repository.go
index 19520da..0a14e42 100644
--- a/internal/repositories/setup_repository.go
+++ b/internal/repositories/setup_repository.go
@@ -9,7 +9,6 @@ import (
 	sq "github.com/Masterminds/squirrel"
 	"github.com/jmoiron/sqlx"
 	"github.com/mattn/go-sqlite3"
-	"log"
 )
 
 type SetupRepository interface {
@@ -127,7 +126,6 @@ func (r SqliteSetupRepository) Add(ctx context.Context, newSetup *entity.NewSetu
 		}
 		return nil, wrapSqliteSetupError(err)
 	}
-	log.Println(err)
 	return &entity.Setup{
 		Id:   int(id),
 		Name: newSetup.Name,
diff --git a/internal/services/delete_service.go b/internal/services/delete_service.go
index 2bf3360..404b270 100644
--- a/internal/services/delete_service.go
+++ b/internal/services/delete_service.go
@@ -4,7 +4,6 @@ import (
 	"context"
 	"errors"
 	"git.miem.hse.ru/hubman/configurator/pkg/errs"
-	"log"
 )
 
 type DeleteService struct {
@@ -55,7 +54,6 @@ func (s *DeleteService) DeleteManipulator(ctx context.Context, setupId, id int)
 
 func (s *DeleteService) DeleteExecutor(ctx context.Context, setupId, id int) error {
 	rules, err := s.ruleService.GetByExecutorId(ctx, setupId, id)
-	log.Println(rules, err)
 	if err != nil {
 		return err
 	}
diff --git a/internal/services/executor_service.go b/internal/services/executor_service.go
index f820ca4..f4bbb9b 100644
--- a/internal/services/executor_service.go
+++ b/internal/services/executor_service.go
@@ -5,7 +5,6 @@ import (
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
 	"git.miem.hse.ru/hubman/configurator/pkg/errs"
 	hubmanclient "git.miem.hse.ru/hubman/hubman-lib/client"
-	"log"
 	"strconv"
 )
 
@@ -49,7 +48,6 @@ func (s *ExecutorService) GetCommandById(ctx context.Context, setupId, deviceId,
 func (s *ExecutorService) getCommands(ctx context.Context, client *hubmanclient.ClientWithResponses) ([]*entity.DeviceMessageDescriptor, error) {
 	cmdResp, err := client.GetCommandsWithResponse(ctx)
 	if err != nil || cmdResp.JSON200 == nil {
-		log.Println(err)
 		return nil, errs.ErrIncorrectParams
 	}
 	commands := make([]*entity.DeviceMessageDescriptor, len(*cmdResp.JSON200))
diff --git a/internal/services/manipulator_service.go b/internal/services/manipulator_service.go
index 1f1a4ef..a36210a 100644
--- a/internal/services/manipulator_service.go
+++ b/internal/services/manipulator_service.go
@@ -5,7 +5,6 @@ import (
 	"git.miem.hse.ru/hubman/configurator/internal/entity"
 	"git.miem.hse.ru/hubman/configurator/pkg/errs"
 	hubmanclient "git.miem.hse.ru/hubman/hubman-lib/client"
-	"log"
 	"strconv"
 )
 
@@ -29,7 +28,6 @@ func (s *ManipulatorService) getClient(ctx context.Context, url string) (*hubman
 func (s *ManipulatorService) getSignals(ctx context.Context, client *hubmanclient.ClientWithResponses) ([]*entity.DeviceMessageDescriptor, error) {
 	cmdResp, err := client.GetSignalsWithResponse(ctx)
 	if err != nil || cmdResp.JSON200 == nil {
-		log.Println(err)
 		return nil, errs.ErrIncorrectParams
 	}
 	signals := make([]*entity.DeviceMessageDescriptor, len(*cmdResp.JSON200))
-- 
GitLab


From 58bd5cdf33118acbb5567e223c97b61feabb2873 Mon Sep 17 00:00:00 2001
From: Sergei Loshkarev <saloshkarev@miem.hse.ru>
Date: Mon, 30 Oct 2023 19:45:23 +0300
Subject: [PATCH 10/12] fix update rule

---
 internal/services/rule_service.go | 40 +++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)

diff --git a/internal/services/rule_service.go b/internal/services/rule_service.go
index a0265e5..18746d4 100644
--- a/internal/services/rule_service.go
+++ b/internal/services/rule_service.go
@@ -6,6 +6,7 @@ import (
 	"git.miem.hse.ru/hubman/configurator/internal/repositories"
 	"git.miem.hse.ru/hubman/configurator/pkg/errs"
 	"github.com/diegoholiveira/jsonlogic"
+	"log"
 	"strconv"
 )
 
@@ -75,17 +76,52 @@ func (s *RuleService) GetByExecutorId(ctx context.Context, setupId, executorId i
 }
 
 func (s *RuleService) Update(ctx context.Context, setupId int, id int, updRule *entity.UpdRule) (*entity.Rule, error) {
+	rule, err := s.ruleRepository.GetOne(ctx, setupId, id)
+	if err != nil {
+		return nil, err
+	}
+	if updRule.ManipulatorId != nil && updRule.SignalDescriptorId == nil {
+		return nil, errs.ErrRuleIncorrectParams
+	}
+	if updRule.ExecutorId != nil && updRule.CommandDescriptorId == nil {
+		return nil, errs.ErrRuleIncorrectParams
+	}
+	log.Println("")
+	if updRule.ManipulatorId != nil {
+		signalDescriptorId := rule.SignalDescriptorId
+		if updRule.SignalDescriptorId != nil {
+			signalDescriptorId = *updRule.SignalDescriptorId
+		}
+		_, err := s.manipulatorService.GetSignalById(
+			ctx, setupId, *updRule.ManipulatorId, signalDescriptorId,
+		)
+		if err != nil {
+			return nil, err
+		}
+	}
+	if updRule.ExecutorId != nil {
+		commandDescriptorId := rule.CommandDescriptorId
+		if updRule.SignalDescriptorId != nil {
+			commandDescriptorId = *updRule.CommandDescriptorId
+		}
+		_, err := s.executorService.GetCommandById(
+			ctx, setupId, *updRule.ManipulatorId, commandDescriptorId,
+		)
+		if err != nil {
+			return nil, err
+		}
+	}
 	if updRule.Trigger != nil {
 		if err := s.validTrigger(updRule.Trigger); err != nil {
 			return nil, err
 		}
 	}
-	if updRule.Trigger != nil {
+	if updRule.Logic != nil {
 		if err := s.validLogic(*updRule.Logic); err != nil {
 			return nil, err
 		}
 	}
-	err := s.ruleRepository.Update(ctx, setupId, id, updRule)
+	err = s.ruleRepository.Update(ctx, setupId, id, updRule)
 	if err != nil {
 		return nil, err
 	}
-- 
GitLab


From 60af89eaa74b5c1cb694ef3f22db3b8e6f917c8c Mon Sep 17 00:00:00 2001
From: Sergei Loshkarev <saloshkarev@miem.hse.ru>
Date: Mon, 30 Oct 2023 19:48:29 +0300
Subject: [PATCH 11/12] rm unused print

---
 internal/services/rule_service.go | 2 --
 1 file changed, 2 deletions(-)

diff --git a/internal/services/rule_service.go b/internal/services/rule_service.go
index 18746d4..f84e39e 100644
--- a/internal/services/rule_service.go
+++ b/internal/services/rule_service.go
@@ -6,7 +6,6 @@ import (
 	"git.miem.hse.ru/hubman/configurator/internal/repositories"
 	"git.miem.hse.ru/hubman/configurator/pkg/errs"
 	"github.com/diegoholiveira/jsonlogic"
-	"log"
 	"strconv"
 )
 
@@ -86,7 +85,6 @@ func (s *RuleService) Update(ctx context.Context, setupId int, id int, updRule *
 	if updRule.ExecutorId != nil && updRule.CommandDescriptorId == nil {
 		return nil, errs.ErrRuleIncorrectParams
 	}
-	log.Println("")
 	if updRule.ManipulatorId != nil {
 		signalDescriptorId := rule.SignalDescriptorId
 		if updRule.SignalDescriptorId != nil {
-- 
GitLab


From c3e51bd3c90d0d9760e136030363700bedb4ea1d Mon Sep 17 00:00:00 2001
From: Sergei Loshkarev <saloshkarev@miem.hse.ru>
Date: Mon, 30 Oct 2023 20:11:43 +0300
Subject: [PATCH 12/12] fix update-rule

---
 internal/services/executor_service.go |  2 +-
 internal/services/rule_service.go     | 20 ++++++++++----------
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/internal/services/executor_service.go b/internal/services/executor_service.go
index f4bbb9b..aac388a 100644
--- a/internal/services/executor_service.go
+++ b/internal/services/executor_service.go
@@ -42,7 +42,7 @@ func (s *ExecutorService) getClient(ctx context.Context, url string) (*hubmancli
 }
 
 func (s *ExecutorService) GetCommandById(ctx context.Context, setupId, deviceId, descriptorId int) (*entity.DeviceDescriptor, error) {
-	return s.deviceStoreService.GetOneDescriptor(ctx, setupId, deviceId, descriptorId, entity.DeviceTypeManipulator)
+	return s.deviceStoreService.GetOneDescriptor(ctx, setupId, deviceId, descriptorId, entity.DeviceTypeExecutor)
 }
 
 func (s *ExecutorService) getCommands(ctx context.Context, client *hubmanclient.ClientWithResponses) ([]*entity.DeviceMessageDescriptor, error) {
diff --git a/internal/services/rule_service.go b/internal/services/rule_service.go
index f84e39e..4d67d2c 100644
--- a/internal/services/rule_service.go
+++ b/internal/services/rule_service.go
@@ -85,25 +85,25 @@ func (s *RuleService) Update(ctx context.Context, setupId int, id int, updRule *
 	if updRule.ExecutorId != nil && updRule.CommandDescriptorId == nil {
 		return nil, errs.ErrRuleIncorrectParams
 	}
-	if updRule.ManipulatorId != nil {
-		signalDescriptorId := rule.SignalDescriptorId
-		if updRule.SignalDescriptorId != nil {
-			signalDescriptorId = *updRule.SignalDescriptorId
+	if updRule.SignalDescriptorId != nil {
+		manipulatorId := rule.ManipulatorId
+		if updRule.ManipulatorId != nil {
+			manipulatorId = *updRule.ManipulatorId
 		}
 		_, err := s.manipulatorService.GetSignalById(
-			ctx, setupId, *updRule.ManipulatorId, signalDescriptorId,
+			ctx, setupId, manipulatorId, *updRule.SignalDescriptorId,
 		)
 		if err != nil {
 			return nil, err
 		}
 	}
-	if updRule.ExecutorId != nil {
-		commandDescriptorId := rule.CommandDescriptorId
-		if updRule.SignalDescriptorId != nil {
-			commandDescriptorId = *updRule.CommandDescriptorId
+	if updRule.CommandDescriptorId != nil {
+		executorId := rule.ExecutorId
+		if updRule.ExecutorId != nil {
+			executorId = *updRule.ExecutorId
 		}
 		_, err := s.executorService.GetCommandById(
-			ctx, setupId, *updRule.ManipulatorId, commandDescriptorId,
+			ctx, setupId, executorId, *updRule.CommandDescriptorId,
 		)
 		if err != nil {
 			return nil, err
-- 
GitLab