feat: it works

This commit is contained in:
Anatoly Antonov 2025-03-26 17:14:00 +03:00
parent 6302dd62d6
commit 778d5ef146
25 changed files with 638 additions and 106 deletions

View file

@ -225,10 +225,9 @@ components:
Error: Error:
type: object type: object
required: required:
- code
- message - message
properties: properties:
code:
type: integer
message: message:
type: string type: string
details:
type: string

View file

@ -1,7 +1,48 @@
package main package main
import "fmt" import (
"log"
"git.intra.yksa.space/gsn/gsn-proxy/internal/repository"
"git.intra.yksa.space/gsn/gsn-proxy/internal/service"
"git.intra.yksa.space/gsn/gsn-proxy/internal/transport/rest"
"git.intra.yksa.space/gsn/gsn-proxy/internal/transport/rest/handler"
)
const (
servicePrefix = "GSN"
)
func main() { func main() {
fmt.Println("gsn") repoConfig, err := repository.NewConfig(servicePrefix)
if err != nil {
log.Fatal(err)
}
repo, err := repository.New(repoConfig)
if err != nil {
log.Fatal(err)
}
svc := service.New(repo)
handler := handler.New(svc)
restConfig, err := rest.NewConfig(servicePrefix)
if err != nil {
log.Fatal(err)
}
transport, err := rest.New(handler, restConfig)
if err != nil {
log.Fatal(err)
}
for {
transport.Run()
if r := recover(); r != nil {
log.Println("panic occured: ", r)
}
}
} }

6
go.mod
View file

@ -5,11 +5,14 @@ go 1.23.0
toolchain go1.23.7 toolchain go1.23.7
require ( require (
github.com/caarlos0/env/v11 v11.3.1
github.com/go-faster/errors v0.7.1 github.com/go-faster/errors v0.7.1
github.com/go-faster/jx v1.1.0 github.com/go-faster/jx v1.1.0
github.com/golang/mock v1.6.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.7.4 github.com/jackc/pgx/v5 v5.7.4
github.com/ogen-go/ogen v1.10.1 github.com/ogen-go/ogen v1.10.1
github.com/stretchr/testify v1.10.0
go.opentelemetry.io/otel v1.35.0 go.opentelemetry.io/otel v1.35.0
go.opentelemetry.io/otel/metric v1.35.0 go.opentelemetry.io/otel/metric v1.35.0
go.opentelemetry.io/otel/trace v1.35.0 go.opentelemetry.io/otel/trace v1.35.0
@ -17,6 +20,7 @@ require (
) )
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/fatih/color v1.18.0 // indirect github.com/fatih/color v1.18.0 // indirect
github.com/ghodss/yaml v1.0.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect
@ -27,6 +31,7 @@ require (
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect github.com/segmentio/asm v1.2.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.27.0 // indirect
@ -37,4 +42,5 @@ require (
golang.org/x/sys v0.31.0 // indirect golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect golang.org/x/text v0.23.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )

27
go.sum
View file

@ -1,3 +1,5 @@
github.com/caarlos0/env/v11 v11.3.1 h1:cArPWC15hWmEt+gWk7YBi7lEXTXCvpaSdCiZE2X5mCA=
github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vauiYEUUr8P4U=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -18,6 +20,8 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
@ -52,6 +56,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
@ -66,20 +71,42 @@ 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/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View file

@ -1,6 +1,8 @@
package ds package ds
import "github.com/google/uuid" import (
"github.com/google/uuid"
)
type Status string type Status string
@ -15,3 +17,9 @@ type Station struct {
Slug string Slug string
Status Status Status Status
} }
type Satellite struct {
ID uuid.UUID
DisplayName string
Status Status
}

View file

@ -0,0 +1,25 @@
package errcodes
import (
"strings"
)
type ErrorCode struct {
StatusCode int
Message string
Details string
}
var errorCodeCounter int32
func New(statusCode int, message string, details ...string) *ErrorCode {
return &ErrorCode{
StatusCode: statusCode,
Message: message,
Details: strings.Join(details, " "),
}
}
func (e *ErrorCode) Error() string {
return e.Message
}

View file

@ -0,0 +1,23 @@
package repository
import (
"fmt"
env "github.com/caarlos0/env/v11"
)
type Config struct {
ConnStr string `env:"CONNSTR" envDefault:"postgres://gsn:gsn@localhost:5432/gsn?sslmode=disable"`
}
func NewConfig(servicePrefix string) (*Config, error) {
cfg := &Config{}
if err := env.ParseWithOptions(cfg, env.Options{
PrefixTagName: fmt.Sprintf("%s_REPOSITORY_", servicePrefix),
}); err != nil {
return nil, err
}
return cfg, nil
}

View file

@ -1,4 +1,3 @@
-- name: GetStationByID :one -- name: GetSatellites :many
select * select *
from stations from satellites;
where id = @id;

View file

@ -3,24 +3,23 @@ package repository
import ( import (
"context" "context"
"git.intra.yksa.space/gsn/gsn-proxy/internal/ds"
"git.intra.yksa.space/gsn/gsn-proxy/internal/repository/sqlc" "git.intra.yksa.space/gsn/gsn-proxy/internal/repository/sqlc"
"github.com/google/uuid" "github.com/jackc/pgx/v5"
) )
type Repository struct { type Repository struct {
queries *sqlc.Queries queries *sqlc.Queries
cfg *Config
} }
func (r *Repository) GetStationByID(ctx context.Context, ID uuid.UUID) (ds.Station, error) { func New(cfg *Config) (*Repository, error) {
ret, err := r.queries.GetStationByID(ctx, UUIDToPg(ID)) conn, err := pgx.Connect(context.Background(), cfg.ConnStr)
if err != nil { if err != nil {
return ds.Station{}, err return nil, err
} }
return ds.Station{ return &Repository{
ID: PGToUUID(ret.ID), queries: sqlc.New(conn),
Slug: ret.Slug, cfg: cfg,
Status: ds.Status(ret.Status),
}, nil }, nil
} }

View file

@ -0,0 +1,27 @@
package repository
import (
"context"
"net/http"
"git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/ds"
"git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/errcodes"
)
func (r *Repository) GetSatellites(ctx context.Context) ([]ds.Satellite, error) {
satellites, err := r.queries.GetSatellites(ctx)
if err != nil {
return nil, errcodes.New(http.StatusInternalServerError, "failed to get satellites", err.Error())
}
ret := make([]ds.Satellite, 0, len(satellites))
for _, val := range satellites {
ret = append(ret, ds.Satellite{
ID: PGToUUID(val.ID),
DisplayName: val.DisplayName,
Status: ds.Status(val.Status),
})
}
return ret, nil
}

View file

@ -7,19 +7,29 @@ package sqlc
import ( import (
"context" "context"
"github.com/jackc/pgx/v5/pgtype"
) )
const getStationByID = `-- name: GetStationByID :one const getSatellites = `-- name: GetSatellites :many
select id, slug, status select id, display_name, status
from stations from satellites
where id = $1
` `
func (q *Queries) GetStationByID(ctx context.Context, id pgtype.UUID) (Station, error) { func (q *Queries) GetSatellites(ctx context.Context) ([]Satellite, error) {
row := q.db.QueryRow(ctx, getStationByID, id) rows, err := q.db.Query(ctx, getSatellites)
var i Station if err != nil {
err := row.Scan(&i.ID, &i.Slug, &i.Status) return nil, err
return i, err }
defer rows.Close()
var items []Satellite
for rows.Next() {
var i Satellite
if err := rows.Scan(&i.ID, &i.DisplayName, &i.Status); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
} }

12
internal/service/deps.go Normal file
View file

@ -0,0 +1,12 @@
package service
import (
"context"
"git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/ds"
)
//go:generate mockgen -source=deps.go -destination=deps_mock.go -package=service
type Repository interface {
GetSatellites(ctx context.Context) ([]ds.Satellite, error)
}

View file

@ -0,0 +1,51 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: deps.go
// Package service is a generated GoMock package.
package service
import (
context "context"
reflect "reflect"
ds "git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/ds"
gomock "github.com/golang/mock/gomock"
)
// MockRepository is a mock of Repository interface.
type MockRepository struct {
ctrl *gomock.Controller
recorder *MockRepositoryMockRecorder
}
// MockRepositoryMockRecorder is the mock recorder for MockRepository.
type MockRepositoryMockRecorder struct {
mock *MockRepository
}
// NewMockRepository creates a new mock instance.
func NewMockRepository(ctrl *gomock.Controller) *MockRepository {
mock := &MockRepository{ctrl: ctrl}
mock.recorder = &MockRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
return m.recorder
}
// GetSatellites mocks base method.
func (m *MockRepository) GetSatellites(ctx context.Context) ([]ds.Satellite, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetSatellites", ctx)
ret0, _ := ret[0].([]ds.Satellite)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetSatellites indicates an expected call of GetSatellites.
func (mr *MockRepositoryMockRecorder) GetSatellites(ctx interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetSatellites", reflect.TypeOf((*MockRepository)(nil).GetSatellites), ctx)
}

View file

@ -0,0 +1,11 @@
package service
import (
"context"
"git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/ds"
)
func (s *Service) GetSatellites(ctx context.Context) ([]ds.Satellite, error) {
return s.repo.GetSatellites(ctx)
}

View file

@ -0,0 +1,66 @@
package service
import (
"context"
"reflect"
"testing"
"git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/ds"
"github.com/golang/mock/gomock"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
func Test_GetSatellites(t *testing.T) {
suite := NewSuite(t)
var (
commonSatelliteID = uuid.New()
commonDisplayName = "test"
commonStatus = ds.StatusActive
)
testCases := []struct {
name string
mock func()
expected []ds.Satellite
wantErr error
}{
{
name: "success",
mock: func() {
suite.mockRepo.EXPECT().GetSatellites(gomock.Any()).Return([]ds.Satellite{
{
ID: commonSatelliteID,
DisplayName: commonDisplayName,
Status: commonStatus,
},
}, nil)
},
expected: []ds.Satellite{
{
ID: commonSatelliteID,
DisplayName: commonDisplayName,
Status: commonStatus,
},
},
wantErr: nil,
},
}
for _, tt := range testCases {
t.Run(tt.name, func(t *testing.T) {
if tt.mock != nil {
tt.mock()
}
result, err := suite.svc.GetSatellites(context.Background())
if tt.wantErr != nil {
require.ErrorIs(t, err, tt.wantErr)
} else {
require.NoError(t, err)
require.True(t, reflect.DeepEqual(tt.expected, result))
}
})
}
}

View file

@ -0,0 +1,11 @@
package service
type Service struct {
repo Repository
}
func New(repo Repository) *Service {
return &Service{
repo: repo,
}
}

View file

@ -0,0 +1,27 @@
package service
import (
"testing"
"github.com/golang/mock/gomock"
)
type TestSuite struct {
svc *Service
mockRepo *MockRepository
}
func NewSuite(t *testing.T) *TestSuite {
t.Helper()
ctrl := gomock.NewController(t)
mockRepo := NewMockRepository(ctrl)
svc := New(mockRepo)
return &TestSuite{
svc: svc,
mockRepo: mockRepo,
}
}

View file

@ -0,0 +1,23 @@
package rest
import (
"fmt"
env "github.com/caarlos0/env/v11"
)
type Config struct {
Port int `env:"PORT" envDefault:"8080"`
}
func NewConfig(servicePrefix string) (*Config, error) {
cfg := &Config{}
if err := env.ParseWithOptions(cfg, env.Options{
PrefixTagName: fmt.Sprintf("%s_REST_", servicePrefix),
}); err != nil {
return nil, err
}
return cfg, nil
}

View file

@ -1,4 +0,0 @@
package rest
type Service interface {
}

View file

@ -1,43 +0,0 @@
package rest
import (
"context"
api "git.intra.yksa.space/gsn/gsn-proxy/pkg/rest"
)
var (
_ api.Handler = (*Handler)(nil)
)
type Handler struct {
svc Service
}
func (h *Handler) GetSatellites(ctx context.Context) (*api.GetSatellitesOK, error) {
return nil, nil
}
func (h *Handler) GetStations(ctx context.Context) (*api.GetStationsOK, error) {
return nil, nil
}
func (h *Handler) GetSubscriptions(ctx context.Context) (*api.GetSubscriptionsOK, error) {
return nil, nil
}
func (h *Handler) SubscribeSatellite(ctx context.Context, req *api.SubscribeSatelliteReq) (*api.SubscribeSatelliteOK, error) {
return nil, nil
}
func (h *Handler) SubscribeStation(ctx context.Context, req *api.SubscribeStationReq) (*api.SubscribeStationOK, error) {
return nil, nil
}
func (h *Handler) Unsubscribe(ctx context.Context, params api.UnsubscribeParams) error {
return nil
}
func (h *Handler) NewError(ctx context.Context, err error) *api.ErrorStatusCode {
return nil
}

View file

@ -0,0 +1,11 @@
package handler
import (
"context"
"git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/ds"
)
type Service interface {
GetSatellites(ctx context.Context) ([]ds.Satellite, error)
}

View file

@ -0,0 +1,89 @@
package handler
import (
"context"
"fmt"
"net/http"
"git.intra.yksa.space/gsn/gsn-proxy/internal/pkg/errcodes"
api "git.intra.yksa.space/gsn/gsn-proxy/pkg/rest"
)
var (
_ api.Handler = (*Handler)(nil)
)
type Handler struct {
svc Service
}
func New(svc Service) *Handler {
return &Handler{
svc: svc,
}
}
func (h *Handler) GetSatellites(ctx context.Context) (*api.GetSatellitesOK, error) {
satellites, err := h.svc.GetSatellites(ctx)
if err != nil {
return nil, err
}
ret := make([]api.GetSatellitesOKSatellitesItem, 0, len(satellites))
for _, val := range satellites {
ret = append(ret, api.GetSatellitesOKSatellitesItem{
ID: val.ID,
DisplayName: val.DisplayName,
Status: api.GetSatellitesOKSatellitesItemStatus(val.Status),
})
}
return &api.GetSatellitesOK{
Satellites: ret,
}, nil
}
func (h *Handler) GetStations(ctx context.Context) (*api.GetStationsOK, error) {
return nil, nil
}
func (h *Handler) GetSubscriptions(ctx context.Context) (*api.GetSubscriptionsOK, error) {
return nil, nil
}
func (h *Handler) SubscribeSatellite(ctx context.Context, req *api.SubscribeSatelliteReq) (*api.SubscribeSatelliteOK, error) {
return nil, nil
}
func (h *Handler) SubscribeStation(ctx context.Context, req *api.SubscribeStationReq) (*api.SubscribeStationOK, error) {
return nil, nil
}
func (h *Handler) Unsubscribe(ctx context.Context, params api.UnsubscribeParams) error {
return nil
}
func (h *Handler) NewError(ctx context.Context, err error) *api.ErrorStatusCode {
if errcode, ok := err.(*errcodes.ErrorCode); ok {
resp := api.Error{
Message: errcode.Message,
}
if errcode.Details != "" {
resp.Details = api.NewOptString(errcode.Details)
}
return &api.ErrorStatusCode{
StatusCode: errcode.StatusCode,
Response: resp,
}
}
return &api.ErrorStatusCode{
StatusCode: http.StatusInternalServerError,
Response: api.Error{
Message: fmt.Sprintf("undefined internal error"),
Details: api.NewOptString(err.Error()),
},
}
}

View file

@ -0,0 +1,33 @@
package rest
import (
"fmt"
"log"
"net/http"
handler "git.intra.yksa.space/gsn/gsn-proxy/internal/transport/rest/handler"
api "git.intra.yksa.space/gsn/gsn-proxy/pkg/rest"
)
type Transport struct {
cfg *Config
srv *api.Server
}
func New(handler *handler.Handler, cfg *Config) (*Transport, error) {
srv, err := api.NewServer(handler)
if err != nil {
return nil, err
}
return &Transport{
srv: srv,
cfg: cfg,
}, nil
}
func (t *Transport) Run() {
if err := http.ListenAndServe(fmt.Sprintf(":%d", t.cfg.Port), t.srv); err != nil {
log.Panic(err)
}
}

View file

@ -22,19 +22,21 @@ func (s *Error) Encode(e *jx.Encoder) {
// encodeFields encodes fields. // encodeFields encodes fields.
func (s *Error) encodeFields(e *jx.Encoder) { func (s *Error) encodeFields(e *jx.Encoder) {
{
e.FieldStart("code")
e.Int(s.Code)
}
{ {
e.FieldStart("message") e.FieldStart("message")
e.Str(s.Message) e.Str(s.Message)
} }
{
if s.Details.Set {
e.FieldStart("details")
s.Details.Encode(e)
}
}
} }
var jsonFieldsNameOfError = [2]string{ var jsonFieldsNameOfError = [2]string{
0: "code", 0: "message",
1: "message", 1: "details",
} }
// Decode decodes Error from json. // Decode decodes Error from json.
@ -46,20 +48,8 @@ func (s *Error) Decode(d *jx.Decoder) error {
if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error { if err := d.ObjBytes(func(d *jx.Decoder, k []byte) error {
switch string(k) { switch string(k) {
case "code":
requiredBitSet[0] |= 1 << 0
if err := func() error {
v, err := d.Int()
s.Code = int(v)
if err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"code\"")
}
case "message": case "message":
requiredBitSet[0] |= 1 << 1 requiredBitSet[0] |= 1 << 0
if err := func() error { if err := func() error {
v, err := d.Str() v, err := d.Str()
s.Message = string(v) s.Message = string(v)
@ -70,6 +60,16 @@ func (s *Error) Decode(d *jx.Decoder) error {
}(); err != nil { }(); err != nil {
return errors.Wrap(err, "decode field \"message\"") return errors.Wrap(err, "decode field \"message\"")
} }
case "details":
if err := func() error {
s.Details.Reset()
if err := s.Details.Decode(d); err != nil {
return err
}
return nil
}(); err != nil {
return errors.Wrap(err, "decode field \"details\"")
}
default: default:
return d.Skip() return d.Skip()
} }
@ -80,7 +80,7 @@ func (s *Error) Decode(d *jx.Decoder) error {
// Validate required fields. // Validate required fields.
var failures []validate.FieldError var failures []validate.FieldError
for i, mask := range [1]uint8{ for i, mask := range [1]uint8{
0b00000011, 0b00000001,
} { } {
if result := (requiredBitSet[i] & mask) ^ mask; result != 0 { if result := (requiredBitSet[i] & mask) ^ mask; result != 0 {
// Mask only required fields and check equality to mask using XOR. // Mask only required fields and check equality to mask using XOR.
@ -977,6 +977,41 @@ func (s *GetSubscriptionsOKSubscriptionsItemType) UnmarshalJSON(data []byte) err
return s.Decode(d) return s.Decode(d)
} }
// Encode encodes string as json.
func (o OptString) Encode(e *jx.Encoder) {
if !o.Set {
return
}
e.Str(string(o.Value))
}
// Decode decodes string from json.
func (o *OptString) Decode(d *jx.Decoder) error {
if o == nil {
return errors.New("invalid: unable to decode OptString to nil")
}
o.Set = true
v, err := d.Str()
if err != nil {
return err
}
o.Value = string(v)
return nil
}
// MarshalJSON implements stdjson.Marshaler.
func (s OptString) MarshalJSON() ([]byte, error) {
e := jx.Encoder{}
s.Encode(&e)
return e.Bytes(), nil
}
// UnmarshalJSON implements stdjson.Unmarshaler.
func (s *OptString) UnmarshalJSON(data []byte) error {
d := jx.DecodeBytes(data)
return s.Decode(d)
}
// Encode implements json.Marshaler. // Encode implements json.Marshaler.
func (s *SubscribeSatelliteOK) Encode(e *jx.Encoder) { func (s *SubscribeSatelliteOK) Encode(e *jx.Encoder) {
e.ObjStart() e.ObjStart()

View file

@ -16,13 +16,8 @@ func (s *ErrorStatusCode) Error() string {
// Ref: #/components/schemas/Error // Ref: #/components/schemas/Error
type Error struct { type Error struct {
Code int `json:"code"` Message string `json:"message"`
Message string `json:"message"` Details OptString `json:"details"`
}
// GetCode returns the value of Code.
func (s *Error) GetCode() int {
return s.Code
} }
// GetMessage returns the value of Message. // GetMessage returns the value of Message.
@ -30,9 +25,9 @@ func (s *Error) GetMessage() string {
return s.Message return s.Message
} }
// SetCode sets the value of Code. // GetDetails returns the value of Details.
func (s *Error) SetCode(val int) { func (s *Error) GetDetails() OptString {
s.Code = val return s.Details
} }
// SetMessage sets the value of Message. // SetMessage sets the value of Message.
@ -40,6 +35,11 @@ func (s *Error) SetMessage(val string) {
s.Message = val s.Message = val
} }
// SetDetails sets the value of Details.
func (s *Error) SetDetails(val OptString) {
s.Details = val
}
// ErrorStatusCode wraps Error with StatusCode. // ErrorStatusCode wraps Error with StatusCode.
type ErrorStatusCode struct { type ErrorStatusCode struct {
StatusCode int StatusCode int
@ -412,6 +412,52 @@ func (s *GetSubscriptionsOKSubscriptionsItemType) UnmarshalText(data []byte) err
} }
} }
// NewOptString returns new OptString with value set to v.
func NewOptString(v string) OptString {
return OptString{
Value: v,
Set: true,
}
}
// OptString is optional string.
type OptString struct {
Value string
Set bool
}
// IsSet returns true if OptString was set.
func (o OptString) IsSet() bool { return o.Set }
// Reset unsets value.
func (o *OptString) Reset() {
var v string
o.Value = v
o.Set = false
}
// SetTo sets value to v.
func (o *OptString) SetTo(v string) {
o.Set = true
o.Value = v
}
// Get returns value and boolean that denotes whether value was set.
func (o OptString) Get() (v string, ok bool) {
if !o.Set {
return v, false
}
return o.Value, true
}
// Or returns value if set, or given parameter if does not.
func (o OptString) Or(d string) string {
if v, ok := o.Get(); ok {
return v
}
return d
}
// NewOptUUID returns new OptUUID with value set to v. // NewOptUUID returns new OptUUID with value set to v.
func NewOptUUID(v uuid.UUID) OptUUID { func NewOptUUID(v uuid.UUID) OptUUID {
return OptUUID{ return OptUUID{