refactor: now uses jet sql as much as possible
This commit is contained in:
parent
4872e653ef
commit
8bb8cb30ec
|
@ -2,25 +2,43 @@ package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"connectrpc.com/connect"
|
"connectrpc.com/connect"
|
||||||
"github.com/tigorlazuardi/bluemage/go/gen/models"
|
|
||||||
"github.com/tigorlazuardi/bluemage/go/pkg/errs"
|
"github.com/tigorlazuardi/bluemage/go/pkg/errs"
|
||||||
"github.com/tigorlazuardi/bluemage/go/pkg/log"
|
"github.com/tigorlazuardi/bluemage/go/pkg/telemetry"
|
||||||
|
|
||||||
|
"github.com/go-jet/jet/v2/qrm"
|
||||||
|
"github.com/tigorlazuardi/bluemage/go/gen/jet/model"
|
||||||
|
|
||||||
|
. "github.com/go-jet/jet/v2/sqlite"
|
||||||
|
. "github.com/tigorlazuardi/bluemage/go/gen/jet/table"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (api *API) GetDevice(ctx context.Context, slug string) (device *models.Device, err error) {
|
func (api *API) GetDevice(ctx context.Context, slug string) (device model.Devices, err error) {
|
||||||
ctx, coll := log.WithQueryCollector(ctx)
|
ctx, span := tracer.Start(ctx, "GetDevice")
|
||||||
device, err = models.FindDevice(ctx, api.Executor, slug)
|
defer func() { telemetry.EndWithStatus(span, err) }()
|
||||||
if err != nil {
|
|
||||||
if err.Error() == "sql: no rows in result set" {
|
|
||||||
return device, errs.Wrapw(err, "device not found",
|
|
||||||
"slug", slug,
|
|
||||||
"query", coll,
|
|
||||||
).Code(connect.CodeNotFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
return device, errs.Wrapw(err, "failed to find device", "slug", slug)
|
stmt := SELECT(Devices.AllColumns).
|
||||||
|
FROM(Devices).
|
||||||
|
WHERE(Devices.Slug.EQ(String(slug)))
|
||||||
|
|
||||||
|
err = stmt.QueryContext(ctx, api.DB, &device)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, qrm.ErrNoRows) {
|
||||||
|
return device, errs.
|
||||||
|
Wrapf(err, "device '%s' does not exist", slug).
|
||||||
|
Details(
|
||||||
|
"slug", slug,
|
||||||
|
"query", stmt.DebugSql(),
|
||||||
|
).
|
||||||
|
Code(connect.CodeNotFound)
|
||||||
|
}
|
||||||
|
return device, errs.Wrapf(err, "failed to get device '%s'", slug).
|
||||||
|
Details(
|
||||||
|
"slug", slug,
|
||||||
|
"query", stmt.DebugSql(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return device, nil
|
return device, nil
|
||||||
|
|
|
@ -3,66 +3,74 @@ package api
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/stephenafamo/bob"
|
|
||||||
"github.com/stephenafamo/bob/dialect/sqlite"
|
|
||||||
"github.com/stephenafamo/bob/dialect/sqlite/dialect"
|
|
||||||
"github.com/stephenafamo/bob/dialect/sqlite/sm"
|
|
||||||
"github.com/tigorlazuardi/bluemage/go/gen/models"
|
|
||||||
device "github.com/tigorlazuardi/bluemage/go/gen/proto/device/v1"
|
device "github.com/tigorlazuardi/bluemage/go/gen/proto/device/v1"
|
||||||
"github.com/tigorlazuardi/bluemage/go/pkg/errs"
|
"github.com/tigorlazuardi/bluemage/go/pkg/errs"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
|
||||||
|
. "github.com/go-jet/jet/v2/sqlite"
|
||||||
|
"github.com/tigorlazuardi/bluemage/go/gen/jet/model"
|
||||||
|
. "github.com/tigorlazuardi/bluemage/go/gen/jet/table"
|
||||||
)
|
)
|
||||||
|
|
||||||
func queryFromListDeviceRequest(req *device.ListDevicesRequest) (expr []bob.Mod[*dialect.SelectQuery]) {
|
func listDevicesRequestSelectStatement(req *device.ListDevicesRequest) SelectStatement {
|
||||||
|
cond := Bool(true)
|
||||||
switch req.Disabled {
|
switch req.Disabled {
|
||||||
case device.DisabledFilter_DISABLED_FILTER_TRUE:
|
case device.DisabledFilter_DISABLED_FILTER_TRUE:
|
||||||
expr = append(expr, models.SelectWhere.Devices.Disabled.EQ(1))
|
cond.AND(Devices.Disabled.EQ(Int(1)))
|
||||||
case device.DisabledFilter_DISABLED_FILTER_FALSE:
|
case device.DisabledFilter_DISABLED_FILTER_FALSE:
|
||||||
expr = append(expr, models.SelectWhere.Devices.Disabled.EQ(0))
|
cond.AND(Devices.Disabled.EQ(Int(0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Search != "" {
|
if req.Search != "" {
|
||||||
arg := sqlite.Arg("%" + req.Search + "%")
|
cond.AND(
|
||||||
expr = append(expr,
|
Devices.Name.LIKE(String("%" + req.Search + "%")).
|
||||||
sm.Where(
|
OR(Devices.Slug.LIKE(String("%" + req.Search + "%"))),
|
||||||
models.DeviceColumns.Name.Like(arg).Or(models.DeviceColumns.Slug.Like(arg)),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stmt := SELECT(Devices.AllColumns).
|
||||||
|
FROM(Devices).
|
||||||
|
WHERE(cond)
|
||||||
|
|
||||||
if req.Limit > 0 {
|
if req.Limit > 0 {
|
||||||
expr = append(expr, sm.Limit(req.Limit))
|
stmt.LIMIT(int64(req.Limit))
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Offset > 0 {
|
if req.Offset > 0 {
|
||||||
expr = append(expr, sm.Offset(req.Offset))
|
stmt.OFFSET(int64(req.Offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.OrderBy == device.OrderBy_ORDER_BY_UNSPECIFIED {
|
if req.OrderBy == device.OrderBy_ORDER_BY_UNSPECIFIED {
|
||||||
return append(expr, sm.OrderBy(models.DeviceColumns.CreatedAt).Desc())
|
return stmt.ORDER_BY(Devices.CreatedAt.DESC())
|
||||||
}
|
}
|
||||||
|
|
||||||
orderByField, _ := strings.CutPrefix(device.OrderBy_name[int32(req.OrderBy)], "ORDER_BY_")
|
orderByField, _ := strings.CutPrefix(device.OrderBy_name[int32(req.OrderBy)], "ORDER_BY_")
|
||||||
orderByField = strings.ToLower(orderByField)
|
orderByField = strings.ToLower(orderByField)
|
||||||
|
|
||||||
orderBy := sm.OrderBy(sqlite.Quote(orderByField))
|
orderBy := StringColumn(orderByField)
|
||||||
if req.Sort == device.Sort_SORT_DESCENDING {
|
|
||||||
expr = append(expr, orderBy.Desc())
|
|
||||||
} else {
|
|
||||||
expr = append(expr, orderBy.Asc())
|
|
||||||
}
|
|
||||||
|
|
||||||
return expr
|
if req.Sort == device.Sort_SORT_DESCENDING {
|
||||||
|
return stmt.ORDER_BY(orderBy.DESC())
|
||||||
|
} else {
|
||||||
|
return stmt.ORDER_BY(orderBy.ASC())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) DevicesList(ctx context.Context, req *device.ListDevicesRequest) (resp *device.ListDevicesResponse, err error) {
|
func (api *API) DevicesList(ctx context.Context, req *device.ListDevicesRequest) (resp *device.ListDevicesResponse, err error) {
|
||||||
|
ctx, span := tracer.Start(ctx, "DevicesList")
|
||||||
|
defer span.End()
|
||||||
|
|
||||||
resp = &device.ListDevicesResponse{}
|
resp = &device.ListDevicesResponse{}
|
||||||
results, err := models.Devices.Query(ctx, api.Executor, queryFromListDeviceRequest(req)...).All()
|
stmt := listDevicesRequestSelectStatement(req)
|
||||||
if err != nil {
|
var out []model.Devices
|
||||||
return resp, errs.Wrapw(err, "failed to list devices", "request", req)
|
if err := stmt.QueryContext(ctx, api.DB, &out); err != nil {
|
||||||
|
return resp, errs.Wrapw(err, "failed to list devices",
|
||||||
|
"request", req,
|
||||||
|
"query", stmt.DebugSql(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
for _, result := range results {
|
for _, result := range out {
|
||||||
resp.Devices = append(resp.Devices, convert.ModelsDeviceToGetDeviceResponse(result))
|
resp.Devices = append(resp.Devices, convert.JetModelDeviceToGetDeviceResponse(result))
|
||||||
}
|
}
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"connectrpc.com/connect"
|
"connectrpc.com/connect"
|
||||||
|
@ -58,18 +59,24 @@ var Cmd = &cobra.Command{
|
||||||
return errs.Wrapw(err, "failed to create download directory", "path", cfg.String("download.directory"))
|
return errs.Wrapw(err, "failed to create download directory", "path", cfg.String("download.directory"))
|
||||||
}
|
}
|
||||||
|
|
||||||
dbPath := fmt.Sprintf("file:%s", cfg.String("db.path"))
|
dbpath := cfg.String("db.path")
|
||||||
sqldb, err := sql.Open(cfg.String("db.driver"), dbPath)
|
dir := filepath.Dir(dbpath)
|
||||||
|
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||||
|
return errs.Wrapw(err, "failed to create database directory", "path", dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
dsn := fmt.Sprintf("file:%s", cfg.String("db.path"))
|
||||||
|
sqldb, err := sql.Open(cfg.String("db.driver"), dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errs.Wrapw(err, "failed to open database",
|
return errs.Wrapw(err, "failed to open database",
|
||||||
"driver", cfg.String("db.driver"),
|
"driver", cfg.String("db.driver"),
|
||||||
"dsn", cfg.String("db.dsn"),
|
"dsn", dsn,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
cleanups = append(cleanups, sqldb.Close)
|
cleanups = append(cleanups, sqldb.Close)
|
||||||
|
|
||||||
sqldb = sqldblogger.OpenDriver(
|
sqldb = sqldblogger.OpenDriver(
|
||||||
dbPath,
|
dsn,
|
||||||
sqldb.Driver(),
|
sqldb.Driver(),
|
||||||
log.SQLLogger{},
|
log.SQLLogger{},
|
||||||
sqldblogger.WithSQLQueryAsMessage(true),
|
sqldblogger.WithSQLQueryAsMessage(true),
|
||||||
|
@ -101,6 +108,7 @@ var Cmd = &cobra.Command{
|
||||||
mux.Handle(v1connect.NewDeviceServiceHandler(handler, connect.WithInterceptors(
|
mux.Handle(v1connect.NewDeviceServiceHandler(handler, connect.WithInterceptors(
|
||||||
validationInterceptor,
|
validationInterceptor,
|
||||||
otelInterceptor,
|
otelInterceptor,
|
||||||
|
server.ErrorMessageInterceptor(),
|
||||||
server.LogInterceptor(),
|
server.LogInterceptor(),
|
||||||
)))
|
)))
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package converts
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/aarondl/opt/omit"
|
"github.com/aarondl/opt/omit"
|
||||||
|
"github.com/tigorlazuardi/bluemage/go/gen/jet/model"
|
||||||
"github.com/tigorlazuardi/bluemage/go/gen/models"
|
"github.com/tigorlazuardi/bluemage/go/gen/models"
|
||||||
device "github.com/tigorlazuardi/bluemage/go/gen/proto/device/v1"
|
device "github.com/tigorlazuardi/bluemage/go/gen/proto/device/v1"
|
||||||
)
|
)
|
||||||
|
@ -30,8 +31,8 @@ type DeviceConverter interface {
|
||||||
// goverter:ignore state sizeCache unknownFields
|
// goverter:ignore state sizeCache unknownFields
|
||||||
ModelsDeviceToCreateDeviceResponse(*models.Device) *device.CreateDeviceResponse
|
ModelsDeviceToCreateDeviceResponse(*models.Device) *device.CreateDeviceResponse
|
||||||
// goverter:ignore state sizeCache unknownFields
|
// goverter:ignore state sizeCache unknownFields
|
||||||
// goverter:map NSFW Nsfw
|
// goverter:useZeroValueOnPointerInconsistency
|
||||||
ModelsDeviceToGetDeviceResponse(*models.Device) *device.GetDeviceResponse
|
JetModelDeviceToGetDeviceResponse(model.Devices) *device.GetDeviceResponse
|
||||||
|
|
||||||
// goverter:ignore Slug SingleFolderMode CreatedAt UpdatedAt
|
// goverter:ignore Slug SingleFolderMode CreatedAt UpdatedAt
|
||||||
// goverter:map Nsfw NSFW
|
// goverter:map Nsfw NSFW
|
||||||
|
|
|
@ -21,7 +21,7 @@ type QueryCollector struct {
|
||||||
|
|
||||||
func (qu *QueryCollector) LogValue() slog.Value {
|
func (qu *QueryCollector) LogValue() slog.Value {
|
||||||
return slog.GroupValue(
|
return slog.GroupValue(
|
||||||
slog.String("statement", strings.ReplaceAll(qu.Statement, `"`, "")),
|
slog.String("statement", qu.Statement),
|
||||||
slog.Any("args", qu.Args),
|
slog.Any("args", qu.Args),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ func (d *DeviceHandler) GetDevice(ctx context.Context, request *connect.Request[
|
||||||
return nil, errs.IntoConnectError(err)
|
return nil, errs.IntoConnectError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
devResp := deviceConvert.ModelsDeviceToGetDeviceResponse(dev)
|
devResp := deviceConvert.JetModelDeviceToGetDeviceResponse(dev)
|
||||||
return connect.NewResponse(devResp), nil
|
return connect.NewResponse(devResp), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
@ -45,3 +46,27 @@ func LogInterceptor() connect.UnaryInterceptorFunc {
|
||||||
}
|
}
|
||||||
return connect.UnaryInterceptorFunc(interceptor)
|
return connect.UnaryInterceptorFunc(interceptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ErrorMessageInterceptor() connect.UnaryInterceptorFunc {
|
||||||
|
return func(uf connect.UnaryFunc) connect.UnaryFunc {
|
||||||
|
return func(ctx context.Context, ar connect.AnyRequest) (connect.AnyResponse, error) {
|
||||||
|
resp, err := uf(ctx, ar)
|
||||||
|
if err == nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
span := trace.SpanFromContext(ctx)
|
||||||
|
if !span.SpanContext().IsValid() {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if cerr := new(connect.Error); errors.As(err, &cerr) {
|
||||||
|
if e := errs.FindError(cerr); e != nil {
|
||||||
|
msg := e.GetMessage()
|
||||||
|
e.Message("[%s] %s", span.SpanContext().SpanID().String(), msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -38,3 +38,20 @@ service DeviceService {
|
||||||
// CountDevices count the number of devices.
|
// CountDevices count the number of devices.
|
||||||
rpc CountDevices(CountDevicesRequest) returns (CountDevicesResponse) {}
|
rpc CountDevices(CountDevicesRequest) returns (CountDevicesResponse) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message Device {
|
||||||
|
string slug = 1;
|
||||||
|
bool disabled = 2;
|
||||||
|
string name = 3;
|
||||||
|
double resolution_x = 4;
|
||||||
|
double resolution_y = 5;
|
||||||
|
double aspect_ratio_tolerance = 6;
|
||||||
|
int32 min_x = 7;
|
||||||
|
int32 min_y = 8;
|
||||||
|
int32 max_x = 9;
|
||||||
|
int32 max_y = 10;
|
||||||
|
bool nsfw = 11;
|
||||||
|
bool single_folder_mode = 12;
|
||||||
|
int64 created_at = 13;
|
||||||
|
int64 updated_at = 14;
|
||||||
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ syntax = "proto3";
|
||||||
package device.v1;
|
package device.v1;
|
||||||
|
|
||||||
import "buf/validate/validate.proto";
|
import "buf/validate/validate.proto";
|
||||||
|
import "device/v1/device.proto";
|
||||||
|
|
||||||
option go_package = "github.com/tigorlazuardi/bluemage/go/gen/proto/device/v1";
|
option go_package = "github.com/tigorlazuardi/bluemage/go/gen/proto/device/v1";
|
||||||
|
|
||||||
|
@ -12,18 +13,5 @@ message GetDeviceRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetDeviceResponse {
|
message GetDeviceResponse {
|
||||||
string slug = 1;
|
Device device = 1;
|
||||||
bool disabled = 2;
|
|
||||||
string name = 3;
|
|
||||||
double resolution_x = 4;
|
|
||||||
double resolution_y = 5;
|
|
||||||
double aspect_ratio_tolerance = 6;
|
|
||||||
int32 min_x = 7;
|
|
||||||
int32 min_y = 8;
|
|
||||||
int32 max_x = 9;
|
|
||||||
int32 max_y = 10;
|
|
||||||
bool nsfw = 11;
|
|
||||||
bool single_folder_mode = 12;
|
|
||||||
int64 created_at = 13;
|
|
||||||
int64 updated_at = 14;
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue