github.com/moov-io/imagecashletter@v0.10.1/internal/files/v2/files.go (about)

     1  package v2
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	"os"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/google/uuid"
    13  	"github.com/gorilla/mux"
    14  	"github.com/moov-io/base/log"
    15  	"github.com/moov-io/imagecashletter"
    16  	"github.com/moov-io/imagecashletter/internal/metrics"
    17  	"github.com/moov-io/imagecashletter/internal/responder"
    18  	"github.com/moov-io/imagecashletter/internal/storage"
    19  )
    20  
    21  var (
    22  	maxReaderBufferSize = func() int {
    23  		v, exists := os.LookupEnv("READER_BUFFER_SIZE")
    24  		if exists {
    25  			n, _ := strconv.ParseInt(v, 10, 32)
    26  			return int(n)
    27  		}
    28  		return bufio.MaxScanTokenSize
    29  	}()
    30  )
    31  
    32  type Controller struct {
    33  	logger log.Logger
    34  	repo   storage.ICLFileRepository
    35  }
    36  
    37  func NewController(logger log.Logger, fileRepo storage.ICLFileRepository) Controller {
    38  	return Controller{
    39  		logger: logger,
    40  		repo:   fileRepo,
    41  	}
    42  }
    43  
    44  func (c Controller) AddRoutes(router *mux.Router) {
    45  	v2Routes := router.PathPrefix("/v2").Subrouter()
    46  
    47  	v2Routes.
    48  		Path("/files").
    49  		Methods(http.MethodPost).
    50  		HandlerFunc(c.createFile)
    51  
    52  }
    53  
    54  func (c Controller) createFile(w http.ResponseWriter, r *http.Request) {
    55  	w = metrics.WrapResponseWriter(c.logger, w, r)
    56  	respond := responder.NewResponder(c.logger, w, r)
    57  
    58  	var created *imagecashletter.File
    59  	var err error
    60  
    61  	contentType := r.Header.Get("Content-Type")
    62  	switch {
    63  	case strings.Contains(contentType, "application/json"):
    64  		created, err = c.fileFromJSON(r)
    65  	case strings.Contains(contentType, "multipart/form-data"):
    66  		created, err = c.fileFromForm(r)
    67  	default:
    68  		err = fmt.Errorf("missing or unsupported Content-Type: %s", contentType)
    69  	}
    70  
    71  	if err != nil {
    72  		c.logger.Error().LogErrorf("creating file: %v", err)
    73  		respond.Error(http.StatusBadRequest, err)
    74  		return
    75  	}
    76  
    77  	if err = c.repo.SaveFile(created); err != nil {
    78  		c.logger.Error().LogErrorf("saving created file: %v", err)
    79  		respond.Error(http.StatusInternalServerError, err)
    80  		return
    81  	}
    82  
    83  	// TODO: Update to the v2 API endpoint once the GET file endpoint is implemented
    84  	location := fmt.Sprintf("%s://%s/files/%s", r.URL.Scheme, r.URL.Host, created.ID)
    85  	respond = respond.WithLocation(location)
    86  	if expectingFile(r) {
    87  		respond.File(http.StatusCreated, *created, fmt.Sprintf("%s.x9", created.ID))
    88  		return
    89  	}
    90  
    91  	respond.JSON(http.StatusCreated, created)
    92  }
    93  
    94  func expectingFile(r *http.Request) bool {
    95  	mimeType := r.Header.Get("Accept")
    96  	return mimeType == "application/octet-stream" || mimeType == "text/plain"
    97  }
    98  
    99  func (c Controller) fileFromJSON(r *http.Request) (*imagecashletter.File, error) {
   100  	contents, err := io.ReadAll(r.Body)
   101  	if err != nil {
   102  		return nil, fmt.Errorf("reading request body: %w", err)
   103  	}
   104  
   105  	file, err := imagecashletter.FileFromJSON(contents)
   106  	if err != nil {
   107  		return nil, fmt.Errorf("parsing request body: %w", err)
   108  	}
   109  	file.ID = uuid.NewString()
   110  
   111  	return file, nil
   112  }
   113  
   114  func (c Controller) fileFromForm(r *http.Request) (*imagecashletter.File, error) {
   115  	if err := r.ParseMultipartForm(int64(maxReaderBufferSize)); err != nil {
   116  		return nil, fmt.Errorf("parsing multipart form: %v", err)
   117  	}
   118  
   119  	formFile, hdr, err := r.FormFile("file")
   120  	if err != nil {
   121  		return nil, fmt.Errorf("reading form file: %w", err)
   122  	}
   123  
   124  	opts := []imagecashletter.ReaderOption{
   125  		imagecashletter.ReadVariableLineLengthOption(),
   126  		imagecashletter.BufferSizeOption(maxReaderBufferSize),
   127  	}
   128  
   129  	// Industry standard encoding is EBCDIC, so unless plain/text was
   130  	// explicitly requested, default to EBCDIC.
   131  	contentType := hdr.Header.Get("Content-Type")
   132  	if contentType != "text/plain" {
   133  		opts = append(opts, imagecashletter.ReadEbcdicEncodingOption())
   134  	}
   135  
   136  	file, err := imagecashletter.NewReader(formFile, opts...).Read()
   137  	if err != nil {
   138  		return nil, fmt.Errorf("parsing file: %w", err)
   139  	}
   140  	file.ID = uuid.NewString()
   141  
   142  	return &file, nil
   143  }