app: added one api connection between backend and frontend
This commit is contained in:
parent
efd2b6b187
commit
261361e5a7
7
Makefile
7
Makefile
|
@ -8,6 +8,13 @@ export GOOSE_MIGRATION_DIR ?= schemas/migrations
|
|||
build: generate-go generate-web
|
||||
go build -o bin/bluemage ./go/cmd/bluemage/main.go
|
||||
|
||||
run: build
|
||||
ARGS=$${ARGS:-serve}
|
||||
./bin/bluemage $$ARGS
|
||||
|
||||
run-web: generate-web
|
||||
cd web && npm run dev
|
||||
|
||||
generate-go: migrate
|
||||
rm -rf go/gen
|
||||
(cd ./schemas/proto && buf generate --template buf.gen.go.yaml .)
|
||||
|
|
6
go.mod
6
go.mod
|
@ -5,11 +5,15 @@ go 1.22.5
|
|||
require (
|
||||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2
|
||||
connectrpc.com/connect v1.16.2
|
||||
connectrpc.com/cors v0.1.0
|
||||
github.com/aarondl/opt v0.0.0-20240623220848-083f18ab9536
|
||||
github.com/bufbuild/protovalidate-go v0.6.3
|
||||
github.com/jaswdr/faker/v2 v2.3.0
|
||||
github.com/mattn/go-sqlite3 v1.14.16
|
||||
github.com/rs/cors v1.11.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/stephenafamo/bob v0.28.1
|
||||
golang.org/x/net v0.23.0
|
||||
google.golang.org/protobuf v1.34.2
|
||||
)
|
||||
|
||||
|
@ -17,7 +21,9 @@ require (
|
|||
github.com/aarondl/json v0.0.0-20221020222930-8b0db17ef1bf // indirect
|
||||
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
|
||||
github.com/google/cel-go v0.20.1 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 // indirect
|
||||
github.com/spf13/pflag v1.0.5 // indirect
|
||||
github.com/stephenafamo/scan v0.4.2 // indirect
|
||||
github.com/stoewer/go-strcase v1.3.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
|
||||
|
|
16
go.sum
16
go.sum
|
@ -2,6 +2,8 @@ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-2024071716455
|
|||
buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.34.2-20240717164558-a6c49f84cc0f.2/go.mod h1:ylS4c28ACSI59oJrOdW4pHS4n0Hw4TgSPHn8rpHl4Yw=
|
||||
connectrpc.com/connect v1.16.2 h1:ybd6y+ls7GOlb7Bh5C8+ghA6SvCBajHwxssO2CGFjqE=
|
||||
connectrpc.com/connect v1.16.2/go.mod h1:n2kgwskMHXC+lVqb18wngEpF95ldBHXjZYJussz5FRc=
|
||||
connectrpc.com/cors v0.1.0 h1:f3gTXJyDZPrDIZCQ567jxfD9PAIpopHiRDnJRt3QuOQ=
|
||||
connectrpc.com/cors v0.1.0/go.mod h1:v8SJZCPfHtGH1zsm+Ttajpozd4cYIUryl4dFB6QEpfg=
|
||||
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
|
||||
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
|
||||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
|
||||
|
@ -18,6 +20,7 @@ github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8
|
|||
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
|
||||
github.com/bufbuild/protovalidate-go v0.6.3 h1:wxQyzW035zM16Binbaz/nWAzS12dRIXhZdSUWRY7Fv0=
|
||||
github.com/bufbuild/protovalidate-go v0.6.3/go.mod h1:J4PtwP9Z2YAGgB0+o+tTWEDtLtXvz/gfhFZD8pbzM/U=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
|
@ -37,6 +40,8 @@ github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs
|
|||
github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jaswdr/faker/v2 v2.3.0 h1:jgQ9UmU2Eb5tSQ8JkUS4tPoyTM2OtThQpOpwk7Fa9RY=
|
||||
github.com/jaswdr/faker/v2 v2.3.0/go.mod h1:ROK8xwQV0hYOLDUtxCQgHGcl10jbVzIvqHxcIDdwY2Q=
|
||||
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
|
||||
|
@ -63,10 +68,17 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494 h1:wSmWgpuccqS2IOfmYrbRiUgv+g37W5suLLLxwwniTSc=
|
||||
github.com/qdm12/reprint v0.0.0-20200326205758-722754a53494/go.mod h1:yipyliwI08eQ6XwDm1fEwKPdF/xdbkiHtrU+1Hg+vc4=
|
||||
github.com/rs/cors v1.11.0 h1:0B9GE/r9Bc2UxRMMtymBkHTenPkHDv0CW4Y98GBY+po=
|
||||
github.com/rs/cors v1.11.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
|
||||
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
|
||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/stephenafamo/bob v0.28.1 h1:yQaHuhP9HoCldoRIrhB7SwcHKMCCSw5499h7ETFcBLs=
|
||||
github.com/stephenafamo/bob v0.28.1/go.mod h1:S/D3dAbBZjBOcts9iv4ywsJDApFK86s3bUBulCoT6kE=
|
||||
github.com/stephenafamo/fakedb v0.0.0-20221230081958-0b86f816ed97 h1:XItoZNmhOih06TC02jK7l3wlpZ0XT/sPQYutDcGOQjg=
|
||||
|
@ -90,8 +102,8 @@ github.com/volatiletech/inflect v0.0.1 h1:2a6FcMQyhmPZcLa+uet3VJ8gLn/9svWhJxJYwv
|
|||
github.com/volatiletech/inflect v0.0.1/go.mod h1:IBti31tG6phkHitLlr5j7shC5SOo//x0AjDzaJU1PLA=
|
||||
github.com/volatiletech/strmangle v0.0.6 h1:AdOYE3B2ygRDq4rXDij/MMwq6KVK/pWAYxpC7CLrkKQ=
|
||||
github.com/volatiletech/strmangle v0.0.6/go.mod h1:ycDvbDkjDvhC0NUU8w3fWwl5JEMTV56vTKXzR3GeR+0=
|
||||
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
|
||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
|
||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
|
||||
type API struct {
|
||||
mu sync.Mutex
|
||||
db bob.Executor
|
||||
DB bob.Executor
|
||||
}
|
||||
|
||||
func (api *API) lockf(f func()) {
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
func (api *API) DevicesCreate(ctx context.Context, params *models.Device) (device *models.Device, err error) {
|
||||
now := time.Now()
|
||||
api.lockf(func() {
|
||||
device, err = models.Devices.Insert(ctx, api.db, &models.DeviceSetter{
|
||||
device, err = models.Devices.Insert(ctx, api.DB, &models.DeviceSetter{
|
||||
Slug: omit.From(params.Slug),
|
||||
Name: omit.From(params.Name),
|
||||
ResolutionX: omit.From(params.ResolutionX),
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
)
|
||||
|
||||
func (api *API) GetDevice(ctx context.Context, slug string) (device *models.Device, err error) {
|
||||
device, err = models.FindDevice(ctx, api.db, slug)
|
||||
device, err = models.FindDevice(ctx, api.DB, slug)
|
||||
if err != nil {
|
||||
if err.Error() == "sql: no rows in result set" {
|
||||
return device, errs.Wrapw(err, "device not found", "slug", slug).Code(connect.CodeNotFound)
|
||||
|
|
|
@ -1,7 +1,28 @@
|
|||
package main
|
||||
|
||||
import "time"
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tigorlazuardi/bluemage/go/cmd/bluemage/serve"
|
||||
)
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "bluemage",
|
||||
Short: "Bluemage is a reddit image downloader",
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.AddCommand(serve.Cmd)
|
||||
}
|
||||
|
||||
func main() {
|
||||
time.Now().Unix()
|
||||
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer cancel()
|
||||
|
||||
if err := Cmd.ExecuteContext(ctx); err != nil {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
63
go/cmd/bluemage/serve/serve.go
Normal file
63
go/cmd/bluemage/serve/serve.go
Normal file
|
@ -0,0 +1,63 @@
|
|||
package serve
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/stephenafamo/bob"
|
||||
"github.com/tigorlazuardi/bluemage/go/api"
|
||||
"github.com/tigorlazuardi/bluemage/go/gen/proto/device/v1/v1connect"
|
||||
"github.com/tigorlazuardi/bluemage/go/pkg/errs"
|
||||
"github.com/tigorlazuardi/bluemage/go/server"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/h2c"
|
||||
)
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
db, err := bob.Open("sqlite3", "file:go/data.db")
|
||||
if err != nil {
|
||||
return errs.Wrap(err, "failed to open database")
|
||||
}
|
||||
|
||||
api := &api.API{
|
||||
DB: db,
|
||||
}
|
||||
|
||||
handler := &server.Server{
|
||||
DeviceHandler: server.DeviceHandler{
|
||||
API: api,
|
||||
},
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.Handle(v1connect.NewDeviceServiceHandler(handler, connect.WithInterceptors(server.LogInterceptor())))
|
||||
|
||||
server := &http.Server{
|
||||
Addr: ":8080",
|
||||
Handler: h2c.NewHandler(server.WithCORS(mux), &http2.Server{}),
|
||||
}
|
||||
|
||||
go func() {
|
||||
<-cmd.Context().Done()
|
||||
slog.Info("Exit signal received. Shutting down server")
|
||||
shutdownCtx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
_ = server.Shutdown(shutdownCtx)
|
||||
}()
|
||||
|
||||
slog.Info("ConnectRPC server started", "addr", server.Addr)
|
||||
err = server.ListenAndServe()
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
return errs.Wrap(err, "failed to serve")
|
||||
}
|
||||
return errors.Join(db.Close())
|
||||
},
|
||||
SilenceUsage: true,
|
||||
}
|
|
@ -229,3 +229,11 @@ func FindError(err error) Error {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func DrillToError(err error) error {
|
||||
e := FindError(err)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
21
go/server/cors.go
Normal file
21
go/server/cors.go
Normal file
|
@ -0,0 +1,21 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
connectcors "connectrpc.com/cors"
|
||||
"github.com/rs/cors"
|
||||
)
|
||||
|
||||
// WithCORS adds CORS support to a Connect HTTP handler.
|
||||
func WithCORS(h http.Handler) http.Handler {
|
||||
middleware := cors.New(cors.Options{
|
||||
// TODO: AllowedOrigins will need to be limited when
|
||||
// the client is embedded to the binary.
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: connectcors.AllowedMethods(),
|
||||
AllowedHeaders: connectcors.AllowedHeaders(),
|
||||
ExposedHeaders: connectcors.ExposedHeaders(),
|
||||
})
|
||||
return middleware.Handler(h)
|
||||
}
|
39
go/server/interceptor.go
Normal file
39
go/server/interceptor.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"time"
|
||||
|
||||
"connectrpc.com/connect"
|
||||
"github.com/tigorlazuardi/bluemage/go/pkg/errs"
|
||||
)
|
||||
|
||||
func LogInterceptor() connect.UnaryInterceptorFunc {
|
||||
interceptor := func(next connect.UnaryFunc) connect.UnaryFunc {
|
||||
return func(ctx context.Context, ar connect.AnyRequest) (connect.AnyResponse, error) {
|
||||
start := time.Now()
|
||||
resp, err := next(ctx, ar)
|
||||
dur := time.Since(start)
|
||||
millisecondsFloat := float64(dur) / float64(time.Millisecond)
|
||||
if err != nil {
|
||||
slog.Error("RPC Error",
|
||||
"procedure", ar.Spec().Procedure,
|
||||
"method", ar.HTTPMethod(),
|
||||
"duration", fmt.Sprintf("%.3fs", millisecondsFloat),
|
||||
"error", errs.DrillToError(err),
|
||||
)
|
||||
} else {
|
||||
slog.Info("RPC Call",
|
||||
"procedure", ar.Spec().Procedure,
|
||||
"method", ar.HTTPMethod(),
|
||||
"duration", fmt.Sprintf("%.3fs", millisecondsFloat),
|
||||
)
|
||||
}
|
||||
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
return connect.UnaryInterceptorFunc(interceptor)
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
version: v2
|
||||
plugins:
|
||||
- local: protoc-gen-es
|
||||
out: ../../web/gen/proto
|
||||
out: ../../web/src/gen/proto
|
||||
opt: target=ts
|
||||
- local: protoc-gen-connect-es
|
||||
out: ../../web/gen/proto
|
||||
out: ../../web/src/gen/proto
|
||||
opt: target=ts
|
||||
|
|
|
@ -2,6 +2,13 @@
|
|||
import svelteLogo from './assets/svelte.svg'
|
||||
import viteLogo from '/vite.svg'
|
||||
import Counter from './lib/Counter.svelte'
|
||||
import { client } from './client/client';
|
||||
|
||||
function getDevice() {
|
||||
return client.getDevice({slug: 'test'})
|
||||
}
|
||||
|
||||
let promise = getDevice()
|
||||
</script>
|
||||
|
||||
<main>
|
||||
|
@ -26,6 +33,14 @@
|
|||
<p class="read-the-docs">
|
||||
Click on the Vite and Svelte logos to learn more
|
||||
</p>
|
||||
|
||||
{#await promise}
|
||||
<p>loading...</p>
|
||||
{:then device}
|
||||
<p>{device.name}</p>
|
||||
{:catch error}
|
||||
<p>{error.message}</p>
|
||||
{/await}
|
||||
</main>
|
||||
|
||||
<style>
|
||||
|
|
9
web/src/client/client.ts
Normal file
9
web/src/client/client.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { createPromiseClient } from "@connectrpc/connect";
|
||||
import { createConnectTransport } from "@connectrpc/connect-web";
|
||||
import { DeviceService } from "../gen/proto/device/v1/device_connect";
|
||||
|
||||
const transport = createConnectTransport({
|
||||
baseUrl: import.meta.env.VITE_BLUEMAGE_API_URL,
|
||||
});
|
||||
|
||||
export const client = createPromiseClient(DeviceService, transport);
|
Loading…
Reference in a new issue