ft/adds table queries

TODO: Add support for multi table requests - currently the existing fields are being merged.
This commit is contained in:
itsscb 2023-10-17 23:16:31 +02:00
parent 03c452e748
commit 6b1f30f0f7
12 changed files with 255 additions and 2 deletions

View File

@ -52,10 +52,10 @@ migratedown:
docker run --name migratedown --privileged=true --rm -v $(PWD)/bff/db/migration:/migrations --network host migrate/migrate -path=/migrations/ -database $(DB_URL) down
createdb:
docker exec -it postgres createdb --username=root --owner=root df
docker exec -it df-bff_postgres_1 createdb --username=root --owner=root df
dropdb:
docker exec -it postgres dropdb df
docker exec -it df-bff_postgres_1 dropdb df
sqlc:
cd bff && \

View File

@ -0,0 +1 @@
DROP TABLE IF EXISTS "queries";

View File

@ -0,0 +1,17 @@
CREATE TABLE "queries" (
"id" bigserial UNIQUE PRIMARY KEY NOT NULL,
"name" varchar NOT NULL,
"query" varchar NOT NULL
);
INSERT INTO "queries" (name, query)
VALUES (
'all_accounts',
'SELECT * FROM accounts'
);
INSERT INTO "queries" (name, query)
VALUES (
'all_persons',
'SELECT * FROM persons'
);

View File

@ -614,6 +614,21 @@ func (mr *MockStoreMockRecorder) GetProvider(arg0, arg1 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetProvider", reflect.TypeOf((*MockStore)(nil).GetProvider), arg0, arg1)
}
// GetQueryByName mocks base method.
func (m *MockStore) GetQueryByName(arg0 context.Context, arg1 string) (db.Query, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetQueryByName", arg0, arg1)
ret0, _ := ret[0].(db.Query)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetQueryByName indicates an expected call of GetQueryByName.
func (mr *MockStoreMockRecorder) GetQueryByName(arg0, arg1 any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetQueryByName", reflect.TypeOf((*MockStore)(nil).GetQueryByName), arg0, arg1)
}
// GetReturn mocks base method.
func (m *MockStore) GetReturn(arg0 context.Context, arg1 uint64) (db.Return, error) {
m.ctrl.T.Helper()
@ -854,6 +869,26 @@ func (mr *MockStoreMockRecorder) ListSessions(arg0, arg1 any) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListSessions", reflect.TypeOf((*MockStore)(nil).ListSessions), arg0, arg1)
}
// Query mocks base method.
func (m *MockStore) Query(arg0 context.Context, arg1 string, arg2 ...any) ([]map[string]any, error) {
m.ctrl.T.Helper()
varargs := []any{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Query", varargs...)
ret0, _ := ret[0].([]map[string]any)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Query indicates an expected call of Query.
func (mr *MockStoreMockRecorder) Query(arg0, arg1 any, arg2 ...any) *gomock.Call {
mr.mock.ctrl.T.Helper()
varargs := append([]any{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Query", reflect.TypeOf((*MockStore)(nil).Query), varargs...)
}
// UpdateAccount mocks base method.
func (m *MockStore) UpdateAccount(arg0 context.Context, arg1 db.UpdateAccountParams) (db.Account, error) {
m.ctrl.T.Helper()

3
bff/db/query/query.sql Normal file
View File

@ -0,0 +1,3 @@
-- name: GetQueryByName :one
SELECT * FROM queries
WHERE "name" = sqlc.arg(name);

View File

@ -108,6 +108,12 @@ type Provider struct {
Changed time.Time `json:"changed"`
}
type Query struct {
ID uint64 `json:"id"`
Name string `json:"name"`
Query string `json:"query"`
}
type Return struct {
ID uint64 `json:"id"`
PersonID uint64 `json:"person_id"`

View File

@ -59,6 +59,7 @@ type Querier interface {
GetPayment(ctx context.Context, id uint64) (Payment, error)
GetPerson(ctx context.Context, id uint64) (Person, error)
GetProvider(ctx context.Context, id uint64) (Provider, error)
GetQueryByName(ctx context.Context, name string) (Query, error)
GetReturn(ctx context.Context, id uint64) (Return, error)
GetReturnIDsByPersonID(ctx context.Context, personID uint64) ([]uint64, error)
GetReturns(ctx context.Context, id uint64) ([]Return, error)

57
bff/db/sqlc/query.go Normal file
View File

@ -0,0 +1,57 @@
package db
import (
"context"
"database/sql"
"log/slog"
)
var (
restrictedFields = map[string]bool{
"passwordhash": true,
}
)
func (store *SQLStore) Query(ctx context.Context, statement string, args ...interface{}) (result []map[string]interface{}, err error) {
var rows *sql.Rows
// var err error
if len(args) > 0 {
rows, err = store.db.QueryContext(ctx, statement, args)
} else {
rows, err = store.db.QueryContext(ctx, statement)
}
if err != nil {
slog.Error("db_query", slog.String("statement", statement), slog.String("error", err.Error()))
return nil, err
}
cols, err := rows.Columns()
if err != nil {
slog.Error("db_query: getting columns", slog.String("error", err.Error()))
return nil, err
}
for rows.Next() {
values := make([]interface{}, len(cols))
res := make(map[string]interface{})
for i := range values {
values[i] = new(interface{})
}
err = rows.Scan(values...)
if err != nil {
slog.Error("db_query: scanning rows", slog.String("error", err.Error()))
return nil, err
}
for i, col := range cols {
if restrictedFields[col] {
continue
}
res[col] = *(values[i].(*interface{}))
}
result = append(result, res)
}
return result, rows.Err()
}

22
bff/db/sqlc/query.sql.go Normal file
View File

@ -0,0 +1,22 @@
// Code generated by sqlc. DO NOT EDIT.
// versions:
// sqlc v1.22.0
// source: query.sql
package db
import (
"context"
)
const getQueryByName = `-- name: GetQueryByName :one
SELECT id, name, query FROM queries
WHERE "name" = $1
`
func (q *Queries) GetQueryByName(ctx context.Context, name string) (Query, error) {
row := q.db.QueryRowContext(ctx, getQueryByName, name)
var i Query
err := row.Scan(&i.ID, &i.Name, &i.Query)
return i, err
}

View File

@ -17,6 +17,7 @@ type Store interface {
DeletePersonTx(ctx context.Context, id uint64) error
CreateDocumentTx(ctx context.Context, arg CreateDocumentTxParams) (doc Document, code int, err error)
DeleteDocumentTx(ctx context.Context, id uint64) (code codes.Code, err error)
Query(ctx context.Context, statement string, args ...interface{}) (result []map[string]interface{}, err error)
}
// Store provides all functions to execute db queries and transactions

109
bff/gw/query.go Normal file
View File

@ -0,0 +1,109 @@
package gw
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"github.com/gin-gonic/gin"
)
func (server *Server) Query(ctx *gin.Context) {
// authHeader := ctx.GetHeader("authorization")
// authFields := strings.Fields(authHeader)
// if len(authFields) != 2 {
// ctx.JSON(http.StatusUnauthorized, errorResponse(errors.New("invalid or missing authorization header")))
// return
// }
// token := authFields[1]
// authPayload, err := server.tokenMaker.VerifyToken(token)
// if err != nil {
// ctx.JSON(http.StatusUnauthorized, errorResponse(errors.New("invalid authorization header")))
// return
// }
// account, err := server.store.GetAccount(ctx, authPayload.AccountID)
// if err != nil {
// ctx.JSON(http.StatusNotFound, errorResponse(errors.New("account not found")))
// return
// }
var jsonData map[string]interface{}
data, err := io.ReadAll(ctx.Request.Body)
if err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(errors.New("failed to parse request body")))
return
}
fmt.Println(string(data))
if err = json.Unmarshal(data, &jsonData); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(errors.New("failed to unmarshal request body json")))
return
}
name, ok := jsonData["name"]
if !ok {
ctx.JSON(http.StatusBadRequest, errorResponse(errors.New("required query name is missing")))
return
}
q, err := server.store.GetQueryByName(ctx, fmt.Sprintf("%s", name))
if err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(errors.New("failed to get query")))
return
}
res, err := server.store.Query(ctx, q.Query)
if err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(fmt.Errorf("failed to run query: %v", err)))
return
}
ctx.JSON(http.StatusOK, res)
return
// var req *uploadDocumentRequest
// err = ctx.ShouldBind(&req)
// if err != nil {
// ctx.JSON(http.StatusBadRequest, errorResponse(errors.New("failed to parse request")))
// return
// }
// r := db.CreateDocumentTxParams{
// AccountID: account.ID,
// PersonID: req.PersonID,
// MailID: req.MailID,
// File: req.File,
// Creator: account.Email,
// }
// doc, code, err := server.store.CreateDocumentTx(ctx, r)
// if err != nil {
// if code == http.StatusInternalServerError {
// slog.Error("create_document", slog.Int64("invoked_by", int64(authPayload.AccountID)), slog.String("document_name", req.File.Filename), slog.String("error", err.Error()))
// }
// ctx.JSON(code, errorResponse(err))
// return
// }
// ctx.JSON(http.StatusOK, doc)
}
func clearStatement(statement string) string {
statement = strings.ReplaceAll(statement, ";", "")
statement = strings.ReplaceAll(statement, "'", "")
statement = strings.ReplaceAll(statement, "\"", "")
statement = strings.ReplaceAll(statement, "`", "")
statement = strings.ReplaceAll(statement, "DROP", "")
statement = strings.ReplaceAll(statement, "DELETE", "")
return statement
}

View File

@ -144,6 +144,7 @@ func runGatewayServer(config util.Config, store db.Store, swaggerFS http.FileSys
mux := gin.New()
mux.Group("v1/*{grpc_gateway}").Any("", gin.WrapH(grpcMux))
mux.POST("documents/upload", server.UploadDocument)
mux.POST("query", server.Query)
mux.StaticFS("/swagger/", swaggerFS)
listener, err := net.Listen("tcp", config.HTTPServerAddress)