rework template load/generation + add generic error on template error
This commit is contained in:
parent
40e08154f6
commit
708db35214
247
forms/forms.go
Normal file
247
forms/forms.go
Normal file
@ -0,0 +1,247 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"librapi/services"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
@ -27,7 +28,7 @@ func getDownload(w http.ResponseWriter, r *http.Request, bs services.IStore) {
|
||||
downloadFiles, ok := queryParams["file"]
|
||||
if !ok {
|
||||
log.Error().Msg("file query param does not exist")
|
||||
http.Error(w, "file does not exists", http.StatusBadRequest)
|
||||
http.Error(w, "file query param does not exist", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@ -40,6 +41,15 @@ func getDownload(w http.ResponseWriter, r *http.Request, bs services.IStore) {
|
||||
filename := downloadFiles[0]
|
||||
filePath := filepath.Join(bs.GetStoreDir(), filename)
|
||||
|
||||
if _, err := os.Stat(filePath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
http.Error(w, "file does not exist", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.ServeFile(w, r, filePath)
|
||||
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
|
||||
|
||||
@ -1,39 +1,18 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"librapi/services"
|
||||
"librapi/templates"
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"librapi/forms"
|
||||
"librapi/services"
|
||||
"librapi/templates"
|
||||
)
|
||||
|
||||
const URL = "/"
|
||||
|
||||
type SearchField struct {
|
||||
Name string
|
||||
Value string
|
||||
Err string
|
||||
}
|
||||
|
||||
type SearchForm struct {
|
||||
Search SearchField
|
||||
Error error
|
||||
Method string
|
||||
Results []services.BookMetadata
|
||||
}
|
||||
|
||||
func NewSearchForm() SearchForm {
|
||||
return SearchForm{
|
||||
Search: SearchField{
|
||||
Name: "search",
|
||||
},
|
||||
Method: http.MethodPost,
|
||||
}
|
||||
}
|
||||
|
||||
func Handler(bs services.IStore) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
@ -48,52 +27,38 @@ func Handler(bs services.IStore) func(http.ResponseWriter, *http.Request) {
|
||||
}
|
||||
|
||||
func getHome(w http.ResponseWriter, _ *http.Request) {
|
||||
home := templates.GetHome()
|
||||
|
||||
buf := bytes.NewBufferString("")
|
||||
if err := home.Execute(buf, &SearchForm{}); err != nil {
|
||||
log.Err(err).Msg("unable to generate template")
|
||||
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
|
||||
tmpl, err := templates.ExecuteHomeTmpl(&forms.SearchForm{}, w)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to generate home template")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprint(w, buf)
|
||||
}
|
||||
|
||||
func extractSearchForm(r *http.Request) SearchForm {
|
||||
sf := NewSearchForm()
|
||||
|
||||
sf.Search.Value = r.FormValue(sf.Search.Name)
|
||||
|
||||
return sf
|
||||
fmt.Fprint(w, tmpl)
|
||||
}
|
||||
|
||||
func postHome(w http.ResponseWriter, r *http.Request, bs services.IStore) {
|
||||
home := templates.GetHome()
|
||||
buf := bytes.NewBufferString("")
|
||||
|
||||
sf := extractSearchForm(r)
|
||||
sf := forms.SearchFormFromRequest(r)
|
||||
bms, err := bs.Search(sf.Search.Value)
|
||||
if err != nil {
|
||||
sf.Error = err
|
||||
if err := home.Execute(buf, sf); err != nil {
|
||||
log.Err(err).Msg("unable to generate template")
|
||||
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
|
||||
tmpl, err := templates.ExecuteHomeTmpl(&sf, w)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to generate home template")
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprint(w, buf.String())
|
||||
fmt.Fprint(w, tmpl)
|
||||
return
|
||||
}
|
||||
|
||||
sf.Results = bms
|
||||
|
||||
if err := home.Execute(buf, sf); err != nil {
|
||||
log.Err(err).Msg("unable to generate template")
|
||||
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
|
||||
tmpl, err := templates.ExecuteHomeTmpl(&sf, w)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to generate home template")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprint(w, buf)
|
||||
fmt.Fprint(w, tmpl)
|
||||
}
|
||||
|
||||
@ -1,58 +1,19 @@
|
||||
package login
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"librapi/forms"
|
||||
"librapi/services"
|
||||
"librapi/templates"
|
||||
)
|
||||
|
||||
const URL = "/login"
|
||||
|
||||
var (
|
||||
ErrInvalidUsername = errors.New("username must not be empty")
|
||||
ErrInvalidPassword = errors.New("password must not be empty")
|
||||
ErrInvalidCredentials = errors.New("bad credentials")
|
||||
)
|
||||
|
||||
type LoginField struct {
|
||||
Name string
|
||||
Value string
|
||||
Err string
|
||||
}
|
||||
|
||||
type LoginForm struct {
|
||||
Username LoginField
|
||||
Password LoginField
|
||||
Error error
|
||||
Method string
|
||||
}
|
||||
|
||||
func NewLoginForm() LoginForm {
|
||||
return LoginForm{
|
||||
Username: LoginField{
|
||||
Name: "username",
|
||||
},
|
||||
Password: LoginField{
|
||||
Name: "password",
|
||||
},
|
||||
Method: http.MethodPost,
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
func Handler(a services.IAuthenticate) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
@ -66,49 +27,27 @@ func Handler(a services.IAuthenticate) func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
}
|
||||
|
||||
func extractLoginForm(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 postLogin(w http.ResponseWriter, r *http.Request, a services.IAuthenticate) {
|
||||
loginForm := templates.GetLoginForm()
|
||||
loginSuccess := templates.GetLoginSuccess()
|
||||
|
||||
if a.IsLogged(r) {
|
||||
buf := bytes.NewBufferString("")
|
||||
if err := loginSuccess.Execute(buf, nil); err != nil {
|
||||
log.Err(err).Msg("unable to generate template")
|
||||
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
|
||||
tmpl, err := templates.ExecuteLoginSuccessTmpl(w)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to generate login success template")
|
||||
return
|
||||
}
|
||||
fmt.Fprint(w, buf)
|
||||
fmt.Fprint(w, tmpl)
|
||||
return
|
||||
}
|
||||
|
||||
lf := extractLoginForm(r)
|
||||
lf := forms.LoginFormFromRequest(r)
|
||||
if lf.HasErrors() {
|
||||
buf := bytes.NewBufferString("")
|
||||
if err := loginForm.Execute(buf, &lf); err != nil {
|
||||
log.Err(err).Msg("unable to generate template")
|
||||
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
|
||||
tmpl, err := templates.ExecuteLoginFormTmpl(&lf, w)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to generate login form template")
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprint(w, buf.String())
|
||||
fmt.Fprint(w, tmpl)
|
||||
return
|
||||
}
|
||||
|
||||
@ -117,17 +56,16 @@ func postLogin(w http.ResponseWriter, r *http.Request, a services.IAuthenticate)
|
||||
if errors.Is(err, services.ErrUnauthorized) {
|
||||
log.Warn().Str("username", lf.Username.Value).Msg("bad credentials")
|
||||
|
||||
lf.Error = ErrInvalidCredentials
|
||||
lf.Error = forms.ErrInvalidCredentials
|
||||
|
||||
buf := bytes.NewBufferString("")
|
||||
if err := loginForm.Execute(buf, &lf); err != nil {
|
||||
log.Err(err).Msg("unable to generate template")
|
||||
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
|
||||
tmpl, err := templates.ExecuteLoginFormTmpl(&lf, w)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to generate login form template")
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprint(w, buf.String())
|
||||
fmt.Fprint(w, tmpl)
|
||||
return
|
||||
}
|
||||
|
||||
@ -138,39 +76,32 @@ func postLogin(w http.ResponseWriter, r *http.Request, a services.IAuthenticate)
|
||||
cookie := session.GenerateCookie(a.IsSecure())
|
||||
http.SetCookie(w, cookie)
|
||||
|
||||
buf := bytes.NewBufferString("")
|
||||
if err := loginSuccess.Execute(buf, nil); err != nil {
|
||||
log.Err(err).Msg("unable to generate template")
|
||||
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
|
||||
tmpl, err := templates.ExecuteLoginSuccessTmpl(w)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to generate login success template")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprint(w, buf)
|
||||
fmt.Fprint(w, tmpl)
|
||||
}
|
||||
|
||||
func getLogin(w http.ResponseWriter, r *http.Request, a services.IAuthenticate) {
|
||||
loginForm := templates.GetLoginForm()
|
||||
|
||||
if a.IsLogged(r) {
|
||||
loginSuccess := templates.GetLoginSuccess()
|
||||
|
||||
buf := bytes.NewBufferString("")
|
||||
if err := loginSuccess.Execute(buf, nil); err != nil {
|
||||
log.Err(err).Msg("unable to generate template")
|
||||
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
|
||||
tmpl, err := templates.ExecuteLoginSuccessTmpl(w)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to generate login success template")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprint(w, buf)
|
||||
fmt.Fprint(w, tmpl)
|
||||
return
|
||||
}
|
||||
|
||||
buf := bytes.NewBufferString("")
|
||||
if err := loginForm.Execute(buf, &LoginForm{}); err != nil {
|
||||
log.Err(err).Msg("unable to generate template")
|
||||
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
|
||||
tmpl, err := templates.ExecuteLoginFormTmpl(&forms.LoginForm{}, w)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to generate login form template")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprint(w, buf)
|
||||
fmt.Fprint(w, tmpl)
|
||||
}
|
||||
|
||||
@ -1,129 +1,20 @@
|
||||
package upload
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"librapi/forms"
|
||||
"librapi/services"
|
||||
"librapi/templates"
|
||||
)
|
||||
|
||||
const (
|
||||
URL = "/upload"
|
||||
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]
|
||||
Description 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",
|
||||
},
|
||||
Description: BookField[string]{
|
||||
Name: "description",
|
||||
},
|
||||
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 (bf *BookForm) IntoMetadata() *services.BookMetadata {
|
||||
bm := &services.BookMetadata{
|
||||
Name: bf.Name.Value,
|
||||
Editor: bf.Editor.Value,
|
||||
Authors: bf.Authors.Value,
|
||||
Year: uint16(bf.Year.Value),
|
||||
Keywords: nil,
|
||||
}
|
||||
|
||||
if desc := bf.Description.Value; desc != "" {
|
||||
bm.Description = &desc
|
||||
}
|
||||
|
||||
if keywords := bf.Keywords.Value; len(keywords) > 0 {
|
||||
bm.Keywords = keywords
|
||||
}
|
||||
|
||||
return bm
|
||||
}
|
||||
|
||||
func Handler(a services.IAuthenticate, s services.IStore) func(http.ResponseWriter, *http.Request) {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
@ -137,123 +28,62 @@ func Handler(a services.IAuthenticate, s services.IStore) func(http.ResponseWrit
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
bf.Description.Value = r.FormValue(bf.Description.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, s services.IStore) {
|
||||
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)
|
||||
tmpl, err := templates.ExecuteUploadFormTmpl(&forms.UploadForm{Error: services.ErrUnauthorized.Error()}, w)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to generate upload template")
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
fmt.Fprint(w, buf.String())
|
||||
fmt.Fprint(w, tmpl)
|
||||
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)
|
||||
uf := forms.UploadFormFromRequest(r)
|
||||
tmpl, err := templates.ExecuteUploadFormTmpl(&uf, w)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to generate upload template")
|
||||
return
|
||||
}
|
||||
|
||||
if bf.HasErrors() {
|
||||
if uf.HasErrors() {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
fmt.Fprint(w, buf.String())
|
||||
fmt.Fprint(w, tmpl)
|
||||
return
|
||||
}
|
||||
|
||||
filename := bf.File.Value.GetFilename()
|
||||
filename := uf.File.Value.GetFilename()
|
||||
log.Info().Str("filename", filename).Msg("file is uploading...")
|
||||
|
||||
if err := s.Save(bf.IntoMetadata(), bf.File.Value.file); err != nil {
|
||||
if err := uploadForm.Execute(buf, &BookForm{Error: err.Error()}); err != nil {
|
||||
log.Err(err).Msg("unable to generate template")
|
||||
http.Error(w, "unexpected error occurred", http.StatusInternalServerError)
|
||||
if err := s.Save(uf.IntoMetadata(), uf.File.Value.File); err != nil {
|
||||
tmpl, err := templates.ExecuteUploadFormTmpl(&uf, w)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to generate upload template")
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprint(w, buf.String())
|
||||
fmt.Fprint(w, tmpl)
|
||||
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)
|
||||
tmpl, err = templates.ExecuteUploadFormTmpl(&forms.UploadForm{Method: http.MethodPost}, w)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to generate upload template")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprint(w, buf.String())
|
||||
fmt.Fprint(w, tmpl)
|
||||
}
|
||||
|
||||
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)
|
||||
tmpl, err := templates.ExecuteUploadFormTmpl(&forms.UploadForm{}, w)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to generate upload template")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprint(w, buf)
|
||||
fmt.Fprint(w, tmpl)
|
||||
}
|
||||
|
||||
@ -29,14 +29,41 @@ type IStore interface {
|
||||
|
||||
var _ IStore = (*BookStore)(nil)
|
||||
|
||||
//go:embed sql/create_books_table.sql
|
||||
var sqlCreateTable string
|
||||
|
||||
//go:embed sql/insert_book.sql
|
||||
var sqlInsertBook string
|
||||
|
||||
//go:embed sql/search_books.sql
|
||||
var sqlSearchBooks string
|
||||
const (
|
||||
sqlSearchBooks = `
|
||||
select
|
||||
b.*
|
||||
from
|
||||
books b
|
||||
where
|
||||
lower(b.name) like lower(?)
|
||||
or lower(b.description) like lower(?)
|
||||
or lower(b.editor) like lower(?)
|
||||
or lower(b.year) like lower(?)
|
||||
union
|
||||
select
|
||||
b2.*
|
||||
from books b2, json_each(b2.authors)
|
||||
where lower(json_each.value) like lower(?)
|
||||
union
|
||||
select
|
||||
b3.*
|
||||
from books b3, json_each(b3.keywords)
|
||||
where lower(json_each.value) like lower(?)
|
||||
`
|
||||
sqlInsertBook = "insert into books(name, description, editor, authors, year, keywords, path) values (?,?,?,?,?,?,?)"
|
||||
sqlCreateBookTable = `
|
||||
create table if not exists books (
|
||||
name text primary key,
|
||||
description text,
|
||||
editor text not null,
|
||||
authors jsonb not null,
|
||||
year int not null,
|
||||
keywords jsonb,
|
||||
path text not null
|
||||
)
|
||||
`
|
||||
)
|
||||
|
||||
type BookMetadata struct {
|
||||
Name string
|
||||
@ -45,7 +72,7 @@ type BookMetadata struct {
|
||||
Authors []string
|
||||
Year uint16
|
||||
Keywords []string
|
||||
Path string
|
||||
path string
|
||||
}
|
||||
|
||||
func (bm *BookMetadata) getAuthors() (string, error) {
|
||||
@ -98,10 +125,14 @@ func (bm *BookMetadata) intoStmtValues() ([]any, error) {
|
||||
authors,
|
||||
bm.Year,
|
||||
keywords,
|
||||
bm.Path,
|
||||
bm.path,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (bm *BookMetadata) GetPath() string {
|
||||
return bm.path
|
||||
}
|
||||
|
||||
type BookStore struct {
|
||||
processing *sync.WaitGroup
|
||||
dir string
|
||||
@ -128,7 +159,7 @@ func (bs *BookStore) init(dir string) {
|
||||
log.Fatal().Err(err).Msg("unable initialize sqlite3 database")
|
||||
}
|
||||
|
||||
if _, err := db.Exec(sqlCreateTable); err != nil {
|
||||
if _, err := db.Exec(sqlCreateBookTable); err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to create books table")
|
||||
}
|
||||
|
||||
@ -179,9 +210,9 @@ func (bs *BookStore) Save(bm *BookMetadata, content io.ReadCloser) error {
|
||||
|
||||
defer content.Close()
|
||||
|
||||
bm.Path = filepath.Join(bs.dir, bm.getFormattedName())
|
||||
bm.path = filepath.Join(bs.dir, bm.getFormattedName())
|
||||
|
||||
dst, err := os.Create(bm.Path)
|
||||
dst, err := os.Create(bm.path)
|
||||
if err != nil {
|
||||
log.Err(err).Msg(ErrFileCreation.Error())
|
||||
return ErrFileCreation
|
||||
@ -220,13 +251,14 @@ func (bs *BookStore) Search(value string) ([]BookMetadata, error) {
|
||||
var bm BookMetadata
|
||||
var authors string
|
||||
var keyword *string
|
||||
if err := rows.Scan(&bm.Name, &bm.Description, &bm.Editor, &authors, &bm.Year, &keyword, &bm.Path); err != nil {
|
||||
if err := rows.Scan(&bm.Name, &bm.Description, &bm.Editor, &authors, &bm.Year, &keyword, &bm.path); err != nil {
|
||||
log.Err(err).Msg("unable to scan row")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var authorsSlice []string
|
||||
if err := json.Unmarshal([]byte(authors), &authorsSlice); err != nil {
|
||||
log.Err(err).Msg("unable to unmarshal authors into slice")
|
||||
return nil, err
|
||||
}
|
||||
bm.Authors = authorsSlice
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
create table if not exists books (
|
||||
name text primary key,
|
||||
description text,
|
||||
editor text not null,
|
||||
authors jsonb not null,
|
||||
year int not null,
|
||||
keywords jsonb,
|
||||
path text not null
|
||||
)
|
||||
@ -1,2 +0,0 @@
|
||||
insert into books(name, description, editor, authors, year, keywords, path)
|
||||
values (?,?,?,?,?,?,?)
|
||||
@ -1,20 +0,0 @@
|
||||
select
|
||||
b.*
|
||||
from
|
||||
books b
|
||||
where
|
||||
lower(b.name) like lower(?)
|
||||
or lower(b.description) like lower(?)
|
||||
or lower(b.editor) like lower(?)
|
||||
or lower(b.year) like lower(?)
|
||||
union
|
||||
select
|
||||
b2.*
|
||||
from books b2, json_each(b2.authors)
|
||||
where lower(json_each.value) like lower(?)
|
||||
union
|
||||
select
|
||||
b3.*
|
||||
from books b3, json_each(b3.keywords)
|
||||
where lower(json_each.value) like lower(?)
|
||||
|
||||
@ -28,6 +28,12 @@
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.col-item {
|
||||
margin: 10px;
|
||||
word-wrap: break-word;
|
||||
width: 200px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
|
||||
4
templates/error.html.tpl
Normal file
4
templates/error.html.tpl
Normal file
@ -0,0 +1,4 @@
|
||||
{{ define "content" }}
|
||||
<h1>Error</h1>
|
||||
<div>Unexpected error occurred, try again or contact the support.</div>
|
||||
{{ end }}
|
||||
@ -24,6 +24,7 @@
|
||||
{{ if ne (errStr .Error) "" }}
|
||||
<div class="error">{{.Error | errStr}}</div>
|
||||
{{ end }}
|
||||
{{ if .Results }}
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
@ -34,15 +35,31 @@
|
||||
</tr>
|
||||
{{range .Results}}
|
||||
<tr>
|
||||
<td><div style="margin: 10px; word-wrap: break-word; width: 300px;">{{.Name}}</div></td>
|
||||
<td><div style="margin: 10px; word-wrap: break-word; width: 300px;">{{.Description | noDesc}}</div></td>
|
||||
<td style="text-align: center;"><div style="margin: 10px;">{{.Editor}}</div></td>
|
||||
<td style="text-align: center; word-wrap: break-word; width: 300px;"><div style="margin: 10px;">{{.Authors | join }}</div></td>
|
||||
<td><div style="margin: 20px;">{{.Year}}</div></td>
|
||||
<td><div style="margin: 20px;"><a target="_blank" href="{{.Path | bookUrl}}">Download</a></div></td>
|
||||
<td>
|
||||
<div class="col-item">{{.Name}}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="col-item">{{.Description | noDesc}}</div>
|
||||
</td>
|
||||
<td style="text-align: center;">
|
||||
<div class="col-item">{{.Editor}}</div>
|
||||
</td>
|
||||
<td style="text-align: center">
|
||||
<div class="col-item">{{.Authors | join }}</div>
|
||||
</td>
|
||||
<td style="text-align: center">
|
||||
<div class="col-item">{{.Year}}</div>
|
||||
</td>
|
||||
<td style="text-align: center">
|
||||
<div class="col-item">
|
||||
<a target="_blank" href="{{.GetPath | bookUrl}}">Download</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</table>
|
||||
<div>{{len .Results}} results found</div>
|
||||
{{ end }}
|
||||
|
||||
</ul>
|
||||
{{ end }}
|
||||
@ -1,32 +1,46 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
"librapi/handlers/download"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed base.html.tpl
|
||||
var base string
|
||||
base string
|
||||
|
||||
//go:embed login/login_form.html.tpl
|
||||
var loginForm string
|
||||
loginForm string
|
||||
|
||||
//go:embed login/login_success.html.tpl
|
||||
var loginSuccess string
|
||||
loginSuccess string
|
||||
|
||||
//go:embed upload_form.html.tpl
|
||||
var uploadForm string
|
||||
uploadForm string
|
||||
|
||||
//go:embed home.html.tpl
|
||||
var home string
|
||||
home string
|
||||
|
||||
//go:embed error.html.tpl
|
||||
errorBase string
|
||||
|
||||
loginFormTmpl = loadLoginFormTmpl()
|
||||
loginSuccessTmpl = loadLoginSuccessTmpl()
|
||||
errTmpl = loadErrorTmpl()
|
||||
homeTmpl = loadHomeTmpl()
|
||||
uploadFormTmpl = loadUploadFormTmpl()
|
||||
)
|
||||
|
||||
var funcMap = template.FuncMap{
|
||||
"year": func(s int) string {
|
||||
@ -56,7 +70,7 @@ var funcMap = template.FuncMap{
|
||||
},
|
||||
"bookUrl": func(path string) string {
|
||||
_, filename := filepath.Split(path)
|
||||
return fmt.Sprintf("/download?file=%s", filename)
|
||||
return fmt.Sprintf("%s?file=%s", download.URL, filename)
|
||||
},
|
||||
"noDesc": func(desc *string) string {
|
||||
if desc == nil {
|
||||
@ -66,46 +80,20 @@ var funcMap = template.FuncMap{
|
||||
},
|
||||
}
|
||||
|
||||
var homeTmpl = sync.OnceValue[*template.Template](func() *template.Template {
|
||||
func loadErrorTmpl() *template.Template {
|
||||
baseTmpl, err := template.New("base").Parse(base)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to parse base tmpl")
|
||||
}
|
||||
|
||||
if _, err := baseTmpl.New("home").Funcs(funcMap).Parse(home); err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to parse home tmpl")
|
||||
if _, err := baseTmpl.New("error").Funcs(funcMap).Parse(errorBase); err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to parse error base tmpl")
|
||||
}
|
||||
|
||||
return baseTmpl
|
||||
})
|
||||
|
||||
var uploadFormTmpl = sync.OnceValue[*template.Template](func() *template.Template {
|
||||
baseTmpl, err := template.New("base").Parse(base)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to parse base tmpl")
|
||||
}
|
||||
|
||||
if _, err := baseTmpl.New("uploadForm").Funcs(funcMap).Parse(uploadForm); err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to parse upload tmpl")
|
||||
}
|
||||
|
||||
return baseTmpl
|
||||
})
|
||||
|
||||
var loginFormTmpl = sync.OnceValue[*template.Template](func() *template.Template {
|
||||
baseTmpl, err := template.New("base").Parse(base)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to parse base tmpl")
|
||||
}
|
||||
|
||||
if _, err := baseTmpl.New("loginForm").Funcs(funcMap).Parse(loginForm); err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to parse login tmpl")
|
||||
}
|
||||
|
||||
return baseTmpl
|
||||
})
|
||||
|
||||
var loginSuccessTmpl = sync.OnceValue[*template.Template](func() *template.Template {
|
||||
func loadLoginSuccessTmpl() *template.Template {
|
||||
baseTmpl, err := template.New("base").Parse(base)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to parse base tmpl")
|
||||
@ -116,20 +104,73 @@ var loginSuccessTmpl = sync.OnceValue[*template.Template](func() *template.Templ
|
||||
}
|
||||
|
||||
return baseTmpl
|
||||
})
|
||||
}
|
||||
|
||||
func loadLoginFormTmpl() *template.Template {
|
||||
baseTmpl, err := template.New("base").Parse(base)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to parse base tmpl")
|
||||
}
|
||||
|
||||
if _, err := baseTmpl.New("loginForm").Funcs(funcMap).Parse(loginForm); err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to parse login form tmpl")
|
||||
}
|
||||
|
||||
return baseTmpl
|
||||
}
|
||||
|
||||
func loadHomeTmpl() *template.Template {
|
||||
baseTmpl, err := template.New("base").Parse(base)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to parse base tmpl")
|
||||
}
|
||||
|
||||
if _, err := baseTmpl.New("home").Funcs(funcMap).Parse(home); err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to parse home tmpl")
|
||||
}
|
||||
|
||||
return baseTmpl
|
||||
}
|
||||
|
||||
func loadUploadFormTmpl() *template.Template {
|
||||
baseTmpl, err := template.New("base").Parse(base)
|
||||
if err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to parse base tmpl")
|
||||
}
|
||||
|
||||
if _, err := baseTmpl.New("uploadform").Funcs(funcMap).Parse(uploadForm); err != nil {
|
||||
log.Fatal().Err(err).Msg("unable to parse upload form tmpl")
|
||||
}
|
||||
|
||||
return baseTmpl
|
||||
}
|
||||
|
||||
func GetHome() *template.Template {
|
||||
return homeTmpl()
|
||||
return homeTmpl
|
||||
}
|
||||
|
||||
func GetLoginForm() *template.Template {
|
||||
return loginFormTmpl()
|
||||
func executeTmpl(tmpl *template.Template, form any, w http.ResponseWriter) (string, error) {
|
||||
buf := bytes.NewBufferString("")
|
||||
if err := tmpl.Execute(buf, form); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
fmt.Fprint(w, errTmpl)
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
func GetLoginSuccess() *template.Template {
|
||||
return loginSuccessTmpl()
|
||||
func ExecuteLoginSuccessTmpl(w http.ResponseWriter) (string, error) {
|
||||
return executeTmpl(loginSuccessTmpl, nil, w)
|
||||
}
|
||||
|
||||
func GetUploadForm() *template.Template {
|
||||
return uploadFormTmpl()
|
||||
func ExecuteLoginFormTmpl(form any, w http.ResponseWriter) (string, error) {
|
||||
return executeTmpl(loginFormTmpl, form, w)
|
||||
}
|
||||
|
||||
func ExecuteHomeTmpl(form any, w http.ResponseWriter) (string, error) {
|
||||
return executeTmpl(homeTmpl, form, w)
|
||||
}
|
||||
|
||||
func ExecuteUploadFormTmpl(form any, w http.ResponseWriter) (string, error) {
|
||||
return executeTmpl(uploadFormTmpl, form, w)
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user