mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-11-13 09:53:53 +00:00
Fixed linting issues
This commit is contained in:
parent
7b8216b71c
commit
ecd7b31910
@ -1,5 +1,7 @@
|
||||
# 📜 ResolveSpec 📜
|
||||
|
||||

|
||||
|
||||
ResolveSpec is a flexible and powerful REST API specification and implementation that provides GraphQL-like capabilities while maintaining REST simplicity. It offers **two complementary approaches**:
|
||||
|
||||
1. **ResolveSpec** - Body-based API with JSON request options
|
||||
|
||||
@ -100,6 +100,10 @@ func (b *BunSelectQuery) Model(model interface{}) common.SelectQuery {
|
||||
b.schema, b.tableName = parseTableName(fullTableName)
|
||||
}
|
||||
|
||||
if provider, ok := model.(common.TableAliasProvider); ok {
|
||||
b.tableAlias = provider.TableAlias()
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
|
||||
@ -86,6 +86,10 @@ func (g *GormSelectQuery) Model(model interface{}) common.SelectQuery {
|
||||
g.schema, g.tableName = parseTableName(fullTableName)
|
||||
}
|
||||
|
||||
if provider, ok := model.(common.TableAliasProvider); ok {
|
||||
g.tableAlias = provider.TableAlias()
|
||||
}
|
||||
|
||||
return g
|
||||
}
|
||||
|
||||
@ -267,11 +271,12 @@ func (g *GormInsertQuery) Returning(columns ...string) common.InsertQuery {
|
||||
|
||||
func (g *GormInsertQuery) Exec(ctx context.Context) (common.Result, error) {
|
||||
var result *gorm.DB
|
||||
if g.model != nil {
|
||||
switch {
|
||||
case g.model != nil:
|
||||
result = g.db.WithContext(ctx).Create(g.model)
|
||||
} else if g.values != nil {
|
||||
case g.values != nil:
|
||||
result = g.db.WithContext(ctx).Create(g.values)
|
||||
} else {
|
||||
default:
|
||||
result = g.db.WithContext(ctx).Create(map[string]interface{}{})
|
||||
}
|
||||
return &GormResult{result: result}, result.Error
|
||||
|
||||
@ -130,7 +130,7 @@ func (h *HTTPRequest) AllHeaders() map[string]string {
|
||||
// HTTPResponseWriter adapts our ResponseWriter interface to standard http.ResponseWriter
|
||||
type HTTPResponseWriter struct {
|
||||
resp http.ResponseWriter
|
||||
w common.ResponseWriter
|
||||
w common.ResponseWriter //nolint:unused
|
||||
status int
|
||||
}
|
||||
|
||||
|
||||
@ -131,6 +131,10 @@ type TableNameProvider interface {
|
||||
TableName() string
|
||||
}
|
||||
|
||||
type TableAliasProvider interface {
|
||||
TableAlias() string
|
||||
}
|
||||
|
||||
// PrimaryKeyNameProvider interface for models that provide primary key column names
|
||||
type PrimaryKeyNameProvider interface {
|
||||
GetIDName() string
|
||||
|
||||
@ -49,12 +49,13 @@ func GetModelColumnDetail(record reflect.Value) []ModelFieldDetail {
|
||||
fielddetail.DataType = fieldtype.Type.Name()
|
||||
fielddetail.SQLName = fnFindKeyVal(gormdetail, "column:")
|
||||
fielddetail.SQLDataType = fnFindKeyVal(gormdetail, "type:")
|
||||
if strings.Index(strings.ToLower(gormdetail), "identity") > 0 ||
|
||||
strings.Index(strings.ToLower(gormdetail), "primary_key") > 0 {
|
||||
gormdetailLower := strings.ToLower(gormdetail)
|
||||
switch {
|
||||
case strings.Index(gormdetailLower, "identity") > 0 || strings.Index(gormdetailLower, "primary_key") > 0:
|
||||
fielddetail.SQLKey = "primary_key"
|
||||
} else if strings.Contains(strings.ToLower(gormdetail), "unique") {
|
||||
case strings.Contains(gormdetailLower, "unique"):
|
||||
fielddetail.SQLKey = "unique"
|
||||
} else if strings.Contains(strings.ToLower(gormdetail), "uniqueindex") {
|
||||
case strings.Contains(gormdetailLower, "uniqueindex"):
|
||||
fielddetail.SQLKey = "uniqueindex"
|
||||
}
|
||||
|
||||
|
||||
@ -481,11 +481,12 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, url
|
||||
case map[string]interface{}:
|
||||
// Determine the ID to use
|
||||
var targetID interface{}
|
||||
if urlID != "" {
|
||||
switch {
|
||||
case urlID != "":
|
||||
targetID = urlID
|
||||
} else if reqID != nil {
|
||||
case reqID != nil:
|
||||
targetID = reqID
|
||||
} else if updates["id"] != nil {
|
||||
case updates["id"] != nil:
|
||||
targetID = updates["id"]
|
||||
}
|
||||
|
||||
@ -723,11 +724,12 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
||||
var itemID interface{}
|
||||
|
||||
// Check if item is a string ID or object with id field
|
||||
if idStr, ok := item.(string); ok {
|
||||
itemID = idStr
|
||||
} else if itemMap, ok := item.(map[string]interface{}); ok {
|
||||
itemID = itemMap["id"]
|
||||
} else {
|
||||
switch v := item.(type) {
|
||||
case string:
|
||||
itemID = v
|
||||
case map[string]interface{}:
|
||||
itemID = v["id"]
|
||||
default:
|
||||
// Try to use the item directly as ID
|
||||
itemID = item
|
||||
}
|
||||
|
||||
@ -781,11 +781,12 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id
|
||||
query := h.db.NewUpdate().Table(tableName).SetMap(dataMap)
|
||||
|
||||
// Apply ID filter
|
||||
if id != "" {
|
||||
switch {
|
||||
case id != "":
|
||||
query = query.Where("id = ?", id)
|
||||
} else if idPtr != nil {
|
||||
case idPtr != nil:
|
||||
query = query.Where("id = ?", *idPtr)
|
||||
} else {
|
||||
default:
|
||||
h.sendError(w, http.StatusBadRequest, "missing_id", "ID is required for update", nil)
|
||||
return
|
||||
}
|
||||
@ -902,11 +903,12 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
||||
var itemID interface{}
|
||||
|
||||
// Check if item is a string ID or object with id field
|
||||
if idStr, ok := item.(string); ok {
|
||||
itemID = idStr
|
||||
} else if itemMap, ok := item.(map[string]interface{}); ok {
|
||||
itemID = itemMap["id"]
|
||||
} else {
|
||||
switch v := item.(type) {
|
||||
case string:
|
||||
itemID = v
|
||||
case map[string]interface{}:
|
||||
itemID = v["id"]
|
||||
default:
|
||||
itemID = item
|
||||
}
|
||||
|
||||
@ -1330,7 +1332,9 @@ func (h *Handler) sendResponse(w common.ResponseWriter, data interface{}, metada
|
||||
Metadata: metadata,
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.WriteJSON(response)
|
||||
if err := w.WriteJSON(response); err != nil {
|
||||
logger.Error("Failed to write JSON response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// sendFormattedResponse sends response with formatting options
|
||||
@ -1350,7 +1354,9 @@ func (h *Handler) sendFormattedResponse(w common.ResponseWriter, data interface{
|
||||
case "simple":
|
||||
// Simple format: just return the data array
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.WriteJSON(data)
|
||||
if err := w.WriteJSON(data); err != nil {
|
||||
logger.Error("Failed to write JSON response: %v", err)
|
||||
}
|
||||
case "syncfusion":
|
||||
// Syncfusion format: { result: data, count: total }
|
||||
response := map[string]interface{}{
|
||||
@ -1360,7 +1366,9 @@ func (h *Handler) sendFormattedResponse(w common.ResponseWriter, data interface{
|
||||
response["count"] = metadata.Total
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.WriteJSON(response)
|
||||
if err := w.WriteJSON(response); err != nil {
|
||||
logger.Error("Failed to write JSON response: %v", err)
|
||||
}
|
||||
default:
|
||||
// Default/detail format: standard response with metadata
|
||||
response := common.Response{
|
||||
@ -1369,7 +1377,9 @@ func (h *Handler) sendFormattedResponse(w common.ResponseWriter, data interface{
|
||||
Metadata: metadata,
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.WriteJSON(response)
|
||||
if err := w.WriteJSON(response); err != nil {
|
||||
logger.Error("Failed to write JSON response: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1397,7 +1407,9 @@ func (h *Handler) sendError(w common.ResponseWriter, statusCode int, code, messa
|
||||
},
|
||||
}
|
||||
w.WriteHeader(statusCode)
|
||||
w.WriteJSON(response)
|
||||
if err := w.WriteJSON(response); err != nil {
|
||||
logger.Error("Failed to write JSON error response: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// FetchRowNumber calculates the row number of a specific record based on sorting and filtering
|
||||
|
||||
@ -416,16 +416,17 @@ func (h *Handler) parseSorting(options *ExtendedRequestOptions, value string) {
|
||||
direction := "ASC"
|
||||
colName := field
|
||||
|
||||
if strings.HasPrefix(field, "-") {
|
||||
switch {
|
||||
case strings.HasPrefix(field, "-"):
|
||||
direction = "DESC"
|
||||
colName = strings.TrimPrefix(field, "-")
|
||||
} else if strings.HasPrefix(field, "+") {
|
||||
case strings.HasPrefix(field, "+"):
|
||||
direction = "ASC"
|
||||
colName = strings.TrimPrefix(field, "+")
|
||||
} else if strings.HasSuffix(field, " desc") {
|
||||
case strings.HasSuffix(field, " desc"):
|
||||
direction = "DESC"
|
||||
colName = strings.TrimSuffix(field, "desc")
|
||||
} else if strings.HasSuffix(field, " asc") {
|
||||
case strings.HasSuffix(field, " asc"):
|
||||
direction = "ASC"
|
||||
colName = strings.TrimSuffix(field, "asc")
|
||||
}
|
||||
|
||||
@ -62,6 +62,7 @@ import (
|
||||
|
||||
"github.com/bitechdev/ResolveSpec/pkg/common/adapters/database"
|
||||
"github.com/bitechdev/ResolveSpec/pkg/common/adapters/router"
|
||||
"github.com/bitechdev/ResolveSpec/pkg/logger"
|
||||
"github.com/bitechdev/ResolveSpec/pkg/modelregistry"
|
||||
)
|
||||
|
||||
@ -252,5 +253,7 @@ func ExampleBunRouterWithBunDB(bunDB *bun.DB) {
|
||||
r := routerAdapter.GetBunRouter()
|
||||
|
||||
// Start server
|
||||
http.ListenAndServe(":8080", r)
|
||||
if err := http.ListenAndServe(":8080", r); err != nil {
|
||||
logger.Error("Server failed to start: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
// DBM "github.com/bitechdev/GoCore/pkg/models"
|
||||
)
|
||||
|
||||
// This file provides example implementations of the required security callbacks.
|
||||
|
||||
@ -27,9 +27,7 @@ func RegisterSecurityHooks(handler *restheadspec.Handler, securityList *Security
|
||||
})
|
||||
|
||||
// Hook 4 (Optional): Audit logging
|
||||
handler.Hooks().Register(restheadspec.AfterRead, func(hookCtx *restheadspec.HookContext) error {
|
||||
return logDataAccess(hookCtx)
|
||||
})
|
||||
handler.Hooks().Register(restheadspec.AfterRead, logDataAccess)
|
||||
}
|
||||
|
||||
// loadSecurityRules loads security configuration for the user and entity
|
||||
@ -162,7 +160,7 @@ func applyColumnSecurity(hookCtx *restheadspec.HookContext, securityList *Securi
|
||||
resultValue = resultValue.Elem()
|
||||
}
|
||||
|
||||
err, maskedResult := securityList.ApplyColumnSecurity(resultValue, modelType, userID, schema, tablename)
|
||||
maskedResult, err := securityList.ApplyColumnSecurity(resultValue, modelType, userID, schema, tablename)
|
||||
if err != nil {
|
||||
logger.Warn("Column security error: %v", err)
|
||||
// Don't fail the request, just log the issue
|
||||
|
||||
@ -5,11 +5,14 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// contextKey is a custom type for context keys to avoid collisions
|
||||
type contextKey string
|
||||
|
||||
const (
|
||||
// Context keys for user information
|
||||
UserIDKey = "user_id"
|
||||
UserRolesKey = "user_roles"
|
||||
UserTokenKey = "user_token"
|
||||
UserIDKey contextKey = "user_id"
|
||||
UserRolesKey contextKey = "user_roles"
|
||||
UserTokenKey contextKey = "user_token"
|
||||
)
|
||||
|
||||
// AuthMiddleware extracts user authentication from request and adds to context
|
||||
|
||||
@ -73,8 +73,9 @@ type SecurityList struct {
|
||||
LoadColumnSecurityCallback LoadColumnSecurityFunc
|
||||
LoadRowSecurityCallback LoadRowSecurityFunc
|
||||
}
|
||||
type CONTEXT_KEY string
|
||||
|
||||
const SECURITY_CONTEXT_KEY = "SecurityList"
|
||||
const SECURITY_CONTEXT_KEY CONTEXT_KEY = "SecurityList"
|
||||
|
||||
var GlobalSecurity SecurityList
|
||||
|
||||
@ -105,22 +106,22 @@ func maskString(pString string, maskStart, maskEnd int, maskChar string, invert
|
||||
}
|
||||
for index, char := range pString {
|
||||
if invert && index >= middleIndex-maskStart && index <= middleIndex {
|
||||
newStr = newStr + maskChar
|
||||
newStr += maskChar
|
||||
continue
|
||||
}
|
||||
if invert && index <= middleIndex+maskEnd && index >= middleIndex {
|
||||
newStr = newStr + maskChar
|
||||
newStr += maskChar
|
||||
continue
|
||||
}
|
||||
if !invert && index <= maskStart {
|
||||
newStr = newStr + maskChar
|
||||
newStr += maskChar
|
||||
continue
|
||||
}
|
||||
if !invert && index >= strLen-1-maskEnd {
|
||||
newStr = newStr + maskChar
|
||||
newStr += maskChar
|
||||
continue
|
||||
}
|
||||
newStr = newStr + string(char)
|
||||
newStr += string(char)
|
||||
}
|
||||
|
||||
return newStr
|
||||
@ -145,7 +146,8 @@ func (m *SecurityList) ColumSecurityApplyOnRecord(prevRecord reflect.Value, newR
|
||||
return cols, fmt.Errorf("no security data")
|
||||
}
|
||||
|
||||
for _, colsec := range colsecList {
|
||||
for i := range colsecList {
|
||||
colsec := &colsecList[i]
|
||||
if !strings.EqualFold(colsec.Accesstype, "mask") && !strings.EqualFold(colsec.Accesstype, "hide") {
|
||||
continue
|
||||
}
|
||||
@ -262,24 +264,25 @@ func setColSecValue(fieldsrc reflect.Value, colsec ColumnSecurity, fieldTypeName
|
||||
fieldval = fieldval.Elem()
|
||||
}
|
||||
|
||||
if strings.Contains(strings.ToLower(fieldval.Kind().String()), "int") &&
|
||||
(strings.EqualFold(colsec.Accesstype, "mask") || strings.EqualFold(colsec.Accesstype, "hide")) {
|
||||
fieldKindLower := strings.ToLower(fieldval.Kind().String())
|
||||
switch {
|
||||
case strings.Contains(fieldKindLower, "int") &&
|
||||
(strings.EqualFold(colsec.Accesstype, "mask") || strings.EqualFold(colsec.Accesstype, "hide")):
|
||||
if fieldval.CanInt() && fieldval.CanSet() {
|
||||
fieldval.SetInt(0)
|
||||
}
|
||||
} else if (strings.Contains(strings.ToLower(fieldval.Kind().String()), "time") ||
|
||||
strings.Contains(strings.ToLower(fieldval.Kind().String()), "date")) &&
|
||||
(strings.EqualFold(colsec.Accesstype, "mask") || strings.EqualFold(colsec.Accesstype, "hide")) {
|
||||
case (strings.Contains(fieldKindLower, "time") || strings.Contains(fieldKindLower, "date")) &&
|
||||
(strings.EqualFold(colsec.Accesstype, "mask") || strings.EqualFold(colsec.Accesstype, "hide")):
|
||||
fieldval.SetZero()
|
||||
} else if strings.Contains(strings.ToLower(fieldval.Kind().String()), "string") {
|
||||
case strings.Contains(fieldKindLower, "string"):
|
||||
strVal := fieldval.String()
|
||||
if strings.EqualFold(colsec.Accesstype, "mask") {
|
||||
fieldval.SetString(maskString(strVal, colsec.MaskStart, colsec.MaskEnd, colsec.MaskChar, colsec.MaskInvert))
|
||||
} else if strings.EqualFold(colsec.Accesstype, "hide") {
|
||||
fieldval.SetString("")
|
||||
}
|
||||
} else if strings.Contains(fieldTypeName, "json") &&
|
||||
(strings.EqualFold(colsec.Accesstype, "mask") || strings.EqualFold(colsec.Accesstype, "hide")) {
|
||||
case strings.Contains(fieldTypeName, "json") &&
|
||||
(strings.EqualFold(colsec.Accesstype, "mask") || strings.EqualFold(colsec.Accesstype, "hide")):
|
||||
if len(colsec.Path) < 2 {
|
||||
return 1, fieldval
|
||||
}
|
||||
@ -300,11 +303,11 @@ func setColSecValue(fieldsrc reflect.Value, colsec ColumnSecurity, fieldTypeName
|
||||
return 0, fieldsrc
|
||||
}
|
||||
|
||||
func (m *SecurityList) ApplyColumnSecurity(records reflect.Value, modelType reflect.Type, pUserID int, pSchema, pTablename string) (error, reflect.Value) {
|
||||
func (m *SecurityList) ApplyColumnSecurity(records reflect.Value, modelType reflect.Type, pUserID int, pSchema, pTablename string) (reflect.Value, error) {
|
||||
defer logger.CatchPanic("ApplyColumnSecurity")
|
||||
|
||||
if m.ColumnSecurity == nil {
|
||||
return fmt.Errorf("security not initialized"), records
|
||||
return records, fmt.Errorf("security not initialized")
|
||||
}
|
||||
|
||||
m.ColumnSecurityMutex.RLock()
|
||||
@ -312,10 +315,11 @@ func (m *SecurityList) ApplyColumnSecurity(records reflect.Value, modelType refl
|
||||
|
||||
colsecList, ok := m.ColumnSecurity[fmt.Sprintf("%s.%s@%d", pSchema, pTablename, pUserID)]
|
||||
if !ok || colsecList == nil {
|
||||
return fmt.Errorf("no security data"), records
|
||||
return records, fmt.Errorf("no security data")
|
||||
}
|
||||
|
||||
for _, colsec := range colsecList {
|
||||
for i := range colsecList {
|
||||
colsec := &colsecList[i]
|
||||
if !strings.EqualFold(colsec.Accesstype, "mask") && !strings.EqualFold(colsec.Accesstype, "hide") {
|
||||
continue
|
||||
}
|
||||
@ -353,7 +357,7 @@ func (m *SecurityList) ApplyColumnSecurity(records reflect.Value, modelType refl
|
||||
|
||||
if i == pathLen-1 {
|
||||
if nameType == "sql" || nameType == "struct" {
|
||||
setColSecValue(field, colsec, fieldName)
|
||||
setColSecValue(field, *colsec, fieldName)
|
||||
}
|
||||
break
|
||||
}
|
||||
@ -365,7 +369,7 @@ func (m *SecurityList) ApplyColumnSecurity(records reflect.Value, modelType refl
|
||||
}
|
||||
}
|
||||
|
||||
return nil, records
|
||||
return records, nil
|
||||
}
|
||||
|
||||
func (m *SecurityList) LoadColumnSecurity(pUserID int, pSchema, pTablename string, pOverwrite bool) error {
|
||||
@ -407,9 +411,10 @@ func (m *SecurityList) ClearSecurity(pUserID int, pSchema, pTablename string) er
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, cs := range list {
|
||||
for i := range list {
|
||||
cs := &list[i]
|
||||
if cs.Schema != pSchema && cs.Tablename != pTablename && cs.UserID != pUserID {
|
||||
filtered = append(filtered, cs)
|
||||
filtered = append(filtered, *cs)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user