internal/service/http_controllers.go
// Copyright (c) 2023 Adam Prakash Stringer
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted (subject to the limitations in the disclaimer
// below) provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// * Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
//
// NO EXPRESS OR IMPLIED LICENSES TO ANY PARTY'S PATENT RIGHTS ARE GRANTED BY
// THIS LICENSE. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
// CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
// BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package service
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"math"
"net/http"
"reflect"
"strings"
"github.com/dgraph-io/badger/v3"
"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/tauraamui/bluepanda/internal/logging"
"github.com/tauraamui/bluepanda/pkg/kvs"
)
const JSONNumber = byte(99)
type typedEntry struct {
t reflect.Type
e kvs.Entry
}
func handleFetch(log logging.Logger, store kvs.KVDB) fiber.Handler {
return func(c *fiber.Ctx) error {
ttype := c.Params("type")
uuidx := c.Params("uuid")
data := []string{}
log.Debug().Msgf("%s", c.Body())
json.Unmarshal(c.Body(), &data)
blankEntries := convertToBlankTypesEntries(ttype, resolveOwnerID(uuidx), uint32(0), data)
dest := []rawData{}
for _, ent := range blankEntries {
// iterate over all stored values for this entry
prefix := ent.PrefixKey()
if err := store.View(func(txn *badger.Txn) error {
it := txn.NewIterator(badger.DefaultIteratorOptions)
defer it.Close()
var destinationindex uint32 = 0
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
item := it.Item()
if err := item.Value(func(val []byte) error {
ent.Data = val
return nil
}); err != nil {
return err
}
ent.Meta = item.UserMeta()
if len(dest) == 0 || destinationindex >= uint32(len(dest)) {
dest = append(dest, rawData{})
}
var v any
if ent.Meta != JSONNumber {
v = reflect.New(reflect.TypeOf(createInstanceOfKind(reflect.Kind(ent.Meta)))).Interface()
if err := convertFromBytes(ent.Data, v); err != nil {
return err
}
} else {
v = json.Number(string(ent.Data))
}
dest[destinationindex][ent.ColumnName] = v
destinationindex++
}
return nil
}); err != nil {
return err
}
}
log.Debug().Msg("loaded entry successfully...")
return c.JSON(dest)
}
}
type PKS map[string]*badger.Sequence
type rawData map[string]any
func handleInserts(log logging.Logger, store kvs.KVDB, gpks PKS) fiber.Handler {
return func(c *fiber.Ctx) error {
ttype := c.Params("type")
uuidx := c.Params("uuid")
data := rawData{}
decoder := json.NewDecoder(bytes.NewReader(c.Body()))
decoder.UseNumber()
if err := decoder.Decode(&data); err != nil {
return err
}
rowID, err := nextRowID(store, resolveOwnerID(uuidx), ttype, gpks)
if err != nil {
return err
}
entries := convertToEntries(ttype, resolveOwnerID(uuidx), rowID, data, true)
for _, entry := range entries {
if err := kvs.Store(store, entry); err != nil {
log.Error().Msgf("failed to store entry: %v", err)
return c.SendStatus(http.StatusInternalServerError)
}
}
log.Debug().Msg("stored entry successfully...")
return nil
}
}
func resolveOwnerID(v string) kvs.UUID {
if v == "root" {
return kvs.RootOwner{}
}
return uuid.MustParse(v)
}
func loadItemDataIntoEntry(ent *kvs.Entry, fn func(func(val []byte) error) error) error {
return fn(func(val []byte) error {
return convertFromBytes(val, &ent.Data)
})
}
func convertToBlankTypesEntries(tableName string, ownerUUID kvs.UUID, rowID uint32, data []string) []kvs.Entry {
entries := []kvs.Entry{}
for _, k := range data {
e := kvs.Entry{
TableName: tableName,
ColumnName: strings.ToLower(k),
OwnerUUID: ownerUUID,
RowID: rowID,
}
entries = append(entries, e)
}
return entries
}
func convertToBlankEntries(tableName string, ownerUUID kvs.UUID, rowID uint32, data map[string]any) []kvs.Entry {
return convertToEntries(tableName, ownerUUID, rowID, data, false)
}
func convertToEntries(tableName string, ownerUUID kvs.UUID, rowID uint32, data map[string]any, includeData bool) []kvs.Entry {
entries := []kvs.Entry{}
for k, v := range data {
jsonNum, isJSONNumber := v.(json.Number)
e := kvs.Entry{
TableName: tableName,
ColumnName: strings.ToLower(k),
OwnerUUID: ownerUUID,
RowID: rowID,
Meta: byte(reflect.TypeOf(v).Kind()),
}
if isJSONNumber {
e.Meta = JSONNumber
}
if includeData {
if !isJSONNumber {
bd, err := convertToBytes(v)
if err != nil {
return entries
}
e.Data = bd
} else {
e.Data = []byte(jsonNum.String())
}
}
entries = append(entries, e)
}
return entries
}
func createInstanceOfKind(kind reflect.Kind) any {
switch kind {
case reflect.Bool:
return false
case reflect.Int:
return int(0)
case reflect.Int8:
return int8(0)
case reflect.Int16:
return int16(0)
case reflect.Int32:
return int32(0)
case reflect.Int64:
return int64(0)
case reflect.Uint:
return uint(0)
case reflect.Uint8:
return uint8(0)
case reflect.Uint16:
return uint16(0)
case reflect.Uint32:
return uint32(0)
case reflect.Uint64:
return uint64(0)
case reflect.Uintptr:
return uintptr(0)
case reflect.Float32:
return float32(0)
case reflect.Float64:
return float64(0)
case reflect.Complex64:
return complex64(0)
case reflect.Complex128:
return complex128(0)
case reflect.Interface:
return new(interface{})
case reflect.String:
return ""
default:
return nil
}
}
func convertToBytes(i interface{}) ([]byte, error) {
switch v := i.(type) {
case []byte:
return v, nil
case string:
return []byte(v), nil
case int:
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, uint32(v))
return buf, nil
case int32:
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, uint32(v))
return buf, nil
case int64:
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, uint64(v))
return buf, nil
case uint:
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, uint32(v))
return buf, nil
case uint32:
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, v)
return buf, nil
case uint64:
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, v)
return buf, nil
case float32:
bits := math.Float32bits(v)
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, bits)
return buf, nil
case float64:
bits := math.Float64bits(v)
buf := make([]byte, 8)
binary.BigEndian.PutUint64(buf, bits)
return buf, nil
case bool:
if v {
return []byte{1}, nil
} else {
return []byte{0}, nil
}
case json.Number:
return []byte(v), nil
default:
return nil, fmt.Errorf("unsupported type")
}
}
func convertFromBytes(data []byte, i interface{}) error {
if reflect.TypeOf(i).Kind() != reflect.Ptr {
return fmt.Errorf("destination must be a pointer")
}
switch v := i.(type) {
case *[]byte:
*v = data
return nil
case *string:
*v = string(data)
return nil
case *int:
if len(data) < 4 {
return fmt.Errorf("insufficient data for int")
}
*v = int(binary.BigEndian.Uint32(data))
return nil
case *int32:
if len(data) < 4 {
return fmt.Errorf("insufficient data for int32")
}
*v = int32(binary.BigEndian.Uint32(data))
return nil
case *int64:
if len(data) < 8 {
return fmt.Errorf("insufficient data for int64")
}
*v = int64(binary.BigEndian.Uint64(data))
return nil
case *uint:
if len(data) < 4 {
return fmt.Errorf("insufficient data for uint")
}
*v = uint(binary.BigEndian.Uint32(data))
return nil
case *uint32:
if len(data) < 4 {
return fmt.Errorf("insufficient data for uint32")
}
*v = binary.BigEndian.Uint32(data)
return nil
case *uint64:
if len(data) < 8 {
return fmt.Errorf("insufficient data for uint64")
}
*v = binary.BigEndian.Uint64(data)
return nil
case *float32:
if len(data) < 4 {
return fmt.Errorf("insufficient data for float32")
}
bits := binary.BigEndian.Uint32(data)
*v = math.Float32frombits(bits)
return nil
case *float64:
if len(data) < 8 {
return fmt.Errorf("insufficient data for float64")
}
bits := binary.BigEndian.Uint64(data)
*v = math.Float64frombits(bits)
return nil
case *bool:
if len(data) < 1 {
return fmt.Errorf("insufficient data for bool")
}
*v = data[0] != 0
return nil
default:
return fmt.Errorf("unsupported type")
}
}
func nextRowID(db kvs.KVDB, owner kvs.UUID, tableName string, pks map[string]*badger.Sequence) (uint32, error) {
seq, err := resolveSequence(db, fmt.Sprintf("%s.%s", owner, tableName), pks)
if err != nil {
return 0, err
}
s, err := seq.Next()
if err != nil {
return 0, err
}
return uint32(s), nil
}
func nextSequence(seq *badger.Sequence) (uint32, error) {
s, err := seq.Next()
if err != nil {
return 0, err
}
return uint32(s), nil
}
func resolveSequence(db kvs.KVDB, sequenceKey string, pks map[string]*badger.Sequence) (*badger.Sequence, error) {
seq, ok := pks[sequenceKey]
var err error
if !ok {
seq, err = db.GetSeq([]byte(sequenceKey), 1)
if err != nil {
return nil, err
}
pks[sequenceKey] = seq
}
return seq, nil
}