Initial commit: Open sourcing all of the Maple Open Technologies code.
This commit is contained in:
commit
755d54a99d
2010 changed files with 448675 additions and 0 deletions
|
|
@ -0,0 +1,96 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/collection/archive.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type ArchiveCollectionHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_collection.ArchiveCollectionService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewArchiveCollectionHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.ArchiveCollectionService,
|
||||
middleware middleware.Middleware,
|
||||
) *ArchiveCollectionHTTPHandler {
|
||||
logger = logger.Named("ArchiveCollectionHTTPHandler")
|
||||
return &ArchiveCollectionHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*ArchiveCollectionHTTPHandler) Pattern() string {
|
||||
return "PUT /api/v1/collections/{id}/archive"
|
||||
}
|
||||
|
||||
func (h *ArchiveCollectionHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *ArchiveCollectionHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract collection ID from the URL
|
||||
collectionIDStr := r.PathValue("id")
|
||||
if collectionIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Collection ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
collectionID, err := gocql.ParseUUID(collectionIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid collection ID format",
|
||||
zap.String("collection_id", collectionIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Invalid collection ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
// Create request DTO
|
||||
dtoReq := &svc_collection.ArchiveCollectionRequestDTO{
|
||||
ID: collectionID,
|
||||
}
|
||||
|
||||
resp, err := h.service.Execute(ctx, dtoReq)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Encode response
|
||||
if resp != nil {
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
h.logger.Error("failed to encode response",
|
||||
zap.Any("error", err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := errors.New("no result")
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/collection/create.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type CreateCollectionHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_collection.CreateCollectionService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewCreateCollectionHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.CreateCollectionService,
|
||||
middleware middleware.Middleware,
|
||||
) *CreateCollectionHTTPHandler {
|
||||
logger = logger.Named("CreateCollectionHTTPHandler")
|
||||
return &CreateCollectionHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*CreateCollectionHTTPHandler) Pattern() string {
|
||||
return "POST /api/v1/collections"
|
||||
}
|
||||
|
||||
func (h *CreateCollectionHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *CreateCollectionHTTPHandler) unmarshalRequest(
|
||||
ctx context.Context,
|
||||
r *http.Request,
|
||||
) (*svc_collection.CreateCollectionRequestDTO, error) {
|
||||
// Initialize our structure which will store the parsed request data
|
||||
var requestData svc_collection.CreateCollectionRequestDTO
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
var rawJSON bytes.Buffer
|
||||
teeReader := io.TeeReader(r.Body, &rawJSON) // TeeReader allows you to read the JSON and capture it
|
||||
|
||||
// Read the JSON string and convert it into our golang struct
|
||||
err := json.NewDecoder(teeReader).Decode(&requestData)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to decode create collection request",
|
||||
zap.Error(err),
|
||||
zap.String("json", rawJSON.String()),
|
||||
)
|
||||
return nil, httperror.NewBadRequestError("Invalid request payload. Please check your collection data.")
|
||||
}
|
||||
|
||||
return &requestData, nil
|
||||
}
|
||||
|
||||
func (h *CreateCollectionHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := h.unmarshalRequest(ctx, r)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to unmarshal create collection request", zap.Error(err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.service.Execute(ctx, req)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to create collection", zap.Error(err))
|
||||
// Service returns RFC 9457 errors, use RespondWithError to handle them
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if resp == nil {
|
||||
h.logger.Error("No collection returned from service")
|
||||
problem := httperror.NewInternalServerError("Failed to create collection. Please try again.").
|
||||
WithInstance(r.URL.Path).
|
||||
WithTraceID(httperror.ExtractRequestID(r))
|
||||
httperror.RespondWithProblem(w, problem)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
h.logger.Error("Failed to encode collection response", zap.Error(err))
|
||||
// At this point headers are already sent, log the error but can't send RFC 9457 response
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/collection/find_by_parent.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type FindCollectionsByParentHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_collection.FindCollectionsByParentService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewFindCollectionsByParentHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.FindCollectionsByParentService,
|
||||
middleware middleware.Middleware,
|
||||
) *FindCollectionsByParentHTTPHandler {
|
||||
logger = logger.Named("FindCollectionsByParentHTTPHandler")
|
||||
return &FindCollectionsByParentHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*FindCollectionsByParentHTTPHandler) Pattern() string {
|
||||
return "GET /api/v1/collections/parent/{parent_id}"
|
||||
}
|
||||
|
||||
func (h *FindCollectionsByParentHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *FindCollectionsByParentHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract parent ID from URL parameters
|
||||
parentIDStr := r.PathValue("parent_id")
|
||||
if parentIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("parent_id", "Parent ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
parentID, err := gocql.ParseUUID(parentIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid parent ID format",
|
||||
zap.String("parent_id", parentIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("parent_id", "Invalid parent ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
// Create request DTO
|
||||
req := &svc_collection.FindByParentRequestDTO{
|
||||
ParentID: parentID,
|
||||
}
|
||||
|
||||
// Call service
|
||||
resp, err := h.service.Execute(ctx, req)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Encode response
|
||||
if resp != nil {
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
h.logger.Error("failed to encode response",
|
||||
zap.Any("error", err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := errors.New("no result")
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/collection/find_root_collections.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type FindRootCollectionsHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_collection.FindRootCollectionsService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewFindRootCollectionsHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.FindRootCollectionsService,
|
||||
middleware middleware.Middleware,
|
||||
) *FindRootCollectionsHTTPHandler {
|
||||
logger = logger.Named("FindRootCollectionsHTTPHandler")
|
||||
return &FindRootCollectionsHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*FindRootCollectionsHTTPHandler) Pattern() string {
|
||||
return "GET /api/v1/collections/root"
|
||||
}
|
||||
|
||||
func (h *FindRootCollectionsHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *FindRootCollectionsHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
resp, err := h.service.Execute(ctx)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to find root collections", zap.Error(err))
|
||||
// Service returns RFC 9457 errors, use RespondWithError to handle them
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if resp == nil {
|
||||
h.logger.Error("No collections returned from service")
|
||||
problem := httperror.NewInternalServerError("Failed to retrieve collections. Please try again.").
|
||||
WithInstance(r.URL.Path).
|
||||
WithTraceID(httperror.ExtractRequestID(r))
|
||||
httperror.RespondWithProblem(w, problem)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
h.logger.Error("Failed to encode collections response", zap.Error(err))
|
||||
// At this point headers are already sent, log the error but can't send RFC 9457 response
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/collection/get.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type GetCollectionHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_collection.GetCollectionService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewGetCollectionHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.GetCollectionService,
|
||||
middleware middleware.Middleware,
|
||||
) *GetCollectionHTTPHandler {
|
||||
logger = logger.Named("GetCollectionHTTPHandler")
|
||||
return &GetCollectionHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*GetCollectionHTTPHandler) Pattern() string {
|
||||
return "GET /api/v1/collections/{id}"
|
||||
}
|
||||
|
||||
func (h *GetCollectionHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *GetCollectionHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract collection ID from URL parameters
|
||||
// Assuming Go 1.22+ where r.PathValue is available for patterns like "/items/{id}"
|
||||
collectionIDStr := r.PathValue("id")
|
||||
if collectionIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Collection ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
collectionID, err := gocql.ParseUUID(collectionIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid collection ID format",
|
||||
zap.String("collection_id", collectionIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Invalid collection ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.service.Execute(ctx, collectionID)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
// Encode response
|
||||
if resp != nil {
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
h.logger.Error("failed to encode response",
|
||||
zap.Any("error", err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := errors.New("no result")
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/collection/get_filtered.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type GetFilteredCollectionsHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_collection.GetFilteredCollectionsService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewGetFilteredCollectionsHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.GetFilteredCollectionsService,
|
||||
middleware middleware.Middleware,
|
||||
) *GetFilteredCollectionsHTTPHandler {
|
||||
logger = logger.Named("GetFilteredCollectionsHTTPHandler")
|
||||
return &GetFilteredCollectionsHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*GetFilteredCollectionsHTTPHandler) Pattern() string {
|
||||
return "GET /api/v1/collections/filtered"
|
||||
}
|
||||
|
||||
func (h *GetFilteredCollectionsHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *GetFilteredCollectionsHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Parse query parameters for filter options
|
||||
req, err := h.parseFilterOptions(r)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.service.Execute(ctx, req)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Encode response
|
||||
if resp != nil {
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
h.logger.Error("failed to encode response",
|
||||
zap.Any("error", err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := errors.New("no result")
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// parseFilterOptions parses the query parameters to create the request DTO
|
||||
func (h *GetFilteredCollectionsHTTPHandler) parseFilterOptions(r *http.Request) (*svc_collection.GetFilteredCollectionsRequestDTO, error) {
|
||||
req := &svc_collection.GetFilteredCollectionsRequestDTO{
|
||||
IncludeOwned: true, // Default to including owned collections
|
||||
IncludeShared: false, // Default to not including shared collections
|
||||
}
|
||||
|
||||
// Parse include_owned parameter
|
||||
if includeOwnedStr := r.URL.Query().Get("include_owned"); includeOwnedStr != "" {
|
||||
includeOwned, err := strconv.ParseBool(includeOwnedStr)
|
||||
if err != nil {
|
||||
h.logger.Warn("Invalid include_owned parameter",
|
||||
zap.String("value", includeOwnedStr),
|
||||
zap.Error(err))
|
||||
return nil, httperror.NewForBadRequestWithSingleField("include_owned", "Invalid boolean value for include_owned parameter")
|
||||
}
|
||||
req.IncludeOwned = includeOwned
|
||||
}
|
||||
|
||||
// Parse include_shared parameter
|
||||
if includeSharedStr := r.URL.Query().Get("include_shared"); includeSharedStr != "" {
|
||||
includeShared, err := strconv.ParseBool(includeSharedStr)
|
||||
if err != nil {
|
||||
h.logger.Warn("Invalid include_shared parameter",
|
||||
zap.String("value", includeSharedStr),
|
||||
zap.Error(err))
|
||||
return nil, httperror.NewForBadRequestWithSingleField("include_shared", "Invalid boolean value for include_shared parameter")
|
||||
}
|
||||
req.IncludeShared = includeShared
|
||||
}
|
||||
|
||||
// Validate that at least one option is enabled
|
||||
if !req.IncludeOwned && !req.IncludeShared {
|
||||
return nil, httperror.NewForBadRequestWithSingleField("filter_options", "At least one filter option (include_owned or include_shared) must be enabled")
|
||||
}
|
||||
|
||||
h.logger.Debug("Parsed filter options",
|
||||
zap.Bool("include_owned", req.IncludeOwned),
|
||||
zap.Bool("include_shared", req.IncludeShared))
|
||||
|
||||
return req, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/collection/list_by_user.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type ListUserCollectionsHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_collection.ListUserCollectionsService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewListUserCollectionsHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.ListUserCollectionsService,
|
||||
middleware middleware.Middleware,
|
||||
) *ListUserCollectionsHTTPHandler {
|
||||
logger = logger.Named("ListUserCollectionsHTTPHandler")
|
||||
return &ListUserCollectionsHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*ListUserCollectionsHTTPHandler) Pattern() string {
|
||||
return "GET /api/v1/collections"
|
||||
}
|
||||
|
||||
func (h *ListUserCollectionsHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *ListUserCollectionsHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
resp, err := h.service.Execute(ctx)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Encode response
|
||||
if resp != nil {
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
h.logger.Error("failed to encode response",
|
||||
zap.Any("error", err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := errors.New("no result")
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/collection/list_shared_with_user.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type ListSharedCollectionsHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_collection.ListSharedCollectionsService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewListSharedCollectionsHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.ListSharedCollectionsService,
|
||||
middleware middleware.Middleware,
|
||||
) *ListSharedCollectionsHTTPHandler {
|
||||
logger = logger.Named("ListSharedCollectionsHTTPHandler")
|
||||
return &ListSharedCollectionsHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*ListSharedCollectionsHTTPHandler) Pattern() string {
|
||||
return "GET /api/v1/collections/shared"
|
||||
}
|
||||
|
||||
func (h *ListSharedCollectionsHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *ListSharedCollectionsHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
// Call service
|
||||
resp, err := h.service.Execute(ctx)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Encode response
|
||||
if resp != nil {
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
h.logger.Error("failed to encode response",
|
||||
zap.Any("error", err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := errors.New("no result")
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/collection/move_collection.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type MoveCollectionHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_collection.MoveCollectionService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewMoveCollectionHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.MoveCollectionService,
|
||||
middleware middleware.Middleware,
|
||||
) *MoveCollectionHTTPHandler {
|
||||
logger = logger.Named("MoveCollectionHTTPHandler")
|
||||
return &MoveCollectionHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*MoveCollectionHTTPHandler) Pattern() string {
|
||||
return "PUT /api/v1/collections/{id}/move"
|
||||
}
|
||||
|
||||
func (h *MoveCollectionHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *MoveCollectionHTTPHandler) unmarshalRequest(
|
||||
ctx context.Context,
|
||||
r *http.Request,
|
||||
collectionID gocql.UUID,
|
||||
) (*svc_collection.MoveCollectionRequestDTO, error) {
|
||||
// Initialize our structure which will store the parsed request data
|
||||
var requestData svc_collection.MoveCollectionRequestDTO
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
var rawJSON bytes.Buffer
|
||||
teeReader := io.TeeReader(r.Body, &rawJSON) // TeeReader allows you to read the JSON and capture it
|
||||
|
||||
// Read the JSON string and convert it into our golang struct
|
||||
err := json.NewDecoder(teeReader).Decode(&requestData)
|
||||
if err != nil {
|
||||
h.logger.Error("decoding error",
|
||||
zap.Any("err", err),
|
||||
zap.String("json", rawJSON.String()),
|
||||
)
|
||||
return nil, httperror.NewForSingleField(http.StatusBadRequest, "non_field_error", "payload structure is wrong")
|
||||
}
|
||||
|
||||
// Set the collection ID from the URL parameter
|
||||
requestData.CollectionID = collectionID
|
||||
|
||||
return &requestData, nil
|
||||
}
|
||||
|
||||
func (h *MoveCollectionHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract collection ID from URL parameters
|
||||
collectionIDStr := r.PathValue("id")
|
||||
if collectionIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Collection ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
collectionID, err := gocql.ParseUUID(collectionIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid collection ID format",
|
||||
zap.String("collection_id", collectionIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Invalid collection ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
req, err := h.unmarshalRequest(ctx, r, collectionID)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.service.Execute(ctx, req)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Encode response
|
||||
if resp != nil {
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
h.logger.Error("failed to encode response",
|
||||
zap.Any("error", err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := errors.New("no result")
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
package collection
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
)
|
||||
|
||||
// Wire providers for collection HTTP handlers
|
||||
|
||||
func ProvideCreateCollectionHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.CreateCollectionService,
|
||||
mw middleware.Middleware,
|
||||
) *CreateCollectionHTTPHandler {
|
||||
return NewCreateCollectionHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideGetCollectionHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.GetCollectionService,
|
||||
mw middleware.Middleware,
|
||||
) *GetCollectionHTTPHandler {
|
||||
return NewGetCollectionHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideListUserCollectionsHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.ListUserCollectionsService,
|
||||
mw middleware.Middleware,
|
||||
) *ListUserCollectionsHTTPHandler {
|
||||
return NewListUserCollectionsHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideUpdateCollectionHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.UpdateCollectionService,
|
||||
mw middleware.Middleware,
|
||||
) *UpdateCollectionHTTPHandler {
|
||||
return NewUpdateCollectionHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideSoftDeleteCollectionHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.SoftDeleteCollectionService,
|
||||
mw middleware.Middleware,
|
||||
) *SoftDeleteCollectionHTTPHandler {
|
||||
return NewSoftDeleteCollectionHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideArchiveCollectionHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.ArchiveCollectionService,
|
||||
mw middleware.Middleware,
|
||||
) *ArchiveCollectionHTTPHandler {
|
||||
return NewArchiveCollectionHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideRestoreCollectionHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.RestoreCollectionService,
|
||||
mw middleware.Middleware,
|
||||
) *RestoreCollectionHTTPHandler {
|
||||
return NewRestoreCollectionHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideListSharedCollectionsHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.ListSharedCollectionsService,
|
||||
mw middleware.Middleware,
|
||||
) *ListSharedCollectionsHTTPHandler {
|
||||
return NewListSharedCollectionsHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideFindRootCollectionsHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.FindRootCollectionsService,
|
||||
mw middleware.Middleware,
|
||||
) *FindRootCollectionsHTTPHandler {
|
||||
return NewFindRootCollectionsHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideFindCollectionsByParentHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.FindCollectionsByParentService,
|
||||
mw middleware.Middleware,
|
||||
) *FindCollectionsByParentHTTPHandler {
|
||||
return NewFindCollectionsByParentHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideCollectionSyncHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.GetCollectionSyncDataService,
|
||||
mw middleware.Middleware,
|
||||
) *CollectionSyncHTTPHandler {
|
||||
return NewCollectionSyncHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideMoveCollectionHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.MoveCollectionService,
|
||||
mw middleware.Middleware,
|
||||
) *MoveCollectionHTTPHandler {
|
||||
return NewMoveCollectionHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideGetFilteredCollectionsHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.GetFilteredCollectionsService,
|
||||
mw middleware.Middleware,
|
||||
) *GetFilteredCollectionsHTTPHandler {
|
||||
return NewGetFilteredCollectionsHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideShareCollectionHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.ShareCollectionService,
|
||||
mw middleware.Middleware,
|
||||
) *ShareCollectionHTTPHandler {
|
||||
return NewShareCollectionHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideRemoveMemberHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.RemoveMemberService,
|
||||
mw middleware.Middleware,
|
||||
) *RemoveMemberHTTPHandler {
|
||||
return NewRemoveMemberHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/collection/remove_member.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type RemoveMemberHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_collection.RemoveMemberService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewRemoveMemberHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.RemoveMemberService,
|
||||
middleware middleware.Middleware,
|
||||
) *RemoveMemberHTTPHandler {
|
||||
logger = logger.Named("RemoveMemberHTTPHandler")
|
||||
return &RemoveMemberHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*RemoveMemberHTTPHandler) Pattern() string {
|
||||
return "DELETE /api/v1/collections/{id}/members/{user_id}"
|
||||
}
|
||||
|
||||
func (h *RemoveMemberHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *RemoveMemberHTTPHandler) unmarshalRequest(
|
||||
ctx context.Context,
|
||||
r *http.Request,
|
||||
collectionID gocql.UUID,
|
||||
recipientID gocql.UUID,
|
||||
) (*svc_collection.RemoveMemberRequestDTO, error) {
|
||||
// Initialize our structure which will store the parsed request data
|
||||
var requestData svc_collection.RemoveMemberRequestDTO
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
var rawJSON bytes.Buffer
|
||||
teeReader := io.TeeReader(r.Body, &rawJSON) // TeeReader allows you to read the JSON and capture it
|
||||
|
||||
// Read the JSON string and convert it into our golang struct
|
||||
err := json.NewDecoder(teeReader).Decode(&requestData)
|
||||
if err != nil {
|
||||
h.logger.Error("decoding error",
|
||||
zap.Any("err", err),
|
||||
zap.String("json", rawJSON.String()),
|
||||
)
|
||||
return nil, httperror.NewForSingleField(http.StatusBadRequest, "non_field_error", "payload structure is wrong")
|
||||
}
|
||||
|
||||
// Set the collection ID and recipient ID from the URL parameters
|
||||
requestData.CollectionID = collectionID
|
||||
requestData.RecipientID = recipientID
|
||||
|
||||
return &requestData, nil
|
||||
}
|
||||
|
||||
func (h *RemoveMemberHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract collection ID from URL parameters
|
||||
collectionIDStr := r.PathValue("id")
|
||||
if collectionIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Collection ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Extract user ID from URL parameters
|
||||
userIDStr := r.PathValue("user_id")
|
||||
if userIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("user_id", "User ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert collection ID string to UUID
|
||||
collectionID, err := gocql.ParseUUID(collectionIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid collection ID format",
|
||||
zap.String("collection_id", collectionIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Invalid collection ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert user ID string to UUID
|
||||
userID, err := gocql.ParseUUID(userIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid user ID format",
|
||||
zap.String("user_id", userIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("user_id", "Invalid user ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
req, err := h.unmarshalRequest(ctx, r, collectionID, userID)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.service.Execute(ctx, req)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Encode response
|
||||
if resp != nil {
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
h.logger.Error("failed to encode response",
|
||||
zap.Any("error", err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := errors.New("no result")
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/collection/restore.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type RestoreCollectionHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_collection.RestoreCollectionService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewRestoreCollectionHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.RestoreCollectionService,
|
||||
middleware middleware.Middleware,
|
||||
) *RestoreCollectionHTTPHandler {
|
||||
logger = logger.Named("RestoreCollectionHTTPHandler")
|
||||
return &RestoreCollectionHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*RestoreCollectionHTTPHandler) Pattern() string {
|
||||
return "PUT /api/v1/collections/{id}/restore"
|
||||
}
|
||||
|
||||
func (h *RestoreCollectionHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *RestoreCollectionHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract collection ID from the URL
|
||||
collectionIDStr := r.PathValue("id")
|
||||
if collectionIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Collection ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
collectionID, err := gocql.ParseUUID(collectionIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid collection ID format",
|
||||
zap.String("collection_id", collectionIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Invalid collection ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
// Create request DTO
|
||||
dtoReq := &svc_collection.RestoreCollectionRequestDTO{
|
||||
ID: collectionID,
|
||||
}
|
||||
|
||||
resp, err := h.service.Execute(ctx, dtoReq)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Encode response
|
||||
if resp != nil {
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
h.logger.Error("failed to encode response",
|
||||
zap.Any("error", err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := errors.New("no result")
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/collection/share_collection.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/validation"
|
||||
)
|
||||
|
||||
type ShareCollectionHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_collection.ShareCollectionService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewShareCollectionHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.ShareCollectionService,
|
||||
middleware middleware.Middleware,
|
||||
) *ShareCollectionHTTPHandler {
|
||||
logger = logger.Named("ShareCollectionHTTPHandler")
|
||||
return &ShareCollectionHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*ShareCollectionHTTPHandler) Pattern() string {
|
||||
return "POST /api/v1/collections/{id}/share"
|
||||
}
|
||||
|
||||
func (h *ShareCollectionHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *ShareCollectionHTTPHandler) unmarshalRequest(
|
||||
ctx context.Context,
|
||||
r *http.Request,
|
||||
collectionID gocql.UUID,
|
||||
) (*svc_collection.ShareCollectionRequestDTO, error) {
|
||||
// Initialize our structure which will store the parsed request data
|
||||
var requestData svc_collection.ShareCollectionRequestDTO
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
var rawJSON bytes.Buffer
|
||||
teeReader := io.TeeReader(r.Body, &rawJSON) // TeeReader allows you to read the JSON and capture it
|
||||
|
||||
// Read the JSON string and convert it into our golang struct
|
||||
err := json.NewDecoder(teeReader).Decode(&requestData)
|
||||
if err != nil {
|
||||
h.logger.Error("JSON decoding error",
|
||||
zap.Any("err", err),
|
||||
zap.String("raw_json", rawJSON.String()),
|
||||
)
|
||||
return nil, httperror.NewForSingleField(http.StatusBadRequest, "non_field_error", "payload structure is wrong")
|
||||
}
|
||||
|
||||
// Log the decoded request for debugging (PII masked for security)
|
||||
h.logger.Debug("decoded share collection request",
|
||||
zap.String("collection_id_from_url", collectionID.String()),
|
||||
zap.String("collection_id_from_body", requestData.CollectionID.String()),
|
||||
zap.String("recipient_id", requestData.RecipientID.String()),
|
||||
zap.String("recipient_email", validation.MaskEmail(requestData.RecipientEmail)),
|
||||
zap.String("permission_level", requestData.PermissionLevel),
|
||||
zap.Int("encrypted_key_length", len(requestData.EncryptedCollectionKey)),
|
||||
zap.Bool("share_with_descendants", requestData.ShareWithDescendants))
|
||||
|
||||
// CRITICAL: Check if encrypted collection key is present in the request
|
||||
if len(requestData.EncryptedCollectionKey) == 0 {
|
||||
h.logger.Error("FRONTEND BUG: encrypted_collection_key is missing from request",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.String("recipient_id", requestData.RecipientID.String()),
|
||||
zap.String("recipient_email", validation.MaskEmail(requestData.RecipientEmail)))
|
||||
// Log raw JSON at debug level only to avoid PII exposure in production logs
|
||||
h.logger.Debug("raw request body for debugging",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.String("raw_json", rawJSON.String()))
|
||||
} else {
|
||||
h.logger.Debug("encrypted_collection_key found in request",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.String("recipient_id", requestData.RecipientID.String()),
|
||||
zap.Int("encrypted_key_length", len(requestData.EncryptedCollectionKey)))
|
||||
}
|
||||
|
||||
// Set the collection ID from the URL parameter
|
||||
requestData.CollectionID = collectionID
|
||||
|
||||
return &requestData, nil
|
||||
}
|
||||
|
||||
func (h *ShareCollectionHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract collection ID from URL parameters
|
||||
collectionIDStr := r.PathValue("id")
|
||||
if collectionIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Collection ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
collectionID, err := gocql.ParseUUID(collectionIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid collection ID format",
|
||||
zap.String("collection_id", collectionIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Invalid collection ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("processing share collection request",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.String("method", r.Method),
|
||||
zap.String("content_type", r.Header.Get("Content-Type")))
|
||||
|
||||
req, err := h.unmarshalRequest(ctx, r, collectionID)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Call service
|
||||
resp, err := h.service.Execute(ctx, req)
|
||||
if err != nil {
|
||||
h.logger.Error("share collection service failed",
|
||||
zap.String("collection_id", collectionID.String()),
|
||||
zap.String("recipient_id", req.RecipientID.String()),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Encode response
|
||||
if resp != nil {
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
h.logger.Error("failed to encode response",
|
||||
zap.Any("error", err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := errors.New("no result")
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/collection/softdelete.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type SoftDeleteCollectionHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_collection.SoftDeleteCollectionService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewSoftDeleteCollectionHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.SoftDeleteCollectionService,
|
||||
middleware middleware.Middleware,
|
||||
) *SoftDeleteCollectionHTTPHandler {
|
||||
logger = logger.Named("SoftDeleteCollectionHTTPHandler")
|
||||
return &SoftDeleteCollectionHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*SoftDeleteCollectionHTTPHandler) Pattern() string {
|
||||
return "DELETE /api/v1/collections/{id}"
|
||||
}
|
||||
|
||||
func (h *SoftDeleteCollectionHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *SoftDeleteCollectionHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract collection ID from the URL
|
||||
collectionIDStr := r.PathValue("id")
|
||||
if collectionIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Collection ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
collectionID, err := gocql.ParseUUID(collectionIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid collection ID format",
|
||||
zap.String("collection_id", collectionIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Invalid collection ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
// Create request DTO
|
||||
dtoReq := &svc_collection.SoftDeleteCollectionRequestDTO{
|
||||
ID: collectionID,
|
||||
}
|
||||
|
||||
resp, err := h.service.Execute(ctx, dtoReq)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Encode response
|
||||
if resp != nil {
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
h.logger.Error("failed to encode response",
|
||||
zap.Any("error", err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := errors.New("no result")
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/collection/sync.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config/constants"
|
||||
dom_sync "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type CollectionSyncHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_collection.GetCollectionSyncDataService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewCollectionSyncHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.GetCollectionSyncDataService,
|
||||
middleware middleware.Middleware,
|
||||
) *CollectionSyncHTTPHandler {
|
||||
logger = logger.Named("CollectionSyncHTTPHandler")
|
||||
return &CollectionSyncHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*CollectionSyncHTTPHandler) Pattern() string {
|
||||
return "POST /api/v1/collections/sync"
|
||||
}
|
||||
|
||||
func (h *CollectionSyncHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *CollectionSyncHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Get user ID from context
|
||||
userID, ok := ctx.Value(constants.SessionUserID).(gocql.UUID)
|
||||
if !ok {
|
||||
h.logger.Error("Failed getting user ID from context")
|
||||
httperror.RespondWithError(w, r, httperror.NewForInternalServerErrorWithSingleField("message", "Authentication context error"))
|
||||
return
|
||||
}
|
||||
|
||||
// Parse query parameters
|
||||
queryParams := r.URL.Query()
|
||||
|
||||
// Parse limit parameter (default: 1000, max: 5000)
|
||||
limit := int64(1000)
|
||||
if limitStr := queryParams.Get("limit"); limitStr != "" {
|
||||
if parsedLimit, err := strconv.ParseInt(limitStr, 10, 64); err == nil {
|
||||
if parsedLimit > 0 && parsedLimit <= 5000 {
|
||||
limit = parsedLimit
|
||||
} else {
|
||||
h.logger.Warn("Invalid limit parameter, using default",
|
||||
zap.String("limit", limitStr),
|
||||
zap.Int64("default", limit))
|
||||
}
|
||||
} else {
|
||||
h.logger.Warn("Failed to parse limit parameter, using default",
|
||||
zap.String("limit", limitStr),
|
||||
zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// Parse cursor parameter
|
||||
var cursor *dom_sync.CollectionSyncCursor
|
||||
if cursorStr := queryParams.Get("cursor"); cursorStr != "" {
|
||||
var parsedCursor dom_sync.CollectionSyncCursor
|
||||
if err := json.Unmarshal([]byte(cursorStr), &parsedCursor); err != nil {
|
||||
h.logger.Error("Failed to parse cursor parameter",
|
||||
zap.String("cursor", cursorStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("cursor", "Invalid cursor format"))
|
||||
return
|
||||
}
|
||||
cursor = &parsedCursor
|
||||
}
|
||||
|
||||
h.logger.Debug("Processing collection sync request",
|
||||
zap.Any("user_id", userID),
|
||||
zap.Int64("limit", limit),
|
||||
zap.Any("cursor", cursor))
|
||||
|
||||
// Call service to get sync data
|
||||
response, err := h.service.Execute(ctx, userID, cursor, limit, "all")
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get collection sync data",
|
||||
zap.Any("user_id", userID),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Encode and return response
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
h.logger.Error("Failed to encode collection sync response",
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("Successfully served collection sync data",
|
||||
zap.Any("user_id", userID),
|
||||
zap.Int("collections_count", len(response.Collections)),
|
||||
zap.Bool("has_more", response.HasMore))
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/collection/update.go
|
||||
package collection
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/gocql/gocql"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/config"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
svc_collection "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/collection"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type UpdateCollectionHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_collection.UpdateCollectionService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewUpdateCollectionHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_collection.UpdateCollectionService,
|
||||
middleware middleware.Middleware,
|
||||
) *UpdateCollectionHTTPHandler {
|
||||
logger = logger.Named("UpdateCollectionHTTPHandler")
|
||||
return &UpdateCollectionHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*UpdateCollectionHTTPHandler) Pattern() string {
|
||||
return "PUT /api/v1/collections/{id}"
|
||||
}
|
||||
|
||||
func (h *UpdateCollectionHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *UpdateCollectionHTTPHandler) unmarshalRequest(
|
||||
ctx context.Context,
|
||||
r *http.Request,
|
||||
collectionID gocql.UUID,
|
||||
) (*svc_collection.UpdateCollectionRequestDTO, error) {
|
||||
// Initialize our structure which will store the parsed request data
|
||||
var requestData svc_collection.UpdateCollectionRequestDTO
|
||||
|
||||
defer r.Body.Close()
|
||||
|
||||
var rawJSON bytes.Buffer
|
||||
teeReader := io.TeeReader(r.Body, &rawJSON) // TeeReader allows you to read the JSON and capture it
|
||||
|
||||
// Read the JSON string and convert it into our golang struct
|
||||
err := json.NewDecoder(teeReader).Decode(&requestData)
|
||||
if err != nil {
|
||||
h.logger.Error("decoding error",
|
||||
zap.Any("err", err),
|
||||
zap.String("json", rawJSON.String()),
|
||||
)
|
||||
return nil, httperror.NewForSingleField(http.StatusBadRequest, "non_field_error", "payload structure is wrong")
|
||||
}
|
||||
|
||||
// Set the collection ID from the URL parameter
|
||||
requestData.ID = collectionID
|
||||
|
||||
return &requestData, nil
|
||||
}
|
||||
|
||||
func (h *UpdateCollectionHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract collection ID from the URL path parameter
|
||||
// This assumes the router is net/http (Go 1.22+) and the pattern was registered like "PUT /path/{id}"
|
||||
collectionIDStr := r.PathValue("id")
|
||||
if collectionIDStr == "" {
|
||||
h.logger.Warn("collection_id not found in path parameters or is empty",
|
||||
zap.String("path", r.URL.Path),
|
||||
zap.String("method", r.Method),
|
||||
)
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Collection ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
collectionID, err := gocql.ParseUUID(collectionIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid collection ID format",
|
||||
zap.String("collection_id", collectionIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("collection_id", "Invalid collection ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
req, err := h.unmarshalRequest(ctx, r, collectionID)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Call service
|
||||
resp, err := h.service.Execute(ctx, req)
|
||||
if err != nil {
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Encode response
|
||||
if resp != nil {
|
||||
if err := json.NewEncoder(w).Encode(resp); err != nil {
|
||||
h.logger.Error("failed to encode response",
|
||||
zap.Any("error", err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
err := errors.New("transaction completed with no result") // Clarified error message
|
||||
h.logger.Error("transaction completed with no result", zap.Any("request_payload", req))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue