package upload import ( "bytes" "errors" "fmt" "io" "mime/multipart" "net/http" "os" "strconv" "strings" "github.com/rs/zerolog/log" "librapi/services" "librapi/templates" ) const MaxFileSize = 200 // in MB var ( ErrInvalidName = errors.New("book name must not be empty") ErrInvalidEditor = errors.New("book editor must not be empty") ErrInvalidYear = errors.New("invalid year, unable to parse") ErrInvalidAuthors = errors.New("must at least contains one author") ErrFileMaxSizeReached = errors.New("max file size reached, must be <= 200MB") ErrFileOpen = errors.New("unable to open file from form") ) type StrList = []string type BookFile struct { file multipart.File Header *multipart.FileHeader } func (bf *BookFile) GetFilename() string { return bf.Header.Filename } func (bf *BookFile) CheckSize() error { if bf.Header.Size > (MaxFileSize << 20) { return ErrFileMaxSizeReached } return nil } type BookFieldType interface { int | string | StrList | BookFile } type BookField[T BookFieldType] struct { Name string Value T Err string } type BookForm struct { Name BookField[string] Editor BookField[string] Authors BookField[StrList] Year BookField[int] Keywords BookField[StrList] File BookField[BookFile] Error string Method string } func NewBookForm() BookForm { return BookForm{ Name: BookField[string]{ Name: "name", }, Editor: BookField[string]{ Name: "editor", }, Authors: BookField[StrList]{ Name: "authors", }, Year: BookField[int]{ Name: "year", }, Keywords: BookField[StrList]{ Name: "keywords", }, File: BookField[BookFile]{ Name: "file", }, Method: http.MethodPost, } } func (bf *BookForm) HasErrors() bool { return bf.Name.Err != "" || bf.Authors.Err != "" || bf.Editor.Err != "" || bf.Year.Err != "" || bf.Keywords.Err != "" || bf.File.Err != "" } func (bf *BookForm) IsSuccess() bool { return bf.Method == http.MethodPost && bf.Error == "" && !bf.HasErrors() } func Handler(a services.IAuthenticate) func(http.ResponseWriter, *http.Request) { return func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodGet: getUploadFile(w, r) case http.MethodPost: postUploadFile(w, r, a) default: http.Error(w, "method not allowed", http.StatusMethodNotAllowed) } } } func extractBookForm(r *http.Request) BookForm { bf := NewBookForm() name := r.FormValue(bf.Name.Name) if name == "" { bf.Name.Err = ErrInvalidName.Error() } bf.Name.Value = name editor := r.FormValue(bf.Editor.Name) if editor == "" { bf.Editor.Err = ErrInvalidEditor.Error() } bf.Editor.Value = editor if a := r.FormValue(bf.Authors.Name); a != "" { bf.Authors.Value = strings.Split(a, ",") } else { bf.Authors.Err = ErrInvalidAuthors.Error() } year, errParse := strconv.Atoi(r.FormValue(bf.Year.Name)) if errParse != nil { log.Err(errParse).Msg("unable to parse date") bf.Year.Err = ErrInvalidYear.Error() } bf.Year.Value = year if kw := r.FormValue(bf.Keywords.Name); kw != "" { bf.Keywords.Value = strings.Split(kw, ",") } file, fileh, err := r.FormFile(bf.File.Name) if err != nil { log.Err(err).Msg("unable to get file from form") bf.File.Err = ErrFileOpen.Error() return bf } bf.File.Value = BookFile{ file: file, Header: fileh, } if err := bf.File.Value.CheckSize(); err != nil { bf.File.Err = err.Error() } return bf } func postUploadFile(w http.ResponseWriter, r *http.Request, a services.IAuthenticate) { uploadForm := templates.GetUploadForm() if !a.IsLogged(r) { buf := bytes.NewBufferString("") if err := uploadForm.Execute(buf, &BookForm{Error: services.ErrUnauthorized.Error()}); err != nil { log.Err(err).Msg("unable to generate template") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) } w.WriteHeader(http.StatusUnauthorized) fmt.Fprint(w, buf.String()) return } bf := extractBookForm(r) buf := bytes.NewBufferString("") if err := uploadForm.Execute(buf, &bf); err != nil { log.Err(err).Msg("unable to generate template") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) } if bf.HasErrors() { w.WriteHeader(http.StatusBadRequest) fmt.Fprint(w, buf.String()) return } filename := bf.File.Value.GetFilename() log.Info().Str("filename", filename).Msg("file is uploading...") dst, err := os.Create(filename) if err != nil { if err := uploadForm.Execute(buf, &BookForm{Error: "unexpected error occurred while creating file"}); err != nil { log.Err(err).Msg("unable to generate template") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) } w.WriteHeader(http.StatusInternalServerError) fmt.Fprint(w, buf.String()) return } defer dst.Close() if _, err := io.Copy(dst, bf.File.Value.file); err != nil { if err := uploadForm.Execute(buf, &BookForm{Error: "unexpected error occurred while uploading file"}); err != nil { log.Err(err).Msg("unable to generate template") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) return } w.WriteHeader(http.StatusInternalServerError) fmt.Fprint(w, buf.String()) return } buf.Reset() if err := uploadForm.Execute(buf, &BookForm{Method: http.MethodPost}); err != nil { log.Err(err).Msg("unable to generate template") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) return } fmt.Fprint(w, buf.String()) } func getUploadFile(w http.ResponseWriter, _ *http.Request) { uploadForm := templates.GetUploadForm() buf := bytes.NewBufferString("") if err := uploadForm.Execute(buf, &BookForm{}); err != nil { log.Err(err).Msg("unable to generate template") http.Error(w, "unexpected error occurred", http.StatusInternalServerError) return } fmt.Fprint(w, buf) }