package forms import ( "errors" "librapi/services" "mime/multipart" "net/http" "strconv" "strings" "github.com/rs/zerolog/log" ) const MaxFileSize = 200 // in MB var ( ErrInvalidUsername = errors.New("username must not be empty") ErrInvalidPassword = errors.New("password must not be empty") ErrInvalidCredentials = errors.New("bad credentials") 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 FormFieldType interface { int | string | StrList | UploadFile } type FormField[T FormFieldType] struct { Name string Value T Err string } type UploadFile struct { File multipart.File Header *multipart.FileHeader } func (uf *UploadFile) GetFilename() string { return uf.Header.Filename } func (uf *UploadFile) CheckSize() error { if uf.Header.Size > (MaxFileSize << 20) { return ErrFileMaxSizeReached } return nil } type UploadForm struct { Name FormField[string] Description FormField[string] Editor FormField[string] Authors FormField[StrList] Year FormField[int] Keywords FormField[StrList] File FormField[UploadFile] Error string Method string } func UploadFormFromRequest(r *http.Request) UploadForm { uf := NewUploadForm() name := r.FormValue(uf.Name.Name) if name == "" { uf.Name.Err = ErrInvalidName.Error() } uf.Name.Value = name uf.Description.Value = r.FormValue(uf.Description.Name) editor := r.FormValue(uf.Editor.Name) if editor == "" { uf.Editor.Err = ErrInvalidEditor.Error() } uf.Editor.Value = editor if a := r.FormValue(uf.Authors.Name); a != "" { uf.Authors.Value = strings.Split(a, ",") } else { uf.Authors.Err = ErrInvalidAuthors.Error() } year, errParse := strconv.Atoi(r.FormValue(uf.Year.Name)) if errParse != nil { log.Err(errParse).Msg("unable to parse date") uf.Year.Err = ErrInvalidYear.Error() } uf.Year.Value = year if kw := r.FormValue(uf.Keywords.Name); kw != "" { uf.Keywords.Value = strings.Split(kw, ",") } file, fileh, err := r.FormFile(uf.File.Name) if err != nil { log.Err(err).Msg("unable to get file from form") uf.File.Err = ErrFileOpen.Error() return uf } uf.File.Value = UploadFile{ File: file, Header: fileh, } if err := uf.File.Value.CheckSize(); err != nil { uf.File.Err = err.Error() } return uf } func NewUploadForm() UploadForm { return UploadForm{ Name: FormField[string]{ Name: "name", }, Description: FormField[string]{ Name: "description", }, Editor: FormField[string]{ Name: "editor", }, Authors: FormField[StrList]{ Name: "authors", }, Year: FormField[int]{ Name: "year", }, Keywords: FormField[StrList]{ Name: "keywords", }, File: FormField[UploadFile]{ Name: "file", }, Method: http.MethodPost, } } func (uf *UploadForm) HasErrors() bool { return uf.Name.Err != "" || uf.Authors.Err != "" || uf.Editor.Err != "" || uf.Year.Err != "" || uf.Keywords.Err != "" || uf.File.Err != "" } func (uf *UploadForm) IsSuccess() bool { return uf.Method == http.MethodPost && uf.Error == "" && !uf.HasErrors() } func (uf *UploadForm) IntoMetadata() *services.BookMetadata { bm := &services.BookMetadata{ Name: uf.Name.Value, Editor: uf.Editor.Value, Authors: uf.Authors.Value, Year: uint16(uf.Year.Value), Keywords: nil, } if desc := uf.Description.Value; desc != "" { bm.Description = &desc } if keywords := uf.Keywords.Value; len(keywords) > 0 { bm.Keywords = keywords } return bm } type LoginForm struct { Username FormField[string] Password FormField[string] Error error Method string } func NewLoginForm() LoginForm { return LoginForm{ Username: FormField[string]{ Name: "username", }, Password: FormField[string]{ Name: "password", }, Method: http.MethodPost, } } func LoginFormFromRequest(r *http.Request) LoginForm { lf := NewLoginForm() username := r.FormValue(lf.Username.Name) if username == "" { lf.Username.Err = ErrInvalidUsername.Error() } lf.Username.Value = username password := r.FormValue(lf.Password.Name) if password == "" { lf.Password.Err = ErrInvalidPassword.Error() } lf.Password.Value = password return lf } func (lf *LoginForm) HasErrors() bool { return lf.Username.Err != "" || lf.Password.Err != "" } func (lf *LoginForm) IsSuccess() bool { return lf.Method == http.MethodPost && lf.Error != nil && !lf.HasErrors() } type SearchForm struct { Search FormField[string] Error error Method string Results []services.BookMetadata } func SearchFormFromRequest(r *http.Request) SearchForm { sf := NewSearchForm() sf.Search.Value = r.FormValue(sf.Search.Name) return sf } func NewSearchForm() SearchForm { return SearchForm{ Search: FormField[string]{ Name: "search", }, Method: http.MethodPost, } }