mirror of
https://github.com/wavetermdev/backup.git
synced 2026-08-05 13:57:07 -07:00
move generic db functions to dbutil (for use with PromptCentral)
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
package dbutil
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func QuickSetStr(strVal *string, m map[string]interface{}, name string) {
|
||||
v, ok := m[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ival, ok := v.(int64)
|
||||
if ok {
|
||||
*strVal = strconv.FormatInt(ival, 10)
|
||||
return
|
||||
}
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
*strVal = str
|
||||
}
|
||||
|
||||
func QuickSetInt64(ival *int64, m map[string]interface{}, name string) {
|
||||
v, ok := m[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
sqlInt, ok := v.(int64)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
*ival = sqlInt
|
||||
}
|
||||
|
||||
func QuickSetBool(bval *bool, m map[string]interface{}, name string) {
|
||||
v, ok := m[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
sqlInt, ok := v.(int64)
|
||||
if ok {
|
||||
if sqlInt > 0 {
|
||||
*bval = true
|
||||
}
|
||||
return
|
||||
}
|
||||
sqlBool, ok := v.(bool)
|
||||
if ok {
|
||||
*bval = sqlBool
|
||||
}
|
||||
}
|
||||
|
||||
func QuickSetBytes(bval *[]byte, m map[string]interface{}, name string) {
|
||||
v, ok := m[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
sqlBytes, ok := v.([]byte)
|
||||
if ok {
|
||||
*bval = sqlBytes
|
||||
}
|
||||
}
|
||||
|
||||
func getByteArr(m map[string]any, name string, def string) ([]byte, bool) {
|
||||
v, ok := m[name]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
barr, ok := v.([]byte)
|
||||
if !ok {
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
barr = []byte(str)
|
||||
}
|
||||
if len(barr) == 0 {
|
||||
barr = []byte(def)
|
||||
}
|
||||
return barr, true
|
||||
}
|
||||
|
||||
func QuickSetJson(ptr interface{}, m map[string]interface{}, name string) {
|
||||
barr, ok := getByteArr(m, name, "{}")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
json.Unmarshal(barr, ptr)
|
||||
}
|
||||
|
||||
func QuickSetNullableJson(ptr interface{}, m map[string]interface{}, name string) {
|
||||
barr, ok := getByteArr(m, name, "null")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
json.Unmarshal(barr, ptr)
|
||||
}
|
||||
|
||||
func QuickSetJsonArr(ptr interface{}, m map[string]interface{}, name string) {
|
||||
barr, ok := getByteArr(m, name, "[]")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
json.Unmarshal(barr, ptr)
|
||||
}
|
||||
|
||||
func QuickNullableJson(v interface{}) string {
|
||||
if v == nil {
|
||||
return "null"
|
||||
}
|
||||
barr, _ := json.Marshal(v)
|
||||
return string(barr)
|
||||
}
|
||||
|
||||
func QuickJson(v interface{}) string {
|
||||
if v == nil {
|
||||
return "{}"
|
||||
}
|
||||
barr, _ := json.Marshal(v)
|
||||
return string(barr)
|
||||
}
|
||||
|
||||
func QuickJsonBytes(v interface{}) []byte {
|
||||
if v == nil {
|
||||
return []byte("{}")
|
||||
}
|
||||
barr, _ := json.Marshal(v)
|
||||
return barr
|
||||
}
|
||||
|
||||
func QuickJsonArr(v interface{}) string {
|
||||
if v == nil {
|
||||
return "[]"
|
||||
}
|
||||
barr, _ := json.Marshal(v)
|
||||
return string(barr)
|
||||
}
|
||||
|
||||
func QuickJsonArrBytes(v interface{}) []byte {
|
||||
if v == nil {
|
||||
return []byte("[]")
|
||||
}
|
||||
barr, _ := json.Marshal(v)
|
||||
return barr
|
||||
}
|
||||
|
||||
func QuickScanJson(ptr interface{}, val interface{}) error {
|
||||
barrVal, ok := val.([]byte)
|
||||
if !ok {
|
||||
strVal, ok := val.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot scan '%T' into '%T'", val, ptr)
|
||||
}
|
||||
barrVal = []byte(strVal)
|
||||
}
|
||||
if len(barrVal) == 0 {
|
||||
barrVal = []byte("{}")
|
||||
}
|
||||
return json.Unmarshal(barrVal, ptr)
|
||||
}
|
||||
|
||||
func QuickValueJson(v interface{}) (driver.Value, error) {
|
||||
if v == nil {
|
||||
return "{}", nil
|
||||
}
|
||||
barr, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return string(barr), nil
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package dbutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/sawka/txwrap"
|
||||
)
|
||||
|
||||
type DBMappable interface {
|
||||
UseDBMap()
|
||||
}
|
||||
|
||||
type MapConverter interface {
|
||||
ToMap() map[string]interface{}
|
||||
FromMap(map[string]interface{}) bool
|
||||
}
|
||||
|
||||
type HasSimpleKey interface {
|
||||
GetSimpleKey() string
|
||||
}
|
||||
|
||||
type MapConverterPtr[T any] interface {
|
||||
MapConverter
|
||||
*T
|
||||
}
|
||||
|
||||
type DBMappablePtr[T any] interface {
|
||||
DBMappable
|
||||
*T
|
||||
}
|
||||
|
||||
func FromMap[PT MapConverterPtr[T], T any](m map[string]any) PT {
|
||||
if len(m) == 0 {
|
||||
return nil
|
||||
}
|
||||
rtn := PT(new(T))
|
||||
ok := rtn.FromMap(m)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return rtn
|
||||
}
|
||||
|
||||
func GetMapGen[PT MapConverterPtr[T], T any](tx *txwrap.TxWrap, query string, args ...interface{}) PT {
|
||||
m := tx.GetMap(query, args...)
|
||||
return FromMap[PT](m)
|
||||
}
|
||||
|
||||
func GetMappable[PT DBMappablePtr[T], T any](tx *txwrap.TxWrap, query string, args ...interface{}) PT {
|
||||
rtn := PT(new(T))
|
||||
m := tx.GetMap(query, args...)
|
||||
if len(m) == 0 {
|
||||
return nil
|
||||
}
|
||||
FromDBMap(rtn, m)
|
||||
return rtn
|
||||
}
|
||||
|
||||
func SelectMapsGen[PT MapConverterPtr[T], T any](tx *txwrap.TxWrap, query string, args ...interface{}) []PT {
|
||||
var rtn []PT
|
||||
marr := tx.SelectMaps(query, args...)
|
||||
for _, m := range marr {
|
||||
val := FromMap[PT](m)
|
||||
if val != nil {
|
||||
rtn = append(rtn, val)
|
||||
}
|
||||
}
|
||||
return rtn
|
||||
}
|
||||
|
||||
func MakeGenMap[T HasSimpleKey](arr []T) map[string]T {
|
||||
rtn := make(map[string]T)
|
||||
for _, val := range arr {
|
||||
rtn[val.GetSimpleKey()] = val
|
||||
}
|
||||
return rtn
|
||||
}
|
||||
|
||||
func isStructType(rt reflect.Type) bool {
|
||||
if rt.Kind() == reflect.Struct {
|
||||
return true
|
||||
}
|
||||
if rt.Kind() == reflect.Pointer && rt.Elem().Kind() == reflect.Struct {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isByteArrayType(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8
|
||||
}
|
||||
|
||||
func ToDBMap(v DBMappable, useBytes bool) map[string]interface{} {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() == reflect.Pointer {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
if rv.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("invalid type %T (non-struct) passed to StructToDBMap", v))
|
||||
}
|
||||
rt := rv.Type()
|
||||
m := make(map[string]interface{})
|
||||
numFields := rt.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
field := rt.Field(i)
|
||||
fieldVal := rv.FieldByIndex(field.Index)
|
||||
dbName := field.Tag.Get("dbmap")
|
||||
if dbName == "" {
|
||||
dbName = strings.ToLower(field.Name)
|
||||
}
|
||||
if dbName == "-" {
|
||||
continue
|
||||
}
|
||||
if isByteArrayType(field.Type) {
|
||||
m[dbName] = fieldVal.Interface()
|
||||
} else if field.Type.Kind() == reflect.Slice {
|
||||
if useBytes {
|
||||
m[dbName] = QuickJsonArrBytes(fieldVal.Interface())
|
||||
} else {
|
||||
m[dbName] = QuickJsonArr(fieldVal.Interface())
|
||||
}
|
||||
} else if isStructType(field.Type) {
|
||||
if useBytes {
|
||||
m[dbName] = QuickJsonBytes(fieldVal.Interface())
|
||||
} else {
|
||||
m[dbName] = QuickJson(fieldVal.Interface())
|
||||
}
|
||||
} else {
|
||||
m[dbName] = fieldVal.Interface()
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func FromDBMap(v DBMappable, m map[string]interface{}) {
|
||||
if v == nil {
|
||||
panic("StructFromDBMap, v cannot be nil")
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() == reflect.Pointer {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
if rv.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("invalid type %T (non-struct) passed to StructFromDBMap", v))
|
||||
}
|
||||
rt := rv.Type()
|
||||
numFields := rt.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
field := rt.Field(i)
|
||||
fieldVal := rv.FieldByIndex(field.Index)
|
||||
dbName := field.Tag.Get("dbmap")
|
||||
if dbName == "" {
|
||||
dbName = strings.ToLower(field.Name)
|
||||
}
|
||||
if dbName == "-" {
|
||||
continue
|
||||
}
|
||||
if isByteArrayType(field.Type) {
|
||||
barrVal := fieldVal.Addr().Interface()
|
||||
QuickSetBytes(barrVal.(*[]byte), m, dbName)
|
||||
} else if field.Type.Kind() == reflect.Slice {
|
||||
QuickSetJsonArr(fieldVal.Addr().Interface(), m, dbName)
|
||||
} else if isStructType(field.Type) {
|
||||
QuickSetJson(fieldVal.Addr().Interface(), m, dbName)
|
||||
} else if field.Type.Kind() == reflect.String {
|
||||
strVal := fieldVal.Addr().Interface()
|
||||
QuickSetStr(strVal.(*string), m, dbName)
|
||||
} else if field.Type.Kind() == reflect.Int64 {
|
||||
intVal := fieldVal.Addr().Interface()
|
||||
QuickSetInt64(intVal.(*int64), m, dbName)
|
||||
} else if field.Type.Kind() == reflect.Bool {
|
||||
boolVal := fieldVal.Addr().Interface()
|
||||
QuickSetBool(boolVal.(*bool), m, dbName)
|
||||
} else {
|
||||
panic(fmt.Sprintf("StructFromDBMap invalid field type %v in %T", fieldVal.Type(), v))
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
-29
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/scripthaus-dev/mshell/pkg/base"
|
||||
"github.com/scripthaus-dev/mshell/pkg/packet"
|
||||
"github.com/scripthaus-dev/mshell/pkg/shexec"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/dbutil"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
|
||||
)
|
||||
|
||||
@@ -88,7 +89,7 @@ func GetAllRemotes(ctx context.Context) ([]*RemoteType, error) {
|
||||
query := `SELECT * FROM remote ORDER BY remoteidx`
|
||||
marr := tx.SelectMaps(query)
|
||||
for _, m := range marr {
|
||||
rtn = append(rtn, FromMap[*RemoteType](m))
|
||||
rtn = append(rtn, dbutil.FromMap[*RemoteType](m))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -103,7 +104,7 @@ func GetRemoteByAlias(ctx context.Context, alias string) (*RemoteType, error) {
|
||||
err := WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `SELECT * FROM remote WHERE remotealias = ?`
|
||||
m := tx.GetMap(query, alias)
|
||||
remote = FromMap[*RemoteType](m)
|
||||
remote = dbutil.FromMap[*RemoteType](m)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -117,7 +118,7 @@ func GetRemoteById(ctx context.Context, remoteId string) (*RemoteType, error) {
|
||||
err := WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `SELECT * FROM remote WHERE remoteid = ?`
|
||||
m := tx.GetMap(query, remoteId)
|
||||
remote = FromMap[*RemoteType](m)
|
||||
remote = dbutil.FromMap[*RemoteType](m)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -131,7 +132,7 @@ func GetLocalRemote(ctx context.Context) (*RemoteType, error) {
|
||||
err := WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `SELECT * FROM remote WHERE local`
|
||||
m := tx.GetMap(query)
|
||||
remote = FromMap[*RemoteType](m)
|
||||
remote = dbutil.FromMap[*RemoteType](m)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -144,7 +145,7 @@ func GetRemoteByCanonicalName(ctx context.Context, cname string) (*RemoteType, e
|
||||
var remote *RemoteType
|
||||
err := WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `SELECT * FROM remote WHERE remotecanonicalname = ?`
|
||||
remote = GetMapGen[*RemoteType](tx, query, cname)
|
||||
remote = dbutil.GetMapGen[*RemoteType](tx, query, cname)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -157,7 +158,7 @@ func GetRemoteByPhysicalId(ctx context.Context, physicalId string) (*RemoteType,
|
||||
var remote *RemoteType
|
||||
err := WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `SELECT * FROM remote WHERE physicalid = ?`
|
||||
remote = GetMapGen[*RemoteType](tx, query, physicalId)
|
||||
remote = dbutil.GetMapGen[*RemoteType](tx, query, physicalId)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -342,7 +343,7 @@ func runHistoryQuery(tx *TxWrap, opts HistoryQueryOpts, realOffset int, itemLimi
|
||||
marr := tx.SelectMaps(query, queryArgs...)
|
||||
rtn := make([]*HistoryItemType, len(marr))
|
||||
for idx, m := range marr {
|
||||
hitem := FromMap[*HistoryItemType](m)
|
||||
hitem := dbutil.FromMap[*HistoryItemType](m)
|
||||
rtn[idx] = hitem
|
||||
}
|
||||
return rtn, nil
|
||||
@@ -367,7 +368,7 @@ func GetHistoryItems(ctx context.Context, opts HistoryQueryOpts) (*HistoryQueryR
|
||||
func GetHistoryItemByLineNum(ctx context.Context, screenId string, lineNum int) (*HistoryItemType, error) {
|
||||
return WithTxRtn(ctx, func(tx *TxWrap) (*HistoryItemType, error) {
|
||||
query := `SELECT * FROM history WHERE screenid = ? AND linenum = ?`
|
||||
hitem := GetMapGen[*HistoryItemType](tx, query, screenId, lineNum)
|
||||
hitem := dbutil.GetMapGen[*HistoryItemType](tx, query, screenId, lineNum)
|
||||
return hitem, nil
|
||||
})
|
||||
}
|
||||
@@ -438,12 +439,12 @@ func GetAllSessions(ctx context.Context) (*ModelUpdate, error) {
|
||||
session.Full = true
|
||||
}
|
||||
query = `SELECT * FROM screen ORDER BY archived, screenidx, archivedts`
|
||||
update.Screens = SelectMapsGen[*ScreenType](tx, query)
|
||||
update.Screens = dbutil.SelectMapsGen[*ScreenType](tx, query)
|
||||
for _, screen := range update.Screens {
|
||||
screen.Full = true
|
||||
}
|
||||
query = `SELECT * FROM remote_instance`
|
||||
riArr := SelectMapsGen[*RemoteInstance](tx, query)
|
||||
riArr := dbutil.SelectMapsGen[*RemoteInstance](tx, query)
|
||||
for _, ri := range riArr {
|
||||
s := sessionMap[ri.SessionId]
|
||||
if s != nil {
|
||||
@@ -459,14 +460,14 @@ func GetAllSessions(ctx context.Context) (*ModelUpdate, error) {
|
||||
func GetScreenLinesById(ctx context.Context, screenId string) (*ScreenLinesType, error) {
|
||||
return WithTxRtn(ctx, func(tx *TxWrap) (*ScreenLinesType, error) {
|
||||
query := `SELECT screenid FROM screen WHERE screenid = ?`
|
||||
screen := GetMappable[*ScreenLinesType](tx, query, screenId)
|
||||
screen := dbutil.GetMappable[*ScreenLinesType](tx, query, screenId)
|
||||
if screen == nil {
|
||||
return nil, nil
|
||||
}
|
||||
query = `SELECT * FROM line WHERE screenid = ? ORDER BY linenum`
|
||||
tx.Select(&screen.Lines, query, screen.ScreenId)
|
||||
query = `SELECT * FROM cmd WHERE cmdid IN (SELECT cmdid FROM line WHERE screenid = ?)`
|
||||
screen.Cmds = SelectMapsGen[*CmdType](tx, query, screen.ScreenId)
|
||||
screen.Cmds = dbutil.SelectMapsGen[*CmdType](tx, query, screen.ScreenId)
|
||||
return screen, nil
|
||||
})
|
||||
}
|
||||
@@ -475,7 +476,7 @@ func GetScreenLinesById(ctx context.Context, screenId string) (*ScreenLinesType,
|
||||
func GetSessionScreens(ctx context.Context, sessionId string) ([]*ScreenType, error) {
|
||||
return WithTxRtn(ctx, func(tx *TxWrap) ([]*ScreenType, error) {
|
||||
query := `SELECT * FROM screen WHERE sessionid = ? ORDER BY archived, screenidx, archivedts`
|
||||
rtn := SelectMapsGen[*ScreenType](tx, query, sessionId)
|
||||
rtn := dbutil.SelectMapsGen[*ScreenType](tx, query, sessionId)
|
||||
for _, screen := range rtn {
|
||||
screen.Full = true
|
||||
}
|
||||
@@ -716,7 +717,7 @@ func InsertScreen(ctx context.Context, sessionId string, origScreenName string,
|
||||
func GetScreenById(ctx context.Context, screenId string) (*ScreenType, error) {
|
||||
return WithTxRtn(ctx, func(tx *TxWrap) (*ScreenType, error) {
|
||||
query := `SELECT * FROM screen WHERE screenid = ?`
|
||||
screen := GetMapGen[*ScreenType](tx, query, screenId)
|
||||
screen := dbutil.GetMapGen[*ScreenType](tx, query, screenId)
|
||||
screen.Full = true
|
||||
return screen, nil
|
||||
})
|
||||
@@ -766,7 +767,7 @@ func GetLineCmdByLineId(ctx context.Context, screenId string, lineId string) (*L
|
||||
var cmdRtn *CmdType
|
||||
if lineVal.CmdId != "" {
|
||||
query = `SELECT * FROM cmd WHERE screenid = ? AND cmdid = ?`
|
||||
cmdRtn = GetMapGen[*CmdType](tx, query, screenId, lineVal.CmdId)
|
||||
cmdRtn = dbutil.GetMapGen[*CmdType](tx, query, screenId, lineVal.CmdId)
|
||||
}
|
||||
return &lineVal, cmdRtn, nil
|
||||
})
|
||||
@@ -781,7 +782,7 @@ func GetLineCmdByCmdId(ctx context.Context, screenId string, cmdId string) (*Lin
|
||||
return nil, nil, nil
|
||||
}
|
||||
query = `SELECT * FROM cmd WHERE screenid = ? AND cmdid = ?`
|
||||
cmdRtn := GetMapGen[*CmdType](tx, query, screenId, cmdId)
|
||||
cmdRtn := dbutil.GetMapGen[*CmdType](tx, query, screenId, cmdId)
|
||||
return &lineVal, cmdRtn, nil
|
||||
})
|
||||
}
|
||||
@@ -832,7 +833,7 @@ func GetCmdByScreenId(ctx context.Context, screenId string, cmdId string) (*CmdT
|
||||
var cmd *CmdType
|
||||
err := WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `SELECT * FROM cmd WHERE screenid = ? AND cmdid = ?`
|
||||
cmd = GetMapGen[*CmdType](tx, query, screenId, cmdId)
|
||||
cmd = dbutil.GetMapGen[*CmdType](tx, query, screenId, cmdId)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@@ -1181,7 +1182,7 @@ func GetRemoteInstance(ctx context.Context, sessionId string, screenId string, r
|
||||
var ri *RemoteInstance
|
||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `SELECT * FROM remote_instance WHERE sessionid = ? AND screenid = ? AND remoteownerid = ? AND remoteid = ? AND name = ?`
|
||||
ri = GetMapGen[*RemoteInstance](tx, query, sessionId, screenId, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name)
|
||||
ri = dbutil.GetMapGen[*RemoteInstance](tx, query, sessionId, screenId, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name)
|
||||
return nil
|
||||
})
|
||||
if txErr != nil {
|
||||
@@ -1227,7 +1228,7 @@ func UpdateRemoteState(ctx context.Context, sessionId string, screenId string, r
|
||||
return fmt.Errorf("cannot update remote instance state: %w", err)
|
||||
}
|
||||
query := `SELECT * FROM remote_instance WHERE sessionid = ? AND screenid = ? AND remoteownerid = ? AND remoteid = ? AND name = ?`
|
||||
ri = GetMapGen[*RemoteInstance](tx, query, sessionId, screenId, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name)
|
||||
ri = dbutil.GetMapGen[*RemoteInstance](tx, query, sessionId, screenId, remotePtr.OwnerId, remotePtr.RemoteId, remotePtr.Name)
|
||||
if ri == nil {
|
||||
ri = &RemoteInstance{
|
||||
RIId: scbase.GenPromptUUID(),
|
||||
@@ -1408,7 +1409,7 @@ func GetRunningScreenCmds(ctx context.Context, screenId string) ([]*CmdType, err
|
||||
var rtn []*CmdType
|
||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `SELECT * from cmd WHERE cmdid IN (SELECT cmdid FROM line WHERE screenid = ?) AND status = ?`
|
||||
rtn = SelectMapsGen[*CmdType](tx, query, screenId, CmdStatusRunning)
|
||||
rtn = dbutil.SelectMapsGen[*CmdType](tx, query, screenId, CmdStatusRunning)
|
||||
return nil
|
||||
})
|
||||
if txErr != nil {
|
||||
@@ -1824,7 +1825,7 @@ func GetFullState(ctx context.Context, ssPtr ShellStatePtr) (*packet.ShellState,
|
||||
}
|
||||
for idx, diffHash := range ssPtr.DiffHashArr {
|
||||
query = `SELECT * FROM state_diff WHERE diffhash = ?`
|
||||
stateDiff := GetMapGen[*StateDiff](tx, query, diffHash)
|
||||
stateDiff := dbutil.GetMapGen[*StateDiff](tx, query, diffHash)
|
||||
if stateDiff == nil {
|
||||
return fmt.Errorf("ShellStateDiff %s not found", diffHash)
|
||||
}
|
||||
@@ -1953,7 +1954,7 @@ func GetRIsForScreen(ctx context.Context, sessionId string, screenId string) ([]
|
||||
var rtn []*RemoteInstance
|
||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `SELECT * FROM remote_instance WHERE sessionid = ? AND (screenid = '' OR screenid = ?)`
|
||||
rtn = SelectMapsGen[*RemoteInstance](tx, query, sessionId, screenId)
|
||||
rtn = dbutil.SelectMapsGen[*RemoteInstance](tx, query, sessionId, screenId)
|
||||
return nil
|
||||
})
|
||||
if txErr != nil {
|
||||
@@ -2112,12 +2113,12 @@ func GetBookmarks(ctx context.Context, tag string) ([]*BookmarkType, error) {
|
||||
var query string
|
||||
if tag == "" {
|
||||
query = `SELECT * FROM bookmark`
|
||||
bms = SelectMapsGen[*BookmarkType](tx, query)
|
||||
bms = dbutil.SelectMapsGen[*BookmarkType](tx, query)
|
||||
} else {
|
||||
query = `SELECT * FROM bookmark WHERE EXISTS (SELECT 1 FROM json_each(tags) WHERE value = ?)`
|
||||
bms = SelectMapsGen[*BookmarkType](tx, query, tag)
|
||||
bms = dbutil.SelectMapsGen[*BookmarkType](tx, query, tag)
|
||||
}
|
||||
bmMap := MakeGenMap(bms)
|
||||
bmMap := dbutil.MakeGenMap(bms)
|
||||
var orders []bookmarkOrderType
|
||||
query = `SELECT bookmarkid, orderidx FROM bookmark_order WHERE tag = ?`
|
||||
tx.Select(&orders, query, tag)
|
||||
@@ -2139,7 +2140,7 @@ func GetBookmarkById(ctx context.Context, bookmarkId string, tag string) (*Bookm
|
||||
var rtn *BookmarkType
|
||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||
query := `SELECT * FROM bookmark WHERE bookmarkid = ?`
|
||||
rtn = GetMapGen[*BookmarkType](tx, query, bookmarkId)
|
||||
rtn = dbutil.GetMapGen[*BookmarkType](tx, query, bookmarkId)
|
||||
if rtn == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -2275,7 +2276,7 @@ func CreatePlaybook(ctx context.Context, name string) (*PlaybookType, error) {
|
||||
|
||||
func selectPlaybook(tx *TxWrap, playbookId string) *PlaybookType {
|
||||
query := `SELECT * FROM playbook where playbookid = ?`
|
||||
playbook := GetMapGen[*PlaybookType](tx, query, playbookId)
|
||||
playbook := dbutil.GetMapGen[*PlaybookType](tx, query, playbookId)
|
||||
return playbook
|
||||
}
|
||||
|
||||
@@ -2363,7 +2364,7 @@ func GetLineCmdsFromHistoryItems(ctx context.Context, historyItems []*HistoryIte
|
||||
query := `SELECT * FROM line WHERE lineid IN (SELECT value FROM json_each(?))`
|
||||
tx.Select(&lineArr, query, quickJsonArr(getLineIdsFromHistoryItems(historyItems)))
|
||||
query = `SELECT * FROM cmd WHERE cmdid IN (SELECT value FROM json_each(?))`
|
||||
cmdArr := SelectMapsGen[*CmdType](tx, query, quickJsonArr(getCmdIdsFromHistoryItems(historyItems)))
|
||||
cmdArr := dbutil.SelectMapsGen[*CmdType](tx, query, quickJsonArr(getCmdIdsFromHistoryItems(historyItems)))
|
||||
return lineArr, cmdArr, nil
|
||||
})
|
||||
}
|
||||
@@ -2371,7 +2372,7 @@ func GetLineCmdsFromHistoryItems(ctx context.Context, historyItems []*HistoryIte
|
||||
func PurgeHistoryByIds(ctx context.Context, historyIds []string) ([]*HistoryItemType, error) {
|
||||
return WithTxRtn(ctx, func(tx *TxWrap) ([]*HistoryItemType, error) {
|
||||
query := `SELECT * FROM history WHERE historyid IN (SELECT value FROM json_each(?))`
|
||||
rtn := SelectMapsGen[*HistoryItemType](tx, query, quickJsonArr(historyIds))
|
||||
rtn := dbutil.SelectMapsGen[*HistoryItemType](tx, query, quickJsonArr(historyIds))
|
||||
query = `DELETE FROM history WHERE historyid IN (SELECT value FROM json_each(?))`
|
||||
tx.Exec(query, quickJsonArr(historyIds))
|
||||
for _, hitem := range rtn {
|
||||
|
||||
@@ -2,81 +2,8 @@ package sstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type DBMappable interface {
|
||||
UseDBMap()
|
||||
}
|
||||
|
||||
type MapConverter interface {
|
||||
ToMap() map[string]interface{}
|
||||
FromMap(map[string]interface{}) bool
|
||||
}
|
||||
|
||||
type HasSimpleKey interface {
|
||||
GetSimpleKey() string
|
||||
}
|
||||
|
||||
type MapConverterPtr[T any] interface {
|
||||
MapConverter
|
||||
*T
|
||||
}
|
||||
|
||||
type DBMappablePtr[T any] interface {
|
||||
DBMappable
|
||||
*T
|
||||
}
|
||||
|
||||
func FromMap[PT MapConverterPtr[T], T any](m map[string]any) PT {
|
||||
if len(m) == 0 {
|
||||
return nil
|
||||
}
|
||||
rtn := PT(new(T))
|
||||
ok := rtn.FromMap(m)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return rtn
|
||||
}
|
||||
|
||||
func GetMapGen[PT MapConverterPtr[T], T any](tx *TxWrap, query string, args ...interface{}) PT {
|
||||
m := tx.GetMap(query, args...)
|
||||
return FromMap[PT](m)
|
||||
}
|
||||
|
||||
func GetMappable[PT DBMappablePtr[T], T any](tx *TxWrap, query string, args ...interface{}) PT {
|
||||
rtn := PT(new(T))
|
||||
m := tx.GetMap(query, args...)
|
||||
if len(m) == 0 {
|
||||
return nil
|
||||
}
|
||||
FromDBMap(rtn, m)
|
||||
return rtn
|
||||
}
|
||||
|
||||
func SelectMapsGen[PT MapConverterPtr[T], T any](tx *TxWrap, query string, args ...interface{}) []PT {
|
||||
var rtn []PT
|
||||
marr := tx.SelectMaps(query, args...)
|
||||
for _, m := range marr {
|
||||
val := FromMap[PT](m)
|
||||
if val != nil {
|
||||
rtn = append(rtn, val)
|
||||
}
|
||||
}
|
||||
return rtn
|
||||
}
|
||||
|
||||
func MakeGenMap[T HasSimpleKey](arr []T) map[string]T {
|
||||
rtn := make(map[string]T)
|
||||
for _, val := range arr {
|
||||
rtn[val.GetSimpleKey()] = val
|
||||
}
|
||||
return rtn
|
||||
}
|
||||
|
||||
func WithTxRtn[RT any](ctx context.Context, fn func(tx *TxWrap) (RT, error)) (RT, error) {
|
||||
var rtn RT
|
||||
txErr := WithTx(ctx, func(tx *TxWrap) error {
|
||||
@@ -104,99 +31,3 @@ func WithTxRtn3[RT1 any, RT2 any](ctx context.Context, fn func(tx *TxWrap) (RT1,
|
||||
})
|
||||
return rtn1, rtn2, txErr
|
||||
}
|
||||
|
||||
func isStructType(rt reflect.Type) bool {
|
||||
if rt.Kind() == reflect.Struct {
|
||||
return true
|
||||
}
|
||||
if rt.Kind() == reflect.Pointer && rt.Elem().Kind() == reflect.Struct {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isByteArrayType(t reflect.Type) bool {
|
||||
return t.Kind() == reflect.Slice && t.Elem().Kind() == reflect.Uint8
|
||||
}
|
||||
|
||||
func ToDBMap(v DBMappable) map[string]interface{} {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() == reflect.Pointer {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
if rv.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("invalid type %T (non-struct) passed to StructToDBMap", v))
|
||||
}
|
||||
rt := rv.Type()
|
||||
m := make(map[string]interface{})
|
||||
numFields := rt.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
field := rt.Field(i)
|
||||
fieldVal := rv.FieldByIndex(field.Index)
|
||||
dbName := field.Tag.Get("dbmap")
|
||||
if dbName == "" {
|
||||
dbName = strings.ToLower(field.Name)
|
||||
}
|
||||
if dbName == "-" {
|
||||
continue
|
||||
}
|
||||
if isByteArrayType(field.Type) {
|
||||
m[dbName] = fieldVal.Interface()
|
||||
} else if field.Type.Kind() == reflect.Slice {
|
||||
m[dbName] = quickJsonArr(fieldVal.Interface())
|
||||
} else if isStructType(field.Type) {
|
||||
m[dbName] = quickJson(fieldVal.Interface())
|
||||
} else {
|
||||
m[dbName] = fieldVal.Interface()
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func FromDBMap(v DBMappable, m map[string]interface{}) {
|
||||
if v == nil {
|
||||
panic("StructFromDBMap, v cannot be nil")
|
||||
}
|
||||
rv := reflect.ValueOf(v)
|
||||
if rv.Kind() == reflect.Pointer {
|
||||
rv = rv.Elem()
|
||||
}
|
||||
if rv.Kind() != reflect.Struct {
|
||||
panic(fmt.Sprintf("invalid type %T (non-struct) passed to StructFromDBMap", v))
|
||||
}
|
||||
rt := rv.Type()
|
||||
numFields := rt.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
field := rt.Field(i)
|
||||
fieldVal := rv.FieldByIndex(field.Index)
|
||||
dbName := field.Tag.Get("dbmap")
|
||||
if dbName == "" {
|
||||
dbName = strings.ToLower(field.Name)
|
||||
}
|
||||
if dbName == "-" {
|
||||
continue
|
||||
}
|
||||
if isByteArrayType(field.Type) {
|
||||
barrVal := fieldVal.Addr().Interface()
|
||||
quickSetBytes(barrVal.(*[]byte), m, dbName)
|
||||
} else if field.Type.Kind() == reflect.Slice {
|
||||
quickSetJsonArr(fieldVal.Addr().Interface(), m, dbName)
|
||||
} else if isStructType(field.Type) {
|
||||
quickSetJson(fieldVal.Addr().Interface(), m, dbName)
|
||||
} else if field.Type.Kind() == reflect.String {
|
||||
strVal := fieldVal.Addr().Interface()
|
||||
quickSetStr(strVal.(*string), m, dbName)
|
||||
} else if field.Type.Kind() == reflect.Int64 {
|
||||
intVal := fieldVal.Addr().Interface()
|
||||
quickSetInt64(intVal.(*int64), m, dbName)
|
||||
} else if field.Type.Kind() == reflect.Bool {
|
||||
boolVal := fieldVal.Addr().Interface()
|
||||
quickSetBool(boolVal.(*bool), m, dbName)
|
||||
} else {
|
||||
panic(fmt.Sprintf("StructFromDBMap invalid field type %v in %T", fieldVal.Type(), v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-156
@@ -1,161 +1,18 @@
|
||||
package sstore
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/dbutil"
|
||||
)
|
||||
|
||||
func quickSetStr(strVal *string, m map[string]interface{}, name string) {
|
||||
v, ok := m[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ival, ok := v.(int64)
|
||||
if ok {
|
||||
*strVal = strconv.FormatInt(ival, 10)
|
||||
return
|
||||
}
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
*strVal = str
|
||||
}
|
||||
|
||||
func quickSetInt64(ival *int64, m map[string]interface{}, name string) {
|
||||
v, ok := m[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
sqlInt, ok := v.(int64)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
*ival = sqlInt
|
||||
}
|
||||
|
||||
func quickSetBool(bval *bool, m map[string]interface{}, name string) {
|
||||
v, ok := m[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
sqlInt, ok := v.(int64)
|
||||
if ok {
|
||||
if sqlInt > 0 {
|
||||
*bval = true
|
||||
}
|
||||
return
|
||||
}
|
||||
sqlBool, ok := v.(bool)
|
||||
if ok {
|
||||
*bval = sqlBool
|
||||
}
|
||||
}
|
||||
|
||||
func quickSetBytes(bval *[]byte, m map[string]interface{}, name string) {
|
||||
v, ok := m[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
sqlBytes, ok := v.([]byte)
|
||||
if ok {
|
||||
*bval = sqlBytes
|
||||
}
|
||||
}
|
||||
|
||||
func quickSetJson(ptr interface{}, m map[string]interface{}, name string) {
|
||||
v, ok := m[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if str == "" {
|
||||
str = "{}"
|
||||
}
|
||||
json.Unmarshal([]byte(str), ptr)
|
||||
}
|
||||
|
||||
func quickSetNullableJson(ptr interface{}, m map[string]interface{}, name string) {
|
||||
v, ok := m[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if str == "" {
|
||||
str = "null"
|
||||
}
|
||||
json.Unmarshal([]byte(str), ptr)
|
||||
}
|
||||
|
||||
func quickSetJsonArr(ptr interface{}, m map[string]interface{}, name string) {
|
||||
v, ok := m[name]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
str, ok := v.(string)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if str == "" {
|
||||
str = "[]"
|
||||
}
|
||||
json.Unmarshal([]byte(str), ptr)
|
||||
}
|
||||
|
||||
func quickNullableJson(v interface{}) string {
|
||||
if v == nil {
|
||||
return "null"
|
||||
}
|
||||
barr, _ := json.Marshal(v)
|
||||
return string(barr)
|
||||
}
|
||||
|
||||
func quickJson(v interface{}) string {
|
||||
if v == nil {
|
||||
return "{}"
|
||||
}
|
||||
barr, _ := json.Marshal(v)
|
||||
return string(barr)
|
||||
}
|
||||
|
||||
func quickJsonArr(v interface{}) string {
|
||||
if v == nil {
|
||||
return "[]"
|
||||
}
|
||||
barr, _ := json.Marshal(v)
|
||||
return string(barr)
|
||||
}
|
||||
|
||||
func quickScanJson(ptr interface{}, val interface{}) error {
|
||||
barrVal, ok := val.([]byte)
|
||||
if !ok {
|
||||
strVal, ok := val.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot scan '%T' into '%T'", val, ptr)
|
||||
}
|
||||
barrVal = []byte(strVal)
|
||||
}
|
||||
if len(barrVal) == 0 {
|
||||
barrVal = []byte("{}")
|
||||
}
|
||||
return json.Unmarshal(barrVal, ptr)
|
||||
}
|
||||
|
||||
func quickValueJson(v interface{}) (driver.Value, error) {
|
||||
if v == nil {
|
||||
return "{}", nil
|
||||
}
|
||||
barr, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return string(barr), nil
|
||||
}
|
||||
var quickSetStr = dbutil.QuickSetStr
|
||||
var quickSetInt64 = dbutil.QuickSetInt64
|
||||
var quickSetBool = dbutil.QuickSetBool
|
||||
var quickSetBytes = dbutil.QuickSetBytes
|
||||
var quickSetJson = dbutil.QuickSetJson
|
||||
var quickSetNullableJson = dbutil.QuickSetNullableJson
|
||||
var quickSetJsonArr = dbutil.QuickSetJsonArr
|
||||
var quickNullableJson = dbutil.QuickNullableJson
|
||||
var quickJson = dbutil.QuickJson
|
||||
var quickJsonArr = dbutil.QuickJsonArr
|
||||
var quickScanJson = dbutil.QuickScanJson
|
||||
var quickValueJson = dbutil.QuickValueJson
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"github.com/sawka/txwrap"
|
||||
"github.com/scripthaus-dev/mshell/pkg/base"
|
||||
"github.com/scripthaus-dev/mshell/pkg/packet"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/dbutil"
|
||||
"github.com/scripthaus-dev/sh2-server/pkg/scbase"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
@@ -1095,7 +1096,7 @@ func createClientData(tx *TxWrap) error {
|
||||
}
|
||||
query := `INSERT INTO client ( clientid, userid, activesessionid, userpublickeybytes, userprivatekeybytes, winsize)
|
||||
VALUES (:clientid,:userid,:activesessionid,:userpublickeybytes,:userprivatekeybytes,:winsize)`
|
||||
tx.NamedExec(query, ToDBMap(c))
|
||||
tx.NamedExec(query, dbutil.ToDBMap(c, false))
|
||||
log.Printf("create new clientid[%s] userid[%s] with public/private keypair\n", c.ClientId, c.UserId)
|
||||
return nil
|
||||
}
|
||||
@@ -1113,7 +1114,7 @@ func EnsureClientData(ctx context.Context) (*ClientData, error) {
|
||||
return nil, createErr
|
||||
}
|
||||
}
|
||||
cdata := GetMappable[*ClientData](tx, `SELECT * FROM client`)
|
||||
cdata := dbutil.GetMappable[*ClientData](tx, `SELECT * FROM client`)
|
||||
if cdata == nil {
|
||||
return nil, fmt.Errorf("no client data found")
|
||||
}
|
||||
@@ -1148,7 +1149,7 @@ func EnsureClientData(ctx context.Context) (*ClientData, error) {
|
||||
|
||||
func GetCmdMigrationInfo(ctx context.Context) (*ClientMigrationData, error) {
|
||||
return WithTxRtn(ctx, func(tx *TxWrap) (*ClientMigrationData, error) {
|
||||
cdata := GetMappable[*ClientData](tx, `SELECT * FROM client`)
|
||||
cdata := dbutil.GetMappable[*ClientData](tx, `SELECT * FROM client`)
|
||||
if cdata == nil {
|
||||
return nil, fmt.Errorf("no client data found")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user