add home handler + add templates folder
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Librapi</title>
|
||||
<style>
|
||||
.main-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
align-self: flex-start;
|
||||
margin: 10px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1>Librapi</h1>
|
||||
</header>
|
||||
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="/">Home</a></li>
|
||||
<li><a href="/login">Login</a></li>
|
||||
<li><a href="/upload">Upload</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
{{ block "content" . }}{{ end }}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© 2025 Librapi</p>
|
||||
</footer>
|
||||
</body>
|
||||
|
||||
<script type="text/javascript">
|
||||
{{ block "script" . }}{{ end }}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,3 @@
|
||||
{{ define "content" }}
|
||||
<h3>A simple API to store, search and download books.</h3>
|
||||
{{ end }}
|
||||
@@ -0,0 +1,33 @@
|
||||
{{ define "content" }}
|
||||
<h2>Login</h2>
|
||||
<form action="/login" method="post" enctype="multipart/form-data">
|
||||
<div class="main-container">
|
||||
<div class="form-item">
|
||||
<div class="form-container">
|
||||
<label>Username:</label>
|
||||
<input type="text" name="username" value="{{.Username.Value}}" />
|
||||
</div>
|
||||
{{ if .Username.Err }}
|
||||
<div class="error">{{.Username.Err}}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-container">
|
||||
<label>Password:</label>
|
||||
<input type="password" name="password" value="{{.Password.Value}}" />
|
||||
</div>
|
||||
{{ if .Password.Err }}
|
||||
<div class="error">{{.Password.Err}}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-container">
|
||||
<button id="submit" type="submit">Login</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{ if ne (errStr .Error) "" }}
|
||||
<div class="error">{{.Error | errStr}}</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,4 @@
|
||||
{{ define "content" }}
|
||||
<h1>Login</h1>
|
||||
<div>You're logged</div>
|
||||
{{ end }}
|
||||
@@ -0,0 +1,110 @@
|
||||
package templates
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"html/template"
|
||||
"mime/multipart"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
//go:embed base.html.tpl
|
||||
var base string
|
||||
|
||||
//go:embed login/login_form.html.tpl
|
||||
var loginForm string
|
||||
|
||||
//go:embed login/login_success.html.tpl
|
||||
var loginSuccess string
|
||||
|
||||
//go:embed upload_form.html.tpl
|
||||
var uploadForm string
|
||||
|
||||
//go:embed home.html.tpl
|
||||
var home string
|
||||
|
||||
var funcMap = template.FuncMap{
|
||||
"year": func(s int) string {
|
||||
if s == 0 {
|
||||
return ""
|
||||
}
|
||||
return strconv.Itoa(s)
|
||||
},
|
||||
"join": func(s []string) string {
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
} else {
|
||||
return strings.Join(s, ",")
|
||||
}
|
||||
},
|
||||
"filename": func(h *multipart.FileHeader) string {
|
||||
if h != nil {
|
||||
return h.Filename
|
||||
}
|
||||
return ""
|
||||
},
|
||||
"errStr": func(err error) string {
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
return ""
|
||||
},
|
||||
}
|
||||
|
||||
var GetHome = sync.OnceValue[*template.Template](func() *template.Template {
|
||||
baseTmpl, err := template.New("base").Parse(base)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to parse base tmpl")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if _, err := baseTmpl.New("home").Parse(home); err != nil {
|
||||
log.Err(err).Msg("unable to parse home tmpl")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return baseTmpl
|
||||
})
|
||||
|
||||
var GetUploadForm = sync.OnceValue[*template.Template](func() *template.Template {
|
||||
baseTmpl, err := template.New("base").Parse(base)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to parse base tmpl")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if _, err := baseTmpl.New("uploadForm").Funcs(funcMap).Parse(uploadForm); err != nil {
|
||||
log.Err(err).Msg("unable to parse upload tmpl")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return baseTmpl
|
||||
})
|
||||
|
||||
var GetLoginForm = sync.OnceValue[*template.Template](func() *template.Template {
|
||||
baseTmpl, err := template.New("base").Parse(base)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to parse base tmpl")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if _, err := baseTmpl.New("loginForm").Funcs(funcMap).Parse(loginForm); err != nil {
|
||||
log.Err(err).Msg("unable to parse login tmpl")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return baseTmpl
|
||||
})
|
||||
|
||||
var GetLoginSuccess = sync.OnceValue[*template.Template](func() *template.Template {
|
||||
tmpl, err := template.New("loginSuccess").Parse(loginSuccess)
|
||||
if err != nil {
|
||||
log.Err(err).Msg("unable to parse login success")
|
||||
os.Exit(1)
|
||||
}
|
||||
return tmpl
|
||||
})
|
||||
@@ -0,0 +1,75 @@
|
||||
{{define "content" }}
|
||||
<h2>Upload a book</h2>
|
||||
<form action="/upload" method="post" enctype="multipart/form-data">
|
||||
<div class="main-container">
|
||||
<div class="form-item">
|
||||
<div class="form-container">
|
||||
<label>Book name:</label>
|
||||
<input type="text" name="name" value="{{.Name.Value}}" />
|
||||
</div>
|
||||
{{ if .Name.Err }}
|
||||
<div class="error">{{.Name.Err}}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-container">
|
||||
<label>Editor:</label>
|
||||
<input type="text" name="editor" value="{{.Editor.Value}}" />
|
||||
</div>
|
||||
{{ if .Editor.Err }}
|
||||
<div class="error">{{.Editor.Err}}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-container">
|
||||
<label>Authors:</label>
|
||||
<input type="text" name="authors" value="{{.Authors.Value | join}}" />
|
||||
</div>
|
||||
{{ if .Authors.Err }}
|
||||
<div class="error">{{.Authors.Err}}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-container">
|
||||
<label>Year:</label>
|
||||
<input type="number" name="year" value="{{.Year.Value | year}}" />
|
||||
</div>
|
||||
{{ if .Year.Err }}
|
||||
<div class="error">{{.Year.Err}}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-container">
|
||||
<label>Keywords:</label>
|
||||
<input type="text" name="keywords" value="{{.Keywords.Value | join}}" />
|
||||
</div>
|
||||
{{ if .Keywords.Err }}
|
||||
<div class="error">{{.Keywords.Err}}</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
<div class="form-item">
|
||||
<div class="form-container">
|
||||
<input type="file" name="file" files="{{.File.Value.Header | filename}}" accept=".pdf" />
|
||||
<button id="submit" type="submit">Upload</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{{ if .File.Err }}
|
||||
<div class="error">{{.File.Err}}</div>
|
||||
{{ end }}
|
||||
{{ if ne .Error "" }}
|
||||
<div class="error">{{.Error}}</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{ define "script" }}
|
||||
var isSuccess = {{.IsSuccess }};
|
||||
var submit = document.getElementById("submit");
|
||||
submit.addEventListener("submit", function () {
|
||||
submit.disabled = true;
|
||||
});
|
||||
if (isSuccess) {
|
||||
alert("file uploaded successfully");
|
||||
}
|
||||
{{ end }}
|
||||
Reference in New Issue
Block a user