Fixed linting issues

This commit is contained in:
Hein 2025-11-11 11:32:30 +02:00
parent 7b8216b71c
commit ecd7b31910
14 changed files with 104 additions and 65 deletions

View File

@ -1,5 +1,7 @@
# 📜 ResolveSpec 📜
![Tests](https://github.com/bitechdev/ResolveSpec/workflows/Tests/badge.svg)
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

View File

@ -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
}

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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"
}

View File

@ -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
}

View File

@ -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

View File

@ -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")
}

View File

@ -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)
}
}

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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)
}
}