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,97 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/file/archive.go
|
||||
package file
|
||||
|
||||
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_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/file"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type ArchiveFileHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_file.ArchiveFileService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewArchiveFileHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.ArchiveFileService,
|
||||
middleware middleware.Middleware,
|
||||
) *ArchiveFileHTTPHandler {
|
||||
logger = logger.Named("ArchiveFileHTTPHandler")
|
||||
return &ArchiveFileHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*ArchiveFileHTTPHandler) Pattern() string {
|
||||
return "PUT /api/v1/file/{id}/archive"
|
||||
}
|
||||
|
||||
func (h *ArchiveFileHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *ArchiveFileHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract file ID from the URL
|
||||
fileIDStr := r.PathValue("id")
|
||||
if fileIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "File ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
fileID, err := gocql.ParseUUID(fileIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid file ID format",
|
||||
zap.String("file_id", fileIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "Invalid file ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
// Create request DTO
|
||||
dtoReq := &svc_file.ArchiveFileRequestDTO{
|
||||
FileID: fileID,
|
||||
}
|
||||
|
||||
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.String("file_id", fileIDStr),
|
||||
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/file/complete_file_upload.go
|
||||
package file
|
||||
|
||||
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_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/file"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type CompleteFileUploadHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_file.CompleteFileUploadService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewCompleteFileUploadHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.CompleteFileUploadService,
|
||||
middleware middleware.Middleware,
|
||||
) *CompleteFileUploadHTTPHandler {
|
||||
logger = logger.Named("CompleteFileUploadHTTPHandler")
|
||||
return &CompleteFileUploadHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*CompleteFileUploadHTTPHandler) Pattern() string {
|
||||
return "POST /api/v1/file/{id}/complete"
|
||||
}
|
||||
|
||||
func (h *CompleteFileUploadHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *CompleteFileUploadHTTPHandler) unmarshalRequest(
|
||||
ctx context.Context,
|
||||
r *http.Request,
|
||||
fileID gocql.UUID,
|
||||
) (*svc_file.CompleteFileUploadRequestDTO, error) {
|
||||
// Initialize our structure which will store the parsed request data
|
||||
var requestData svc_file.CompleteFileUploadRequestDTO
|
||||
|
||||
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 file ID from the URL parameter
|
||||
requestData.FileID = fileID
|
||||
|
||||
return &requestData, nil
|
||||
}
|
||||
|
||||
func (h *CompleteFileUploadHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract file ID from URL parameters
|
||||
fileIDStr := r.PathValue("id")
|
||||
if fileIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "File ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
fileID, err := gocql.ParseUUID(fileIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid file ID format",
|
||||
zap.String("file_id", fileIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "Invalid file ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
req, err := h.unmarshalRequest(ctx, r, fileID)
|
||||
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,108 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/file/create_pending_file.go
|
||||
package file
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"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_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/file"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type CreatePendingFileHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_file.CreatePendingFileService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewCreatePendingFileHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.CreatePendingFileService,
|
||||
middleware middleware.Middleware,
|
||||
) *CreatePendingFileHTTPHandler {
|
||||
logger = logger.Named("CreatePendingFileHTTPHandler")
|
||||
return &CreatePendingFileHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*CreatePendingFileHTTPHandler) Pattern() string {
|
||||
return "POST /api/v1/files/pending"
|
||||
}
|
||||
|
||||
func (h *CreatePendingFileHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *CreatePendingFileHTTPHandler) unmarshalRequest(
|
||||
ctx context.Context,
|
||||
r *http.Request,
|
||||
) (*svc_file.CreatePendingFileRequestDTO, error) {
|
||||
// Initialize our structure which will store the parsed request data
|
||||
var requestData svc_file.CreatePendingFileRequestDTO
|
||||
|
||||
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))
|
||||
// Log raw JSON at debug level only to avoid PII exposure in production logs
|
||||
h.logger.Debug("raw request body for debugging",
|
||||
zap.String("json", rawJSON.String()))
|
||||
return nil, httperror.NewForSingleField(http.StatusBadRequest, "non_field_error", "payload structure is wrong")
|
||||
}
|
||||
|
||||
return &requestData, nil
|
||||
}
|
||||
|
||||
func (h *CreatePendingFileHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := h.unmarshalRequest(ctx, 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
|
||||
}
|
||||
}
|
||||
91
cloud/maplefile-backend/internal/interface/http/file/get.go
Normal file
91
cloud/maplefile-backend/internal/interface/http/file/get.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/file/get.go
|
||||
package file
|
||||
|
||||
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_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/file"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type GetFileHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_file.GetFileService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewGetFileHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.GetFileService,
|
||||
middleware middleware.Middleware,
|
||||
) *GetFileHTTPHandler {
|
||||
logger = logger.Named("GetFileHTTPHandler")
|
||||
return &GetFileHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*GetFileHTTPHandler) Pattern() string {
|
||||
return "GET /api/v1/file/{id}"
|
||||
}
|
||||
|
||||
func (h *GetFileHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *GetFileHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract file ID from URL parameters
|
||||
fileIDStr := r.PathValue("id")
|
||||
if fileIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "File ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
fileID, err := gocql.ParseUUID(fileIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid file ID format",
|
||||
zap.String("file_id", fileIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "Invalid file ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
resp, err := h.service.Execute(ctx, fileID)
|
||||
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,134 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/file/get_presigned_download_url.go
|
||||
package file
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"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_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/file"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type GetPresignedDownloadURLHTTPRequestDTO struct {
|
||||
URLDurationStr string `json:"url_duration,omitempty"` // Optional, duration as string of nanoseconds, defaults to 1 hour
|
||||
}
|
||||
|
||||
type GetPresignedDownloadURLHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_file.GetPresignedDownloadURLService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewGetPresignedDownloadURLHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.GetPresignedDownloadURLService,
|
||||
middleware middleware.Middleware,
|
||||
) *GetPresignedDownloadURLHTTPHandler {
|
||||
logger = logger.Named("GetPresignedDownloadURLHTTPHandler")
|
||||
return &GetPresignedDownloadURLHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*GetPresignedDownloadURLHTTPHandler) Pattern() string {
|
||||
return "GET /api/v1/file/{id}/download-url"
|
||||
}
|
||||
|
||||
func (h *GetPresignedDownloadURLHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *GetPresignedDownloadURLHTTPHandler) unmarshalRequest(
|
||||
ctx context.Context,
|
||||
r *http.Request,
|
||||
fileID gocql.UUID,
|
||||
) (*svc_file.GetPresignedDownloadURLRequestDTO, error) {
|
||||
// For GET requests, read from query parameters instead of body
|
||||
urlDurationStr := r.URL.Query().Get("url_duration")
|
||||
|
||||
// Set default URL duration if not provided (1 hour in nanoseconds)
|
||||
var urlDuration time.Duration
|
||||
if urlDurationStr == "" {
|
||||
urlDuration = 1 * time.Hour
|
||||
} else {
|
||||
// Parse the string to int64 (nanoseconds)
|
||||
durationNanos, err := strconv.ParseInt(urlDurationStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, httperror.NewForSingleField(http.StatusBadRequest, "url_duration", "Invalid duration format")
|
||||
}
|
||||
urlDuration = time.Duration(durationNanos)
|
||||
}
|
||||
|
||||
// Convert to service DTO
|
||||
serviceRequest := &svc_file.GetPresignedDownloadURLRequestDTO{
|
||||
FileID: fileID,
|
||||
URLDuration: urlDuration,
|
||||
}
|
||||
|
||||
return serviceRequest, nil
|
||||
}
|
||||
|
||||
func (h *GetPresignedDownloadURLHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract file ID from URL parameters
|
||||
fileIDStr := r.PathValue("id")
|
||||
if fileIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "File ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
fileID, err := gocql.ParseUUID(fileIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid file ID format",
|
||||
zap.String("file_id", fileIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "Invalid file ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
req, err := h.unmarshalRequest(ctx, r, fileID)
|
||||
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,152 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/file/get_presigned_upload_url.go
|
||||
package file
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"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_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/file"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type GetPresignedUploadURLHTTPRequestDTO struct {
|
||||
URLDurationStr string `json:"url_duration,omitempty"` // Optional, duration as string of nanoseconds, defaults to 1 hour
|
||||
}
|
||||
|
||||
type GetPresignedUploadURLHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_file.GetPresignedUploadURLService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewGetPresignedUploadURLHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.GetPresignedUploadURLService,
|
||||
middleware middleware.Middleware,
|
||||
) *GetPresignedUploadURLHTTPHandler {
|
||||
logger = logger.Named("GetPresignedUploadURLHTTPHandler")
|
||||
return &GetPresignedUploadURLHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*GetPresignedUploadURLHTTPHandler) Pattern() string {
|
||||
return "GET /api/v1/file/{id}/upload-url"
|
||||
}
|
||||
|
||||
func (h *GetPresignedUploadURLHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *GetPresignedUploadURLHTTPHandler) unmarshalRequest(
|
||||
ctx context.Context,
|
||||
r *http.Request,
|
||||
fileID gocql.UUID,
|
||||
) (*svc_file.GetPresignedUploadURLRequestDTO, error) {
|
||||
// Initialize our structure which will store the parsed request data
|
||||
var httpRequestData GetPresignedUploadURLHTTPRequestDTO
|
||||
|
||||
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(&httpRequestData)
|
||||
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 default URL duration if not provided (1 hour in nanoseconds)
|
||||
var urlDuration time.Duration
|
||||
if httpRequestData.URLDurationStr == "" {
|
||||
urlDuration = 1 * time.Hour
|
||||
} else {
|
||||
// Parse the string to int64 (nanoseconds)
|
||||
durationNanos, err := strconv.ParseInt(httpRequestData.URLDurationStr, 10, 64)
|
||||
if err != nil {
|
||||
return nil, httperror.NewForSingleField(http.StatusBadRequest, "url_duration", "Invalid duration format")
|
||||
}
|
||||
urlDuration = time.Duration(durationNanos)
|
||||
}
|
||||
|
||||
// Convert to service DTO
|
||||
serviceRequest := &svc_file.GetPresignedUploadURLRequestDTO{
|
||||
FileID: fileID,
|
||||
URLDuration: urlDuration,
|
||||
}
|
||||
|
||||
return serviceRequest, nil
|
||||
}
|
||||
|
||||
func (h *GetPresignedUploadURLHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract file ID from URL parameters
|
||||
fileIDStr := r.PathValue("id")
|
||||
if fileIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "File ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
fileID, err := gocql.ParseUUID(fileIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid file ID format",
|
||||
zap.String("file_id", fileIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "Invalid file ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
req, err := h.unmarshalRequest(ctx, r, fileID)
|
||||
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("no result")
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/file/list_by_collection.go
|
||||
package file
|
||||
|
||||
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_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/file"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type ListFilesByCollectionHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_file.ListFilesByCollectionService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewListFilesByCollectionHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.ListFilesByCollectionService,
|
||||
middleware middleware.Middleware,
|
||||
) *ListFilesByCollectionHTTPHandler {
|
||||
logger = logger.Named("ListFilesByCollectionHTTPHandler")
|
||||
return &ListFilesByCollectionHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*ListFilesByCollectionHTTPHandler) Pattern() string {
|
||||
return "GET /api/v1/collection/{collection_id}/files"
|
||||
}
|
||||
|
||||
func (h *ListFilesByCollectionHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *ListFilesByCollectionHTTPHandler) 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("collection_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
|
||||
req := &svc_file.ListFilesByCollectionRequestDTO{
|
||||
CollectionID: collectionID,
|
||||
}
|
||||
|
||||
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,106 @@
|
|||
// cloud/maplefile-backend/internal/maplefile/interface/http/file/list_recent_files.go
|
||||
package file
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"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"
|
||||
file_service "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/file"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type ListRecentFilesHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
listRecentFilesService file_service.ListRecentFilesService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewListRecentFilesHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
listRecentFilesService file_service.ListRecentFilesService,
|
||||
middleware middleware.Middleware,
|
||||
) *ListRecentFilesHTTPHandler {
|
||||
logger = logger.Named("ListRecentFilesHTTPHandler")
|
||||
return &ListRecentFilesHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
listRecentFilesService: listRecentFilesService,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*ListRecentFilesHTTPHandler) Pattern() string {
|
||||
return "GET /api/v1/files/recent"
|
||||
}
|
||||
|
||||
func (h *ListRecentFilesHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *ListRecentFilesHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Parse query parameters
|
||||
queryParams := r.URL.Query()
|
||||
|
||||
// Parse limit parameter (default: 30, max: 100)
|
||||
limit := int64(30)
|
||||
if limitStr := queryParams.Get("limit"); limitStr != "" {
|
||||
if parsedLimit, err := strconv.ParseInt(limitStr, 10, 64); err == nil {
|
||||
if parsedLimit > 0 && parsedLimit <= 100 {
|
||||
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 *string
|
||||
if cursorStr := queryParams.Get("cursor"); cursorStr != "" {
|
||||
cursor = &cursorStr
|
||||
}
|
||||
|
||||
h.logger.Debug("Processing recent files request",
|
||||
zap.Int64("limit", limit),
|
||||
zap.Any("cursor", cursor))
|
||||
|
||||
// Call service to get recent files
|
||||
response, err := h.listRecentFilesService.Execute(ctx, cursor, limit)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get recent files",
|
||||
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 recent files response",
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("Successfully served recent files",
|
||||
zap.Int("files_count", len(response.Files)),
|
||||
zap.Bool("has_more", response.HasMore),
|
||||
zap.Any("next_cursor", response.NextCursor))
|
||||
}
|
||||
|
|
@ -0,0 +1,146 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/file/list_sync.go
|
||||
package file
|
||||
|
||||
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_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/domain/file"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/interface/http/middleware"
|
||||
file_service "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/file"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type FileSyncHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
fileSyncService file_service.ListFileSyncDataService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewFileSyncHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
fileSyncService file_service.ListFileSyncDataService,
|
||||
middleware middleware.Middleware,
|
||||
) *FileSyncHTTPHandler {
|
||||
logger = logger.Named("FileSyncHTTPHandler")
|
||||
return &FileSyncHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
fileSyncService: fileSyncService,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*FileSyncHTTPHandler) Pattern() string {
|
||||
return "POST /api/v1/files/sync"
|
||||
}
|
||||
|
||||
func (h *FileSyncHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *FileSyncHTTPHandler) 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: 5000, max: 10000)
|
||||
limit := int64(5000)
|
||||
if limitStr := queryParams.Get("limit"); limitStr != "" {
|
||||
if parsedLimit, err := strconv.ParseInt(limitStr, 10, 64); err == nil {
|
||||
if parsedLimit > 0 && parsedLimit <= 10000 {
|
||||
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_file.FileSyncCursor
|
||||
if cursorStr := queryParams.Get("cursor"); cursorStr != "" {
|
||||
var parsedCursor dom_file.FileSyncCursor
|
||||
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 file sync request",
|
||||
zap.Any("user_id", userID),
|
||||
zap.Int64("limit", limit),
|
||||
zap.Any("cursor", cursor))
|
||||
|
||||
// Call service to get sync data
|
||||
response, err := h.fileSyncService.Execute(ctx, cursor, limit)
|
||||
if err != nil {
|
||||
h.logger.Error("Failed to get file sync data",
|
||||
zap.Any("user_id", userID),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the response contains all fields including EncryptedFileSizeInBytes before encoding
|
||||
h.logger.Debug("File sync response validation",
|
||||
zap.Any("user_id", userID),
|
||||
zap.Int("files_count", len(response.Files)))
|
||||
|
||||
for i, item := range response.Files {
|
||||
h.logger.Debug("File sync response item",
|
||||
zap.Int("index", i),
|
||||
zap.String("file_id", item.ID.String()),
|
||||
zap.String("collection_id", item.CollectionID.String()),
|
||||
zap.Uint64("version", item.Version),
|
||||
zap.Time("modified_at", item.ModifiedAt),
|
||||
zap.String("state", item.State),
|
||||
zap.Uint64("tombstone_version", item.TombstoneVersion),
|
||||
zap.Time("tombstone_expiry", item.TombstoneExpiry),
|
||||
zap.Int64("encrypted_file_size_in_bytes", item.EncryptedFileSizeInBytes))
|
||||
}
|
||||
|
||||
// Encode and return response
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
h.logger.Error("Failed to encode file sync response",
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("Successfully served file sync data",
|
||||
zap.Any("user_id", userID),
|
||||
zap.Int("files_count", len(response.Files)),
|
||||
zap.Bool("has_more", response.HasMore),
|
||||
zap.Any("next_cursor", response.NextCursor))
|
||||
}
|
||||
136
cloud/maplefile-backend/internal/interface/http/file/provider.go
Normal file
136
cloud/maplefile-backend/internal/interface/http/file/provider.go
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
package file
|
||||
|
||||
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_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/file"
|
||||
)
|
||||
|
||||
// Wire providers for file HTTP handlers
|
||||
|
||||
func ProvideCreatePendingFileHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.CreatePendingFileService,
|
||||
mw middleware.Middleware,
|
||||
) *CreatePendingFileHTTPHandler {
|
||||
return NewCreatePendingFileHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideGetPresignedUploadURLHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.GetPresignedUploadURLService,
|
||||
mw middleware.Middleware,
|
||||
) *GetPresignedUploadURLHTTPHandler {
|
||||
return NewGetPresignedUploadURLHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideCompleteFileUploadHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.CompleteFileUploadService,
|
||||
mw middleware.Middleware,
|
||||
) *CompleteFileUploadHTTPHandler {
|
||||
return NewCompleteFileUploadHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideGetFileHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.GetFileService,
|
||||
mw middleware.Middleware,
|
||||
) *GetFileHTTPHandler {
|
||||
return NewGetFileHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideGetPresignedDownloadURLHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.GetPresignedDownloadURLService,
|
||||
mw middleware.Middleware,
|
||||
) *GetPresignedDownloadURLHTTPHandler {
|
||||
return NewGetPresignedDownloadURLHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideListFilesByCollectionHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.ListFilesByCollectionService,
|
||||
mw middleware.Middleware,
|
||||
) *ListFilesByCollectionHTTPHandler {
|
||||
return NewListFilesByCollectionHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideListRecentFilesHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.ListRecentFilesService,
|
||||
mw middleware.Middleware,
|
||||
) *ListRecentFilesHTTPHandler {
|
||||
return NewListRecentFilesHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideUpdateFileHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.UpdateFileService,
|
||||
mw middleware.Middleware,
|
||||
) *UpdateFileHTTPHandler {
|
||||
return NewUpdateFileHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideSoftDeleteFileHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.SoftDeleteFileService,
|
||||
mw middleware.Middleware,
|
||||
) *SoftDeleteFileHTTPHandler {
|
||||
return NewSoftDeleteFileHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideArchiveFileHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.ArchiveFileService,
|
||||
mw middleware.Middleware,
|
||||
) *ArchiveFileHTTPHandler {
|
||||
return NewArchiveFileHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideRestoreFileHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.RestoreFileService,
|
||||
mw middleware.Middleware,
|
||||
) *RestoreFileHTTPHandler {
|
||||
return NewRestoreFileHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideDeleteMultipleFilesHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.DeleteMultipleFilesService,
|
||||
mw middleware.Middleware,
|
||||
) *DeleteMultipleFilesHTTPHandler {
|
||||
return NewDeleteMultipleFilesHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideFileSyncHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.ListFileSyncDataService,
|
||||
mw middleware.Middleware,
|
||||
) *FileSyncHTTPHandler {
|
||||
return NewFileSyncHTTPHandler(cfg, logger, service, mw)
|
||||
}
|
||||
|
||||
func ProvideReportDownloadCompletedHTTPHandler(
|
||||
cfg *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
mw middleware.Middleware,
|
||||
) *ReportDownloadCompletedHTTPHandler {
|
||||
return NewReportDownloadCompletedHTTPHandler(cfg, logger, mw)
|
||||
}
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/file/report_download_completed.go
|
||||
package file
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"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"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type ReportDownloadCompletedHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewReportDownloadCompletedHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
middleware middleware.Middleware,
|
||||
) *ReportDownloadCompletedHTTPHandler {
|
||||
logger = logger.Named("ReportDownloadCompletedHTTPHandler")
|
||||
return &ReportDownloadCompletedHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*ReportDownloadCompletedHTTPHandler) Pattern() string {
|
||||
return "POST /api/v1/file/{id}/download-completed"
|
||||
}
|
||||
|
||||
func (h *ReportDownloadCompletedHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *ReportDownloadCompletedHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
// Extract file ID from the URL
|
||||
fileIDStr := r.PathValue("id")
|
||||
if fileIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "File ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Validate UUID format
|
||||
_, err := gocql.ParseUUID(fileIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid file ID format",
|
||||
zap.String("file_id", fileIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "Invalid file ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
// Log the download completion (analytics/telemetry)
|
||||
h.logger.Debug("download completed reported",
|
||||
zap.String("file_id", fileIDStr))
|
||||
|
||||
// Return success response
|
||||
response := map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "Download completion recorded",
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
h.logger.Error("failed to encode response",
|
||||
zap.String("file_id", fileIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/file/restore.go
|
||||
package file
|
||||
|
||||
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_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/file"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type RestoreFileHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_file.RestoreFileService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewRestoreFileHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.RestoreFileService,
|
||||
middleware middleware.Middleware,
|
||||
) *RestoreFileHTTPHandler {
|
||||
logger = logger.Named("RestoreFileHTTPHandler")
|
||||
return &RestoreFileHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*RestoreFileHTTPHandler) Pattern() string {
|
||||
return "PUT /api/v1/file/{id}/restore"
|
||||
}
|
||||
|
||||
func (h *RestoreFileHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *RestoreFileHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract file ID from the URL
|
||||
fileIDStr := r.PathValue("id")
|
||||
if fileIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "File ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
fileID, err := gocql.ParseUUID(fileIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid file ID format",
|
||||
zap.String("file_id", fileIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "Invalid file ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
// Create request DTO
|
||||
dtoReq := &svc_file.RestoreFileRequestDTO{
|
||||
FileID: fileID,
|
||||
}
|
||||
|
||||
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.String("file_id", fileIDStr),
|
||||
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,97 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/file/softdelete.go
|
||||
package file
|
||||
|
||||
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_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/file"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type SoftDeleteFileHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_file.SoftDeleteFileService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewSoftDeleteFileHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.SoftDeleteFileService,
|
||||
middleware middleware.Middleware,
|
||||
) *SoftDeleteFileHTTPHandler {
|
||||
logger = logger.Named("SoftDeleteFileHTTPHandler")
|
||||
return &SoftDeleteFileHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*SoftDeleteFileHTTPHandler) Pattern() string {
|
||||
return "DELETE /api/v1/file/{id}"
|
||||
}
|
||||
|
||||
func (h *SoftDeleteFileHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *SoftDeleteFileHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract file ID from the URL
|
||||
fileIDStr := r.PathValue("id")
|
||||
if fileIDStr == "" {
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "File ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
fileID, err := gocql.ParseUUID(fileIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid file ID format",
|
||||
zap.String("file_id", fileIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "Invalid file ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
// Create request DTO
|
||||
dtoReq := &svc_file.SoftDeleteFileRequestDTO{
|
||||
FileID: fileID,
|
||||
}
|
||||
|
||||
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.String("file_id", fileIDStr),
|
||||
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,107 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/file/delete_multiple.go
|
||||
package file
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"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_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/file"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type DeleteMultipleFilesHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_file.DeleteMultipleFilesService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewDeleteMultipleFilesHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.DeleteMultipleFilesService,
|
||||
middleware middleware.Middleware,
|
||||
) *DeleteMultipleFilesHTTPHandler {
|
||||
logger = logger.Named("DeleteMultipleFilesHTTPHandler")
|
||||
return &DeleteMultipleFilesHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*DeleteMultipleFilesHTTPHandler) Pattern() string {
|
||||
return "POST /api/v1/files/delete-multiple"
|
||||
}
|
||||
|
||||
func (h *DeleteMultipleFilesHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *DeleteMultipleFilesHTTPHandler) unmarshalRequest(
|
||||
ctx context.Context,
|
||||
r *http.Request,
|
||||
) (*svc_file.DeleteMultipleFilesRequestDTO, error) {
|
||||
// Initialize our structure which will store the parsed request data
|
||||
var requestData svc_file.DeleteMultipleFilesRequestDTO
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
return &requestData, nil
|
||||
}
|
||||
|
||||
func (h *DeleteMultipleFilesHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
req, err := h.unmarshalRequest(ctx, 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
|
||||
}
|
||||
}
|
||||
135
cloud/maplefile-backend/internal/interface/http/file/update.go
Normal file
135
cloud/maplefile-backend/internal/interface/http/file/update.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// monorepo/cloud/backend/internal/maplefile/interface/http/file/update.go
|
||||
package file
|
||||
|
||||
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_file "codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/internal/service/file"
|
||||
"codeberg.org/mapleopentech/monorepo/cloud/maplefile-backend/pkg/httperror"
|
||||
)
|
||||
|
||||
type UpdateFileHTTPHandler struct {
|
||||
config *config.Configuration
|
||||
logger *zap.Logger
|
||||
service svc_file.UpdateFileService
|
||||
middleware middleware.Middleware
|
||||
}
|
||||
|
||||
func NewUpdateFileHTTPHandler(
|
||||
config *config.Configuration,
|
||||
logger *zap.Logger,
|
||||
service svc_file.UpdateFileService,
|
||||
middleware middleware.Middleware,
|
||||
) *UpdateFileHTTPHandler {
|
||||
logger = logger.Named("UpdateFileHTTPHandler")
|
||||
return &UpdateFileHTTPHandler{
|
||||
config: config,
|
||||
logger: logger,
|
||||
service: service,
|
||||
middleware: middleware,
|
||||
}
|
||||
}
|
||||
|
||||
func (*UpdateFileHTTPHandler) Pattern() string {
|
||||
return "PUT /api/v1/file/{id}"
|
||||
}
|
||||
|
||||
func (h *UpdateFileHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
// Apply middleware before handling the request
|
||||
h.middleware.Attach(h.Execute)(w, req)
|
||||
}
|
||||
|
||||
func (h *UpdateFileHTTPHandler) unmarshalRequest(
|
||||
ctx context.Context,
|
||||
r *http.Request,
|
||||
fileID gocql.UUID,
|
||||
) (*svc_file.UpdateFileRequestDTO, error) {
|
||||
// Initialize our structure which will store the parsed request data
|
||||
var requestData svc_file.UpdateFileRequestDTO
|
||||
|
||||
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))
|
||||
// Log raw JSON at debug level only to avoid PII exposure in production logs
|
||||
h.logger.Debug("raw request body for debugging",
|
||||
zap.String("json", rawJSON.String()))
|
||||
return nil, httperror.NewForSingleField(http.StatusBadRequest, "non_field_error", "payload structure is wrong")
|
||||
}
|
||||
|
||||
// Set the file ID from the URL parameter
|
||||
requestData.ID = fileID
|
||||
|
||||
return &requestData, nil
|
||||
}
|
||||
|
||||
func (h *UpdateFileHTTPHandler) Execute(w http.ResponseWriter, r *http.Request) {
|
||||
// Set response content type
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
// Extract file ID from the URL path parameter
|
||||
fileIDStr := r.PathValue("id")
|
||||
if fileIDStr == "" {
|
||||
h.logger.Warn("file_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("file_id", "File ID is required"))
|
||||
return
|
||||
}
|
||||
|
||||
// Convert string ID to ObjectID
|
||||
fileID, err := gocql.ParseUUID(fileIDStr)
|
||||
if err != nil {
|
||||
h.logger.Error("invalid file ID format",
|
||||
zap.String("file_id", fileIDStr),
|
||||
zap.Error(err))
|
||||
httperror.RespondWithError(w, r, httperror.NewForBadRequestWithSingleField("file_id", "Invalid file ID format"))
|
||||
return
|
||||
}
|
||||
|
||||
req, err := h.unmarshalRequest(ctx, r, fileID)
|
||||
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("transaction completed with no result")
|
||||
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