refactor: adding base template for header & nav

This commit is contained in:
efim 2023-10-06 13:39:36 +00:00
parent 2676b87d5b
commit ffd74c4222
5 changed files with 220 additions and 126 deletions

View File

@ -50,10 +50,20 @@ so, yup. coupling between js code of oauth, middlewares and body tag. this seems
but it somewhat works
** TODO add one more page that checks auth
** DONE add one more page that checks auth
and let's use existing middleware from framework documentation
with hx-boost things are well,
but i also need header as fragment, so that opening in new tab would work.
and all js imports and libraries that are required by all pages, should be in all templates
** DONE i suppose there has to be a base template then
and now all since base template has Nav,
i need to provide attibutes which are used there, huh
well. hmmmmm. yeah, i guess
** TODO add tailwind styling
** TODO package static into single binary
i guess already done?
** TODO write nix build
** TODO write nixos module
** TODO add docker image from nix
@ -61,3 +71,7 @@ and let's use existing middleware from framework documentation
** TODO add readme and comments
** TODO configure tls / ssl / https on franzk deployment
can it be configured on render.com?
** TODO maybe add middleware so that 401 would be a page, and not json
** TODO i guess i'll want a makefile?
then wgo could be build with makefile and run
and nix packaging could be more straightforward, and not too prohibitive to those who don't use nix

View File

@ -5,6 +5,7 @@ import (
"embed"
"fmt"
"html/template"
"math/rand"
"net/http"
"github.com/labstack/echo/v5"
@ -21,21 +22,28 @@ var staticFilesFS embed.FS
func AddPageRoutes(app *pocketbase.PocketBase) {
app.OnBeforeServe().Add(getIndexPageRoute(app))
app.OnBeforeServe().Add(somePageRoute)
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
e.Router.StaticFS("/static", staticFilesFS)
// this path works : http://127.0.0.1:8090/static/static/public/htmx.min.js
return nil
return nil
})
}
// render and return index page
func getIndexPageRoute(app *pocketbase.PocketBase) func(*core.ServeEvent) error {
return func (e *core.ServeEvent) error {
type navInfo struct {
Username string
IsGuest bool
EnabledOauthProviders []string
}
// render and return some page
func getIndexPageRoute(app *pocketbase.PocketBase) func(*core.ServeEvent) error {
return func(e *core.ServeEvent) error {
e.Router.GET("/", func(c echo.Context) error {
// first collect data
info := apis.RequestInfo(c)
admin := info.Admin // nil if not authenticated as admin
info := apis.RequestInfo(c)
admin := info.Admin // nil if not authenticated as admin
record := info.AuthRecord // nil if not authenticated as regular auth record
isGuest := admin == nil && record == nil
@ -60,19 +68,22 @@ func getIndexPageRoute(app *pocketbase.PocketBase) func(*core.ServeEvent) error
fmt.Printf(">> enabled providers names %+v\n", oauthProviderNames)
indexPageData := struct {
IsGuest, IsAdmin bool
Username string
IsGuest, IsAdmin bool
Username string
EnabledOauthProviders []string
NavInfo navInfo
}{
IsGuest: isGuest,
IsAdmin: admin != nil,
Username: username,
EnabledOauthProviders: oauthProviderNames,
NavInfo: navInfo{
IsGuest: isGuest,
Username: username,
EnabledOauthProviders: oauthProviderNames,
},
}
// then render template with it
templateName := "templates/index.gohtml"
tmpl := template.Must(template.ParseFS(templatesFS, templateName))
tmpl := template.Must(template.ParseFS(templatesFS, "templates/base.gohtml", templateName))
var instantiatedTemplate bytes.Buffer
if err := tmpl.Execute(&instantiatedTemplate, indexPageData); err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"message": "error parsing template"})
@ -83,3 +94,55 @@ func getIndexPageRoute(app *pocketbase.PocketBase) func(*core.ServeEvent) error
return nil
}
}
const charset = "abcdefghijklmnopqrstuvwxyz" +
"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
func stringWithCharset(length int, charset string) string {
b := make([]byte, length)
for i := range b {
b[i] = charset[rand.Intn(len(charset))]
}
return string(b)
}
func somePageRoute(e *core.ServeEvent) error {
e.Router.GET("/somepage", func(c echo.Context) error {
// get data
// and since i'm using 'base.gohtml' with Nav, i'll need Nav info
info := apis.RequestInfo(c)
admin := info.Admin // nil if not authenticated as admin
record := info.AuthRecord // nil if not authenticated as regular auth record
username := ""
switch {
case admin != nil:
username = admin.Email
case record != nil:
username = record.Username()
}
somePageData := struct {
RandomNumber int
RandomString string
NavInfo navInfo
}{
RandomNumber: rand.Int(),
RandomString: stringWithCharset(25, charset),
NavInfo: navInfo{
Username: username,
},
}
// then render template with it
templateName := "templates/somepage.gohtml"
tmpl := template.Must(template.ParseFS(templatesFS, "templates/base.gohtml", templateName))
var instantiatedTemplate bytes.Buffer
if err := tmpl.Execute(&instantiatedTemplate, somePageData); err != nil {
return c.JSON(http.StatusInternalServerError, map[string]string{"message": "error parsing template"})
}
return c.HTML(http.StatusOK, instantiatedTemplate.String())
}, apis.RequireAdminOrRecordAuth())
return nil
}

100
pages/templates/base.gohtml Normal file
View File

@ -0,0 +1,100 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>{{ template "title" . }}</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<!-- Place favicon.ico in the root directory -->
<script
defer
src="https://cdn.jsdelivr.net/gh/pocketbase/js-sdk@master/dist/pocketbase.umd.js"
></script>
<script defer src="/static/static/public/htmx.min.js"></script>
</head>
<body hx-get="/" hx-trigger="auth-change-event">
<!--[if lt IE 8]>
<p class="browserupgrade">
You are using an <strong>outdated</strong> browser. Please
<a href="http://browsehappy.com/">upgrade your browser</a> to improve
your experience.
<![endif]-->
<nav>
<p>Using SSR and oauth with pocketbase as Go framework</p>
{{ if .NavInfo.IsGuest }}
<button id="openAuth">Authenticate</button>
<dialog id="authDialog">
<button id="closeAuth">[X]</button>
<p>Greetings, one and all!</p>
{{ range .NavInfo.EnabledOauthProviders }}
<button onClick="callOauth('{{ . }}')">Login with {{ . }}</button>
{{ else }}
<p>Please configure at least one oauth provider</p>
{{ end }}
</dialog>
<script defer type="text/javascript">
async function callOauth(providerName) {
const pb = new PocketBase("http://127.0.0.1:8090");
// This method initializes a one-off realtime subscription and will
// open a popup window with the OAuth2 vendor page to authenticate.
//
// Once the external OAuth2 sign-in/sign-up flow is completed, the popup
// window will be automatically closed and the OAuth2 data sent back
// to the user through the previously established realtime connection.
const authData = await pb
.collection("users")
.authWithOAuth2({ provider: providerName });
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.model.id);
// "logout" the last authenticated model
pb.authStore.clear();
document.body.dispatchEvent(new Event("auth-change-event"));
}
</script>
<script defer type="text/javascript">
function initAuthDialog() {
const dialog = document.querySelector("#authDialog");
const showButton = document.querySelector("#openAuth");
// const closeButton = document.querySelector("#closeAuth");
// Select all buttons that are direct children of the dialog
var buttons = authDialog.querySelectorAll("button");
if (!dialog || !showButton) {
console.log("some auth elements are not present");
return;
}
console.log("setting up script for buttons");
// "Show the dialog" button opens the dialog modally
showButton.addEventListener("click", () => {
dialog.showModal();
});
buttons.forEach(function (button) {
button.addEventListener("click", function () {
authDialog.close();
});
});
}
initAuthDialog();
// "DOMContentLoaded" doesn't work with htmx replacing body
// maybe i could use htmx related event, but ok to just do asap, i guess
// also - i bet hyperscript would work here
// document.addEventListener("DOMContentLoaded", initAuthDialog);
</script>
{{ else }}
<p>{{ .NavInfo.Username }}</p>
<button hx-get="/logout">Logout</button>
{{ end }}
</nav>
<main hx-boost="true">{{ template "content" . }}</main>
</body>
</html>

View File

@ -1,114 +1,17 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<title>index page</title>
<meta name="description" content="" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{{ define "title" }}
Index page
{{ end }}
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<!-- Place favicon.ico in the root directory -->
<script
defer
src="https://cdn.jsdelivr.net/gh/pocketbase/js-sdk@master/dist/pocketbase.umd.js"
></script>
<script defer src="/static/static/public/htmx.min.js"></script>
<script defer type="text/javascript">
async function callOauth(providerName) {
const pb = new PocketBase("http://127.0.0.1:8090");
// This method initializes a one-off realtime subscription and will
// open a popup window with the OAuth2 vendor page to authenticate.
//
// Once the external OAuth2 sign-in/sign-up flow is completed, the popup
// window will be automatically closed and the OAuth2 data sent back
// to the user through the previously established realtime connection.
const authData = await pb
.collection("users")
.authWithOAuth2({ provider: providerName });
// after the above you can also access the auth data from the authStore
console.log(pb.authStore.isValid);
console.log(pb.authStore.token);
console.log(pb.authStore.model.id);
// "logout" the last authenticated model
pb.authStore.clear();
document.body.dispatchEvent(new Event("auth-change-event"))
}
</script>
</head>
<body
hx-get="/" hx-trigger="auth-change-event"
>
<!--[if lt IE 8]>
<p class="browserupgrade">
You are using an <strong>outdated</strong> browser. Please
<a href="http://browsehappy.com/">upgrade your browser</a> to improve
your experience.
</p<dialog open>
<p>Greetings, one and all!</p>
<form method="dialog">
<button>OK</button>
</form>
</dialog>
>
<![endif]-->
<nav>
<p>Using SSR and oauth with pocketbase as Go framework</p>
{{ if .IsGuest }}
<button id="openAuth">Authenticate</button>
<dialog id="authDialog">
<button id="closeAuth">[X]</button>
<p>Greetings, one and all!</p>
{{ range .EnabledOauthProviders }}
<button onClick="callOauth('{{ . }}')">Login with {{ . }}</button>
{{ else }}
<p>Please configure at least one oauth provider</p>
{{ end }}
</dialog>
<script defer type="text/javascript">
function initAuthDialog() {
const dialog = document.querySelector("#authDialog");
const showButton = document.querySelector("#openAuth");
// const closeButton = document.querySelector("#closeAuth");
// Select all buttons that are direct children of the dialog
var buttons = authDialog.querySelectorAll("button");
if (!dialog || !showButton) {
console.log("some auth elements are not present");
return;
}
console.log("setting up script for buttons");
// "Show the dialog" button opens the dialog modally
showButton.addEventListener("click", () => {
dialog.showModal();
});
buttons.forEach(function (button) {
button.addEventListener("click", function () {
authDialog.close();
});
});
}
initAuthDialog();
// "DOMContentLoaded" doesn't work with htmx replacing body
// maybe i could use htmx related event, but ok to just do asap, i guess
// also - i bet hyperscript would work here
// document.addEventListener("DOMContentLoaded", initAuthDialog);
</script>
{{ else }}
<p>{{ .Username }}</p>
<button hx-get="/logout">Logout</button>
{{ end }}
</nav>
<main>
<h1>Welcome to index page</h1>
{{ if not .IsGuest }}
<p>This is content only for authenticated users! Congratulations!</p>
{{ end }}
</main>
</body>
</html>
{{ define "content" }}
<h1>Welcome to index page</h1>
{{ if not .NavInfo.IsGuest }}
<p>This is content only for authenticated users! Congratulations!</p>
<ul>
<li>
<a href="/somepage">Link to page only for logged in users</a>
</li>
</ul>
{{ else }}
<p>There will be some content only for authorized users</p>
{{ end }}
{{ end }}

View File

@ -0,0 +1,14 @@
{{ define "title" }}
Some page with content
{{ end }}
{{ define "content" }}
<h1>This is another page</h1>
<p>Will be rendered on server</p>
<p>and locked under apis.RequireAdminOrRecordAuth default middleware</p>
<p>here are some random numbers</p>
<ul>
<li>{{ .RandomNumber }}</li>
<li>{{ .RandomString }}</li>
</ul>
{{ end }}