mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-11-13 18:03:53 +00:00
Better handling with context
This commit is contained in:
parent
d122c7af42
commit
c88bff1883
@ -15,12 +15,12 @@ if [[ $make_release =~ ^[Yy]$ ]]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Create an annotated tag
|
# Create an annotated tag
|
||||||
git tag -a "$version" -m "Released Core $version"
|
git tag -a "$version" -m "Released $version"
|
||||||
|
|
||||||
# Push the tag to the remote repository
|
# Push the tag to the remote repository
|
||||||
git push origin "$version"
|
git push origin "$version"
|
||||||
|
|
||||||
echo "Tag $version created for Core and pushed to the remote repository."
|
echo "Tag $version created and pushed to the remote repository."
|
||||||
else
|
else
|
||||||
echo "No release version created."
|
echo "No release version created."
|
||||||
fi
|
fi
|
||||||
|
|||||||
85
pkg/resolvespec/context.go
Normal file
85
pkg/resolvespec/context.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package resolvespec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Context keys for request-scoped data
|
||||||
|
type contextKey string
|
||||||
|
|
||||||
|
const (
|
||||||
|
contextKeySchema contextKey = "schema"
|
||||||
|
contextKeyEntity contextKey = "entity"
|
||||||
|
contextKeyTableName contextKey = "tableName"
|
||||||
|
contextKeyModel contextKey = "model"
|
||||||
|
contextKeyModelPtr contextKey = "modelPtr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithSchema adds schema to context
|
||||||
|
func WithSchema(ctx context.Context, schema string) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKeySchema, schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSchema retrieves schema from context
|
||||||
|
func GetSchema(ctx context.Context) string {
|
||||||
|
if v := ctx.Value(contextKeySchema); v != nil {
|
||||||
|
return v.(string)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEntity adds entity to context
|
||||||
|
func WithEntity(ctx context.Context, entity string) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKeyEntity, entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntity retrieves entity from context
|
||||||
|
func GetEntity(ctx context.Context) string {
|
||||||
|
if v := ctx.Value(contextKeyEntity); v != nil {
|
||||||
|
return v.(string)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTableName adds table name to context
|
||||||
|
func WithTableName(ctx context.Context, tableName string) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKeyTableName, tableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTableName retrieves table name from context
|
||||||
|
func GetTableName(ctx context.Context) string {
|
||||||
|
if v := ctx.Value(contextKeyTableName); v != nil {
|
||||||
|
return v.(string)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithModel adds model to context
|
||||||
|
func WithModel(ctx context.Context, model interface{}) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKeyModel, model)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetModel retrieves model from context
|
||||||
|
func GetModel(ctx context.Context) interface{} {
|
||||||
|
return ctx.Value(contextKeyModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithModelPtr adds model pointer to context
|
||||||
|
func WithModelPtr(ctx context.Context, modelPtr interface{}) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKeyModelPtr, modelPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetModelPtr retrieves model pointer from context
|
||||||
|
func GetModelPtr(ctx context.Context) interface{} {
|
||||||
|
return ctx.Value(contextKeyModelPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRequestData adds all request-scoped data to context at once
|
||||||
|
func WithRequestData(ctx context.Context, schema, entity, tableName string, model, modelPtr interface{}) context.Context {
|
||||||
|
ctx = WithSchema(ctx, schema)
|
||||||
|
ctx = WithEntity(ctx, entity)
|
||||||
|
ctx = WithTableName(ctx, tableName)
|
||||||
|
ctx = WithModel(ctx, model)
|
||||||
|
ctx = WithModelPtr(ctx, modelPtr)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
@ -50,15 +50,30 @@ func (h *Handler) Handle(w common.ResponseWriter, r common.Request, params map[s
|
|||||||
|
|
||||||
logger.Info("Handling %s operation for %s.%s", req.Operation, schema, entity)
|
logger.Info("Handling %s operation for %s.%s", req.Operation, schema, entity)
|
||||||
|
|
||||||
|
// Get model and populate context with request-scoped data
|
||||||
|
model, err := h.registry.GetModelByEntity(schema, entity)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Invalid entity: %v", err)
|
||||||
|
h.sendError(w, http.StatusBadRequest, "invalid_entity", "Invalid entity", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a pointer to the model type for database operations
|
||||||
|
modelPtr := reflect.New(reflect.TypeOf(model)).Interface()
|
||||||
|
tableName := h.getTableName(schema, entity, model)
|
||||||
|
|
||||||
|
// Add request-scoped data to context
|
||||||
|
ctx = WithRequestData(ctx, schema, entity, tableName, model, modelPtr)
|
||||||
|
|
||||||
switch req.Operation {
|
switch req.Operation {
|
||||||
case "read":
|
case "read":
|
||||||
h.handleRead(ctx, w, schema, entity, id, req.Options)
|
h.handleRead(ctx, w, id, req.Options)
|
||||||
case "create":
|
case "create":
|
||||||
h.handleCreate(ctx, w, schema, entity, req.Data, req.Options)
|
h.handleCreate(ctx, w, req.Data, req.Options)
|
||||||
case "update":
|
case "update":
|
||||||
h.handleUpdate(ctx, w, schema, entity, id, req.ID, req.Data, req.Options)
|
h.handleUpdate(ctx, w, id, req.ID, req.Data, req.Options)
|
||||||
case "delete":
|
case "delete":
|
||||||
h.handleDelete(ctx, w, schema, entity, id)
|
h.handleDelete(ctx, w, id)
|
||||||
default:
|
default:
|
||||||
logger.Error("Invalid operation: %s", req.Operation)
|
logger.Error("Invalid operation: %s", req.Operation)
|
||||||
h.sendError(w, http.StatusBadRequest, "invalid_operation", "Invalid operation", nil)
|
h.sendError(w, http.StatusBadRequest, "invalid_operation", "Invalid operation", nil)
|
||||||
@ -83,24 +98,16 @@ func (h *Handler) HandleGet(w common.ResponseWriter, r common.Request, params ma
|
|||||||
h.sendResponse(w, metadata, nil)
|
h.sendResponse(w, metadata, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, schema, entity, id string, options common.RequestOptions) {
|
func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id string, options common.RequestOptions) {
|
||||||
|
schema := GetSchema(ctx)
|
||||||
|
entity := GetEntity(ctx)
|
||||||
|
tableName := GetTableName(ctx)
|
||||||
|
model := GetModel(ctx)
|
||||||
|
modelPtr := GetModelPtr(ctx)
|
||||||
|
|
||||||
logger.Info("Reading records from %s.%s", schema, entity)
|
logger.Info("Reading records from %s.%s", schema, entity)
|
||||||
|
|
||||||
model, err := h.registry.GetModelByEntity(schema, entity)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Invalid entity: %v", err)
|
|
||||||
h.sendError(w, http.StatusBadRequest, "invalid_entity", "Invalid entity", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Model is now a non-pointer struct, create a pointer instance for ORM
|
|
||||||
modelType := reflect.TypeOf(model)
|
|
||||||
modelPtr := reflect.New(modelType).Interface()
|
|
||||||
|
|
||||||
query := h.db.NewSelect().Model(modelPtr)
|
query := h.db.NewSelect().Model(modelPtr)
|
||||||
|
|
||||||
// Get table name
|
|
||||||
tableName := h.getTableName(schema, entity, model)
|
|
||||||
query = query.Table(tableName)
|
query = query.Table(tableName)
|
||||||
|
|
||||||
// Apply column selection
|
// Apply column selection
|
||||||
@ -154,7 +161,7 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, schem
|
|||||||
if id != "" {
|
if id != "" {
|
||||||
logger.Debug("Querying single record with ID: %s", id)
|
logger.Debug("Querying single record with ID: %s", id)
|
||||||
// Create a pointer to the struct type for scanning
|
// Create a pointer to the struct type for scanning
|
||||||
singleResult := reflect.New(modelType).Interface()
|
singleResult := reflect.New(reflect.TypeOf(model)).Interface()
|
||||||
query = query.Where("id = ?", id)
|
query = query.Where("id = ?", id)
|
||||||
if err := query.Scan(ctx, singleResult); err != nil {
|
if err := query.Scan(ctx, singleResult); err != nil {
|
||||||
logger.Error("Error querying record: %v", err)
|
logger.Error("Error querying record: %v", err)
|
||||||
@ -164,8 +171,8 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, schem
|
|||||||
result = singleResult
|
result = singleResult
|
||||||
} else {
|
} else {
|
||||||
logger.Debug("Querying multiple records")
|
logger.Debug("Querying multiple records")
|
||||||
// Create a slice of the struct type (not pointers)
|
// Create a slice of pointers to the model type
|
||||||
sliceType := reflect.SliceOf(modelType)
|
sliceType := reflect.SliceOf(reflect.PointerTo(reflect.TypeOf(model)))
|
||||||
results := reflect.New(sliceType).Interface()
|
results := reflect.New(sliceType).Interface()
|
||||||
|
|
||||||
if err := query.Scan(ctx, results); err != nil {
|
if err := query.Scan(ctx, results); err != nil {
|
||||||
@ -195,17 +202,13 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, schem
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, schema, entity string, data interface{}, options common.RequestOptions) {
|
func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, data interface{}, options common.RequestOptions) {
|
||||||
|
schema := GetSchema(ctx)
|
||||||
|
entity := GetEntity(ctx)
|
||||||
|
tableName := GetTableName(ctx)
|
||||||
|
|
||||||
logger.Info("Creating records for %s.%s", schema, entity)
|
logger.Info("Creating records for %s.%s", schema, entity)
|
||||||
|
|
||||||
// Get the model to determine the actual table name
|
|
||||||
model, err := h.registry.GetModelByEntity(schema, entity)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn("Model not found, using default table name")
|
|
||||||
model = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tableName := h.getTableName(schema, entity, model)
|
|
||||||
query := h.db.NewInsert().Table(tableName)
|
query := h.db.NewInsert().Table(tableName)
|
||||||
|
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
@ -275,18 +278,13 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, sch
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, schema, entity, urlID string, reqID interface{}, data interface{}, options common.RequestOptions) {
|
func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, urlID string, reqID interface{}, data interface{}, options common.RequestOptions) {
|
||||||
|
schema := GetSchema(ctx)
|
||||||
|
entity := GetEntity(ctx)
|
||||||
|
tableName := GetTableName(ctx)
|
||||||
|
|
||||||
logger.Info("Updating records for %s.%s", schema, entity)
|
logger.Info("Updating records for %s.%s", schema, entity)
|
||||||
|
|
||||||
// Get the model to determine the actual table name
|
|
||||||
model, err := h.registry.GetModelByEntity(schema, entity)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn("Model not found, using default table name")
|
|
||||||
// Fallback to entity name (without schema for SQLite compatibility)
|
|
||||||
model = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tableName := h.getTableName(schema, entity, model)
|
|
||||||
query := h.db.NewUpdate().Table(tableName)
|
query := h.db.NewUpdate().Table(tableName)
|
||||||
|
|
||||||
switch updates := data.(type) {
|
switch updates := data.(type) {
|
||||||
@ -330,7 +328,11 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, sch
|
|||||||
h.sendResponse(w, data, nil)
|
h.sendResponse(w, data, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, schema, entity, id string) {
|
func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id string) {
|
||||||
|
schema := GetSchema(ctx)
|
||||||
|
entity := GetEntity(ctx)
|
||||||
|
tableName := GetTableName(ctx)
|
||||||
|
|
||||||
logger.Info("Deleting records from %s.%s", schema, entity)
|
logger.Info("Deleting records from %s.%s", schema, entity)
|
||||||
|
|
||||||
if id == "" {
|
if id == "" {
|
||||||
@ -339,14 +341,6 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, sch
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the model to determine the actual table name
|
|
||||||
model, err := h.registry.GetModelByEntity(schema, entity)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warn("Model not found, using default table name")
|
|
||||||
model = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tableName := h.getTableName(schema, entity, model)
|
|
||||||
query := h.db.NewDelete().Table(tableName).Where("id = ?", id)
|
query := h.db.NewDelete().Table(tableName).Where("id = ?", id)
|
||||||
|
|
||||||
result, err := query.Exec(ctx)
|
result, err := query.Exec(ctx)
|
||||||
|
|||||||
85
pkg/restheadspec/context.go
Normal file
85
pkg/restheadspec/context.go
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
package restheadspec
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Context keys for request-scoped data
|
||||||
|
type contextKey string
|
||||||
|
|
||||||
|
const (
|
||||||
|
contextKeySchema contextKey = "schema"
|
||||||
|
contextKeyEntity contextKey = "entity"
|
||||||
|
contextKeyTableName contextKey = "tableName"
|
||||||
|
contextKeyModel contextKey = "model"
|
||||||
|
contextKeyModelPtr contextKey = "modelPtr"
|
||||||
|
)
|
||||||
|
|
||||||
|
// WithSchema adds schema to context
|
||||||
|
func WithSchema(ctx context.Context, schema string) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKeySchema, schema)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSchema retrieves schema from context
|
||||||
|
func GetSchema(ctx context.Context) string {
|
||||||
|
if v := ctx.Value(contextKeySchema); v != nil {
|
||||||
|
return v.(string)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEntity adds entity to context
|
||||||
|
func WithEntity(ctx context.Context, entity string) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKeyEntity, entity)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEntity retrieves entity from context
|
||||||
|
func GetEntity(ctx context.Context) string {
|
||||||
|
if v := ctx.Value(contextKeyEntity); v != nil {
|
||||||
|
return v.(string)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTableName adds table name to context
|
||||||
|
func WithTableName(ctx context.Context, tableName string) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKeyTableName, tableName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTableName retrieves table name from context
|
||||||
|
func GetTableName(ctx context.Context) string {
|
||||||
|
if v := ctx.Value(contextKeyTableName); v != nil {
|
||||||
|
return v.(string)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithModel adds model to context
|
||||||
|
func WithModel(ctx context.Context, model interface{}) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKeyModel, model)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetModel retrieves model from context
|
||||||
|
func GetModel(ctx context.Context) interface{} {
|
||||||
|
return ctx.Value(contextKeyModel)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithModelPtr adds model pointer to context
|
||||||
|
func WithModelPtr(ctx context.Context, modelPtr interface{}) context.Context {
|
||||||
|
return context.WithValue(ctx, contextKeyModelPtr, modelPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetModelPtr retrieves model pointer from context
|
||||||
|
func GetModelPtr(ctx context.Context) interface{} {
|
||||||
|
return ctx.Value(contextKeyModelPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithRequestData adds all request-scoped data to context at once
|
||||||
|
func WithRequestData(ctx context.Context, schema, entity, tableName string, model, modelPtr interface{}) context.Context {
|
||||||
|
ctx = WithSchema(ctx, schema)
|
||||||
|
ctx = WithEntity(ctx, entity)
|
||||||
|
ctx = WithTableName(ctx, tableName)
|
||||||
|
ctx = WithModel(ctx, model)
|
||||||
|
ctx = WithModelPtr(ctx, modelPtr)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
@ -44,14 +44,28 @@ func (h *Handler) Handle(w common.ResponseWriter, r common.Request, params map[s
|
|||||||
|
|
||||||
logger.Info("Handling %s request for %s.%s", method, schema, entity)
|
logger.Info("Handling %s request for %s.%s", method, schema, entity)
|
||||||
|
|
||||||
|
// Get model and populate context with request-scoped data
|
||||||
|
model, err := h.registry.GetModelByEntity(schema, entity)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Invalid entity: %v", err)
|
||||||
|
h.sendError(w, http.StatusBadRequest, "invalid_entity", "Invalid entity", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
modelPtr := reflect.New(reflect.TypeOf(model)).Interface()
|
||||||
|
tableName := h.getTableName(schema, entity, model)
|
||||||
|
|
||||||
|
// Add request-scoped data to context
|
||||||
|
ctx = WithRequestData(ctx, schema, entity, tableName, model, modelPtr)
|
||||||
|
|
||||||
switch method {
|
switch method {
|
||||||
case "GET":
|
case "GET":
|
||||||
if id != "" {
|
if id != "" {
|
||||||
// GET with ID - read single record
|
// GET with ID - read single record
|
||||||
h.handleRead(ctx, w, schema, entity, id, options)
|
h.handleRead(ctx, w, id, options)
|
||||||
} else {
|
} else {
|
||||||
// GET without ID - read multiple records
|
// GET without ID - read multiple records
|
||||||
h.handleRead(ctx, w, schema, entity, "", options)
|
h.handleRead(ctx, w, "", options)
|
||||||
}
|
}
|
||||||
case "POST":
|
case "POST":
|
||||||
// Create operation
|
// Create operation
|
||||||
@ -67,7 +81,7 @@ func (h *Handler) Handle(w common.ResponseWriter, r common.Request, params map[s
|
|||||||
h.sendError(w, http.StatusBadRequest, "invalid_request", "Invalid request body", err)
|
h.sendError(w, http.StatusBadRequest, "invalid_request", "Invalid request body", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.handleCreate(ctx, w, schema, entity, data, options)
|
h.handleCreate(ctx, w, data, options)
|
||||||
case "PUT", "PATCH":
|
case "PUT", "PATCH":
|
||||||
// Update operation
|
// Update operation
|
||||||
body, err := r.Body()
|
body, err := r.Body()
|
||||||
@ -82,9 +96,9 @@ func (h *Handler) Handle(w common.ResponseWriter, r common.Request, params map[s
|
|||||||
h.sendError(w, http.StatusBadRequest, "invalid_request", "Invalid request body", err)
|
h.sendError(w, http.StatusBadRequest, "invalid_request", "Invalid request body", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
h.handleUpdate(ctx, w, schema, entity, id, nil, data, options)
|
h.handleUpdate(ctx, w, id, nil, data, options)
|
||||||
case "DELETE":
|
case "DELETE":
|
||||||
h.handleDelete(ctx, w, schema, entity, id)
|
h.handleDelete(ctx, w, id)
|
||||||
default:
|
default:
|
||||||
logger.Error("Invalid HTTP method: %s", method)
|
logger.Error("Invalid HTTP method: %s", method)
|
||||||
h.sendError(w, http.StatusMethodNotAllowed, "invalid_method", "Invalid HTTP method", nil)
|
h.sendError(w, http.StatusMethodNotAllowed, "invalid_method", "Invalid HTTP method", nil)
|
||||||
@ -111,20 +125,15 @@ func (h *Handler) HandleGet(w common.ResponseWriter, r common.Request, params ma
|
|||||||
|
|
||||||
// parseOptionsFromHeaders is now implemented in headers.go
|
// parseOptionsFromHeaders is now implemented in headers.go
|
||||||
|
|
||||||
func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, schema, entity, id string, options ExtendedRequestOptions) {
|
func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id string, options ExtendedRequestOptions) {
|
||||||
|
schema := GetSchema(ctx)
|
||||||
|
entity := GetEntity(ctx)
|
||||||
|
tableName := GetTableName(ctx)
|
||||||
|
modelPtr := GetModelPtr(ctx)
|
||||||
|
|
||||||
logger.Info("Reading records from %s.%s", schema, entity)
|
logger.Info("Reading records from %s.%s", schema, entity)
|
||||||
|
|
||||||
model, err := h.registry.GetModelByEntity(schema, entity)
|
query := h.db.NewSelect().Model(modelPtr)
|
||||||
if err != nil {
|
|
||||||
logger.Error("Invalid entity: %v", err)
|
|
||||||
h.sendError(w, http.StatusBadRequest, "invalid_entity", "Invalid entity", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
query := h.db.NewSelect().Model(model)
|
|
||||||
|
|
||||||
// Get table name
|
|
||||||
tableName := h.getTableName(schema, entity, model)
|
|
||||||
query = query.Table(tableName)
|
query = query.Table(tableName)
|
||||||
|
|
||||||
// Apply column selection
|
// Apply column selection
|
||||||
@ -214,8 +223,9 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, schem
|
|||||||
query = query.Offset(*options.Offset)
|
query = query.Offset(*options.Offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute query
|
// Execute query - create a slice of pointers to the model type
|
||||||
resultSlice := reflect.New(reflect.SliceOf(reflect.TypeOf(model))).Interface()
|
model := GetModel(ctx)
|
||||||
|
resultSlice := reflect.New(reflect.SliceOf(reflect.PointerTo(reflect.TypeOf(model)))).Interface()
|
||||||
if err := query.Scan(ctx, resultSlice); err != nil {
|
if err := query.Scan(ctx, resultSlice); err != nil {
|
||||||
logger.Error("Error executing query: %v", err)
|
logger.Error("Error executing query: %v", err)
|
||||||
h.sendError(w, http.StatusInternalServerError, "query_error", "Error executing query", err)
|
h.sendError(w, http.StatusInternalServerError, "query_error", "Error executing query", err)
|
||||||
@ -241,18 +251,14 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, schem
|
|||||||
h.sendFormattedResponse(w, resultSlice, metadata, options)
|
h.sendFormattedResponse(w, resultSlice, metadata, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, schema, entity string, data interface{}, options ExtendedRequestOptions) {
|
func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, data interface{}, options ExtendedRequestOptions) {
|
||||||
|
schema := GetSchema(ctx)
|
||||||
|
entity := GetEntity(ctx)
|
||||||
|
tableName := GetTableName(ctx)
|
||||||
|
model := GetModel(ctx)
|
||||||
|
|
||||||
logger.Info("Creating record in %s.%s", schema, entity)
|
logger.Info("Creating record in %s.%s", schema, entity)
|
||||||
|
|
||||||
model, err := h.registry.GetModelByEntity(schema, entity)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Invalid entity: %v", err)
|
|
||||||
h.sendError(w, http.StatusBadRequest, "invalid_entity", "Invalid entity", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tableName := h.getTableName(schema, entity, model)
|
|
||||||
|
|
||||||
// Handle batch creation
|
// Handle batch creation
|
||||||
dataValue := reflect.ValueOf(data)
|
dataValue := reflect.ValueOf(data)
|
||||||
if dataValue.Kind() == reflect.Slice || dataValue.Kind() == reflect.Array {
|
if dataValue.Kind() == reflect.Slice || dataValue.Kind() == reflect.Array {
|
||||||
@ -263,8 +269,8 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, sch
|
|||||||
for i := 0; i < dataValue.Len(); i++ {
|
for i := 0; i < dataValue.Len(); i++ {
|
||||||
item := dataValue.Index(i).Interface()
|
item := dataValue.Index(i).Interface()
|
||||||
|
|
||||||
// Convert item to model type
|
// Convert item to model type - create a pointer to the model
|
||||||
modelValue := reflect.New(reflect.TypeOf(model).Elem()).Interface()
|
modelValue := reflect.New(reflect.TypeOf(model)).Interface()
|
||||||
jsonData, err := json.Marshal(item)
|
jsonData, err := json.Marshal(item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal item: %w", err)
|
return fmt.Errorf("failed to marshal item: %w", err)
|
||||||
@ -291,8 +297,8 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, sch
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Single record creation
|
// Single record creation - create a pointer to the model
|
||||||
modelValue := reflect.New(reflect.TypeOf(model).Elem()).Interface()
|
modelValue := reflect.New(reflect.TypeOf(model)).Interface()
|
||||||
jsonData, err := json.Marshal(data)
|
jsonData, err := json.Marshal(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Error marshaling data: %v", err)
|
logger.Error("Error marshaling data: %v", err)
|
||||||
@ -315,18 +321,13 @@ func (h *Handler) handleCreate(ctx context.Context, w common.ResponseWriter, sch
|
|||||||
h.sendResponse(w, modelValue, nil)
|
h.sendResponse(w, modelValue, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, schema, entity, id string, idPtr *int64, data interface{}, options ExtendedRequestOptions) {
|
func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id string, idPtr *int64, data interface{}, options ExtendedRequestOptions) {
|
||||||
|
schema := GetSchema(ctx)
|
||||||
|
entity := GetEntity(ctx)
|
||||||
|
tableName := GetTableName(ctx)
|
||||||
|
|
||||||
logger.Info("Updating record in %s.%s", schema, entity)
|
logger.Info("Updating record in %s.%s", schema, entity)
|
||||||
|
|
||||||
model, err := h.registry.GetModelByEntity(schema, entity)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Invalid entity: %v", err)
|
|
||||||
h.sendError(w, http.StatusBadRequest, "invalid_entity", "Invalid entity", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tableName := h.getTableName(schema, entity, model)
|
|
||||||
|
|
||||||
// Convert data to map
|
// Convert data to map
|
||||||
dataMap, ok := data.(map[string]interface{})
|
dataMap, ok := data.(map[string]interface{})
|
||||||
if !ok {
|
if !ok {
|
||||||
@ -367,18 +368,13 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, sch
|
|||||||
}, nil)
|
}, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, schema, entity, id string) {
|
func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id string) {
|
||||||
|
schema := GetSchema(ctx)
|
||||||
|
entity := GetEntity(ctx)
|
||||||
|
tableName := GetTableName(ctx)
|
||||||
|
|
||||||
logger.Info("Deleting record from %s.%s", schema, entity)
|
logger.Info("Deleting record from %s.%s", schema, entity)
|
||||||
|
|
||||||
model, err := h.registry.GetModelByEntity(schema, entity)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("Invalid entity: %v", err)
|
|
||||||
h.sendError(w, http.StatusBadRequest, "invalid_entity", "Invalid entity", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tableName := h.getTableName(schema, entity, model)
|
|
||||||
|
|
||||||
query := h.db.NewDelete().Table(tableName)
|
query := h.db.NewDelete().Table(tableName)
|
||||||
|
|
||||||
if id == "" {
|
if id == "" {
|
||||||
|
|||||||
@ -1,3 +1,55 @@
|
|||||||
|
// Package restheadspec provides the Rest Header Spec API framework.
|
||||||
|
//
|
||||||
|
// Rest Header Spec (restheadspec) is a RESTful API framework that reads query options,
|
||||||
|
// filters, sorting, pagination, and other parameters from HTTP headers instead of
|
||||||
|
// request bodies or query parameters. This approach provides a clean separation between
|
||||||
|
// data and metadata in API requests.
|
||||||
|
//
|
||||||
|
// # Key Features
|
||||||
|
//
|
||||||
|
// - Header-based API configuration: All query options are passed via HTTP headers
|
||||||
|
// - Database-agnostic: Works with both GORM and Bun ORM through adapters
|
||||||
|
// - Router-agnostic: Supports multiple HTTP routers (Mux, BunRouter, etc.)
|
||||||
|
// - Advanced filtering: Supports complex filter operations (eq, gt, lt, like, between, etc.)
|
||||||
|
// - Pagination and sorting: Built-in support for limit, offset, and multi-column sorting
|
||||||
|
// - Preloading and expansion: Support for eager loading relationships
|
||||||
|
// - Multiple response formats: Default, simple, and Syncfusion formats
|
||||||
|
//
|
||||||
|
// # HTTP Headers
|
||||||
|
//
|
||||||
|
// The following headers are supported for configuring API requests:
|
||||||
|
//
|
||||||
|
// - X-Filters: JSON array of filter conditions
|
||||||
|
// - X-Columns: Comma-separated list of columns to select
|
||||||
|
// - X-Sort: JSON array of sort specifications
|
||||||
|
// - X-Limit: Maximum number of records to return
|
||||||
|
// - X-Offset: Number of records to skip
|
||||||
|
// - X-Preload: Comma-separated list of relations to preload
|
||||||
|
// - X-Expand: Comma-separated list of relations to expand (LEFT JOIN)
|
||||||
|
// - X-Distinct: Boolean to enable DISTINCT queries
|
||||||
|
// - X-Skip-Count: Boolean to skip total count query
|
||||||
|
// - X-Response-Format: Response format (detail, simple, syncfusion)
|
||||||
|
// - X-Clean-JSON: Boolean to remove null/empty fields
|
||||||
|
// - X-Custom-SQL-Where: Custom SQL WHERE clause (AND)
|
||||||
|
// - X-Custom-SQL-Or: Custom SQL WHERE clause (OR)
|
||||||
|
//
|
||||||
|
// # Usage Example
|
||||||
|
//
|
||||||
|
// // Create a handler with GORM
|
||||||
|
// handler := restheadspec.NewHandlerWithGORM(db)
|
||||||
|
//
|
||||||
|
// // Register models
|
||||||
|
// handler.Registry.RegisterModel("users", User{})
|
||||||
|
//
|
||||||
|
// // Setup routes with Mux
|
||||||
|
// muxRouter := mux.NewRouter()
|
||||||
|
// restheadspec.SetupMuxRoutes(muxRouter, handler)
|
||||||
|
//
|
||||||
|
// // Make a request with headers
|
||||||
|
// // GET /public/users
|
||||||
|
// // X-Filters: [{"column":"age","operator":"gt","value":18}]
|
||||||
|
// // X-Sort: [{"column":"name","direction":"asc"}]
|
||||||
|
// // X-Limit: 10
|
||||||
package restheadspec
|
package restheadspec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user