mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-30 16:24:26 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5c0608f63 | ||
|
|
39c3f05d21 | ||
|
|
4ecd1ac17e | ||
|
|
2b1aea0338 | ||
|
|
1e749efeb3 | ||
|
|
09be676096 | ||
|
|
e8350a70be | ||
|
|
5937b9eab5 |
@@ -2,6 +2,7 @@ package resolvespec
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -957,7 +958,29 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
query := h.db.NewDelete().Table(tableName).Where(fmt.Sprintf("%s = ?", common.QuoteIdent(reflection.GetPrimaryKeyName(model))), id)
|
// Get primary key name
|
||||||
|
pkName := reflection.GetPrimaryKeyName(model)
|
||||||
|
|
||||||
|
// First, fetch the record that will be deleted
|
||||||
|
modelType := reflect.TypeOf(model)
|
||||||
|
if modelType.Kind() == reflect.Ptr {
|
||||||
|
modelType = modelType.Elem()
|
||||||
|
}
|
||||||
|
recordToDelete := reflect.New(modelType).Interface()
|
||||||
|
|
||||||
|
selectQuery := h.db.NewSelect().Model(recordToDelete).Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), id)
|
||||||
|
if err := selectQuery.ScanModel(ctx); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
logger.Warn("Record not found for delete: %s = %s", pkName, id)
|
||||||
|
h.sendError(w, http.StatusNotFound, "not_found", "Record not found", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Error("Error fetching record for delete: %v", err)
|
||||||
|
h.sendError(w, http.StatusInternalServerError, "fetch_error", "Error fetching record", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
query := h.db.NewDelete().Table(tableName).Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), id)
|
||||||
|
|
||||||
result, err := query.Exec(ctx)
|
result, err := query.Exec(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -966,14 +989,16 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if the record was actually deleted
|
||||||
if result.RowsAffected() == 0 {
|
if result.RowsAffected() == 0 {
|
||||||
logger.Warn("No record found to delete with ID: %s", id)
|
logger.Warn("No rows deleted for ID: %s", id)
|
||||||
h.sendError(w, http.StatusNotFound, "not_found", "Record not found", nil)
|
h.sendError(w, http.StatusNotFound, "not_found", "Record not found or already deleted", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("Successfully deleted record with ID: %s", id)
|
logger.Info("Successfully deleted record with ID: %s", id)
|
||||||
h.sendResponse(w, nil, nil)
|
// Return the deleted record data
|
||||||
|
h.sendResponse(w, recordToDelete, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) applyFilter(query common.SelectQuery, filter common.FilterOption) common.SelectQuery {
|
func (h *Handler) applyFilter(query common.SelectQuery, filter common.FilterOption) common.SelectQuery {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package restheadspec
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"database/sql"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -661,6 +662,14 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if a specific ID was requested but no record was found
|
||||||
|
resultCount := reflection.Len(modelPtr)
|
||||||
|
if id != "" && resultCount == 0 {
|
||||||
|
logger.Warn("Record not found for ID: %s", id)
|
||||||
|
h.sendError(w, http.StatusNotFound, "not_found", "Record not found", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
limit := 0
|
limit := 0
|
||||||
if options.Limit != nil {
|
if options.Limit != nil {
|
||||||
limit = *options.Limit
|
limit = *options.Limit
|
||||||
@@ -675,7 +684,7 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
|||||||
|
|
||||||
metadata := &common.Metadata{
|
metadata := &common.Metadata{
|
||||||
Total: int64(total),
|
Total: int64(total),
|
||||||
Count: int64(reflection.Len(modelPtr)),
|
Count: int64(resultCount),
|
||||||
Filtered: int64(total),
|
Filtered: int64(total),
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
Offset: offset,
|
Offset: offset,
|
||||||
@@ -1247,7 +1256,7 @@ func (h *Handler) handleUpdate(ctx context.Context, w common.ResponseWriter, id
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create update query using Model() to preserve custom types and driver.Valuer interfaces
|
// Create update query using Model() to preserve custom types and driver.Valuer interfaces
|
||||||
query := tx.NewUpdate().Model(modelInstance).Table(tableName)
|
query := tx.NewUpdate().Model(modelInstance)
|
||||||
query = query.Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), targetID)
|
query = query.Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), targetID)
|
||||||
|
|
||||||
// Execute BeforeScan hooks - pass query chain so hooks can modify it
|
// Execute BeforeScan hooks - pass query chain so hooks can modify it
|
||||||
@@ -1514,7 +1523,34 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Single delete with URL ID
|
// Single delete with URL ID
|
||||||
// Execute BeforeDelete hooks
|
if id == "" {
|
||||||
|
h.sendError(w, http.StatusBadRequest, "missing_id", "ID is required for delete", nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get primary key name
|
||||||
|
pkName := reflection.GetPrimaryKeyName(model)
|
||||||
|
|
||||||
|
// First, fetch the record that will be deleted
|
||||||
|
modelType := reflect.TypeOf(model)
|
||||||
|
if modelType.Kind() == reflect.Ptr {
|
||||||
|
modelType = modelType.Elem()
|
||||||
|
}
|
||||||
|
recordToDelete := reflect.New(modelType).Interface()
|
||||||
|
|
||||||
|
selectQuery := h.db.NewSelect().Model(recordToDelete).Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), id)
|
||||||
|
if err := selectQuery.ScanModel(ctx); err != nil {
|
||||||
|
if err == sql.ErrNoRows {
|
||||||
|
logger.Warn("Record not found for delete: %s = %s", pkName, id)
|
||||||
|
h.sendError(w, http.StatusNotFound, "not_found", "Record not found", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.Error("Error fetching record for delete: %v", err)
|
||||||
|
h.sendError(w, http.StatusInternalServerError, "fetch_error", "Error fetching record", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute BeforeDelete hooks with the record data
|
||||||
hookCtx := &HookContext{
|
hookCtx := &HookContext{
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
Handler: h,
|
Handler: h,
|
||||||
@@ -1525,6 +1561,7 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
ID: id,
|
ID: id,
|
||||||
Writer: w,
|
Writer: w,
|
||||||
Tx: h.db,
|
Tx: h.db,
|
||||||
|
Data: recordToDelete,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.hooks.Execute(BeforeDelete, hookCtx); err != nil {
|
if err := h.hooks.Execute(BeforeDelete, hookCtx); err != nil {
|
||||||
@@ -1534,13 +1571,7 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := h.db.NewDelete().Table(tableName)
|
query := h.db.NewDelete().Table(tableName)
|
||||||
|
query = query.Where(fmt.Sprintf("%s = ?", common.QuoteIdent(pkName)), id)
|
||||||
if id == "" {
|
|
||||||
h.sendError(w, http.StatusBadRequest, "missing_id", "ID is required for delete", nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
query = query.Where(fmt.Sprintf("%s = ?", common.QuoteIdent(reflection.GetPrimaryKeyName(model))), id)
|
|
||||||
|
|
||||||
// Execute BeforeScan hooks - pass query chain so hooks can modify it
|
// Execute BeforeScan hooks - pass query chain so hooks can modify it
|
||||||
hookCtx.Query = query
|
hookCtx.Query = query
|
||||||
@@ -1562,11 +1593,15 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute AfterDelete hooks
|
// Check if the record was actually deleted
|
||||||
responseData := map[string]interface{}{
|
if result.RowsAffected() == 0 {
|
||||||
"deleted": result.RowsAffected(),
|
logger.Warn("No rows deleted for ID: %s", id)
|
||||||
|
h.sendError(w, http.StatusNotFound, "not_found", "Record not found or already deleted", nil)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
hookCtx.Result = responseData
|
|
||||||
|
// Execute AfterDelete hooks with the deleted record data
|
||||||
|
hookCtx.Result = recordToDelete
|
||||||
hookCtx.Error = nil
|
hookCtx.Error = nil
|
||||||
|
|
||||||
if err := h.hooks.Execute(AfterDelete, hookCtx); err != nil {
|
if err := h.hooks.Execute(AfterDelete, hookCtx); err != nil {
|
||||||
@@ -1575,7 +1610,8 @@ func (h *Handler) handleDelete(ctx context.Context, w common.ResponseWriter, id
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.sendResponse(w, responseData, nil)
|
// Return the deleted record data
|
||||||
|
h.sendResponse(w, recordToDelete, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeRecordWithRequest merges a database record with the original request data
|
// mergeRecordWithRequest merges a database record with the original request data
|
||||||
@@ -2071,14 +2107,20 @@ func (h *Handler) sendResponse(w common.ResponseWriter, data interface{}, metada
|
|||||||
|
|
||||||
// sendResponseWithOptions sends a response with optional formatting
|
// sendResponseWithOptions sends a response with optional formatting
|
||||||
func (h *Handler) sendResponseWithOptions(w common.ResponseWriter, data interface{}, metadata *common.Metadata, options *ExtendedRequestOptions) {
|
func (h *Handler) sendResponseWithOptions(w common.ResponseWriter, data interface{}, metadata *common.Metadata, options *ExtendedRequestOptions) {
|
||||||
|
w.SetHeader("Content-Type", "application/json")
|
||||||
|
if data == nil {
|
||||||
|
data = map[string]interface{}{}
|
||||||
|
w.WriteHeader(http.StatusPartialContent)
|
||||||
|
} else {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
// Normalize single-record arrays to objects if requested
|
// Normalize single-record arrays to objects if requested
|
||||||
if options != nil && options.SingleRecordAsObject {
|
if options != nil && options.SingleRecordAsObject {
|
||||||
data = h.normalizeResultArray(data)
|
data = h.normalizeResultArray(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return data as-is without wrapping in common.Response
|
// Return data as-is without wrapping in common.Response
|
||||||
w.SetHeader("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
if err := w.WriteJSON(data); err != nil {
|
if err := w.WriteJSON(data); err != nil {
|
||||||
logger.Error("Failed to write JSON response: %v", err)
|
logger.Error("Failed to write JSON response: %v", err)
|
||||||
}
|
}
|
||||||
@@ -2088,7 +2130,7 @@ func (h *Handler) sendResponseWithOptions(w common.ResponseWriter, data interfac
|
|||||||
// Returns the single element if data is a slice/array with exactly one element, otherwise returns data unchanged
|
// Returns the single element if data is a slice/array with exactly one element, otherwise returns data unchanged
|
||||||
func (h *Handler) normalizeResultArray(data interface{}) interface{} {
|
func (h *Handler) normalizeResultArray(data interface{}) interface{} {
|
||||||
if data == nil {
|
if data == nil {
|
||||||
return nil
|
return map[string]interface{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use reflection to check if data is a slice or array
|
// Use reflection to check if data is a slice or array
|
||||||
@@ -2097,18 +2139,41 @@ func (h *Handler) normalizeResultArray(data interface{}) interface{} {
|
|||||||
dataValue = dataValue.Elem()
|
dataValue = dataValue.Elem()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if it's a slice or array with exactly one element
|
// Check if it's a slice or array
|
||||||
if (dataValue.Kind() == reflect.Slice || dataValue.Kind() == reflect.Array) && dataValue.Len() == 1 {
|
if dataValue.Kind() == reflect.Slice || dataValue.Kind() == reflect.Array {
|
||||||
// Return the single element
|
if dataValue.Len() == 1 {
|
||||||
return dataValue.Index(0).Interface()
|
// Return the single element
|
||||||
|
return dataValue.Index(0).Interface()
|
||||||
|
} else if dataValue.Len() == 0 {
|
||||||
|
// Return empty object instead of empty array
|
||||||
|
return map[string]interface{}{}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dataValue.Kind() == reflect.String {
|
||||||
|
str := dataValue.String()
|
||||||
|
if str == "" || str == "null" {
|
||||||
|
return map[string]interface{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendFormattedResponse sends response with formatting options
|
// sendFormattedResponse sends response with formatting options
|
||||||
func (h *Handler) sendFormattedResponse(w common.ResponseWriter, data interface{}, metadata *common.Metadata, options ExtendedRequestOptions) {
|
func (h *Handler) sendFormattedResponse(w common.ResponseWriter, data interface{}, metadata *common.Metadata, options ExtendedRequestOptions) {
|
||||||
// Normalize single-record arrays to objects if requested
|
// Normalize single-record arrays to objects if requested
|
||||||
|
httpStatus := http.StatusOK
|
||||||
|
if data == nil {
|
||||||
|
data = map[string]interface{}{}
|
||||||
|
httpStatus = http.StatusPartialContent
|
||||||
|
} else {
|
||||||
|
dataLen := reflection.Len(data)
|
||||||
|
if dataLen == 0 {
|
||||||
|
httpStatus = http.StatusPartialContent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if options.SingleRecordAsObject {
|
if options.SingleRecordAsObject {
|
||||||
data = h.normalizeResultArray(data)
|
data = h.normalizeResultArray(data)
|
||||||
}
|
}
|
||||||
@@ -2127,7 +2192,7 @@ func (h *Handler) sendFormattedResponse(w common.ResponseWriter, data interface{
|
|||||||
switch options.ResponseFormat {
|
switch options.ResponseFormat {
|
||||||
case "simple":
|
case "simple":
|
||||||
// Simple format: just return the data array
|
// Simple format: just return the data array
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(httpStatus)
|
||||||
if err := w.WriteJSON(data); err != nil {
|
if err := w.WriteJSON(data); err != nil {
|
||||||
logger.Error("Failed to write JSON response: %v", err)
|
logger.Error("Failed to write JSON response: %v", err)
|
||||||
}
|
}
|
||||||
@@ -2139,7 +2204,7 @@ func (h *Handler) sendFormattedResponse(w common.ResponseWriter, data interface{
|
|||||||
if metadata != nil {
|
if metadata != nil {
|
||||||
response["count"] = metadata.Total
|
response["count"] = metadata.Total
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(httpStatus)
|
||||||
if err := w.WriteJSON(response); err != nil {
|
if err := w.WriteJSON(response); err != nil {
|
||||||
logger.Error("Failed to write JSON response: %v", err)
|
logger.Error("Failed to write JSON response: %v", err)
|
||||||
}
|
}
|
||||||
@@ -2150,7 +2215,7 @@ func (h *Handler) sendFormattedResponse(w common.ResponseWriter, data interface{
|
|||||||
Data: data,
|
Data: data,
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(httpStatus)
|
||||||
if err := w.WriteJSON(response); err != nil {
|
if err := w.WriteJSON(response); err != nil {
|
||||||
logger.Error("Failed to write JSON response: %v", err)
|
logger.Error("Failed to write JSON response: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user