refactor: adding base template for header & nav
This commit is contained in:
parent
2676b87d5b
commit
ffd74c4222
|
@ -50,10 +50,20 @@ so, yup. coupling between js code of oauth, middlewares and body tag. this seems
|
||||||
|
|
||||||
but it somewhat works
|
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
|
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 add tailwind styling
|
||||||
** TODO package static into single binary
|
** TODO package static into single binary
|
||||||
|
i guess already done?
|
||||||
** TODO write nix build
|
** TODO write nix build
|
||||||
** TODO write nixos module
|
** TODO write nixos module
|
||||||
** TODO add docker image from nix
|
** 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 add readme and comments
|
||||||
** TODO configure tls / ssl / https on franzk deployment
|
** TODO configure tls / ssl / https on franzk deployment
|
||||||
can it be configured on render.com?
|
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
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"embed"
|
"embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/labstack/echo/v5"
|
"github.com/labstack/echo/v5"
|
||||||
|
@ -21,6 +22,7 @@ var staticFilesFS embed.FS
|
||||||
|
|
||||||
func AddPageRoutes(app *pocketbase.PocketBase) {
|
func AddPageRoutes(app *pocketbase.PocketBase) {
|
||||||
app.OnBeforeServe().Add(getIndexPageRoute(app))
|
app.OnBeforeServe().Add(getIndexPageRoute(app))
|
||||||
|
app.OnBeforeServe().Add(somePageRoute)
|
||||||
|
|
||||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||||
e.Router.StaticFS("/static", staticFilesFS)
|
e.Router.StaticFS("/static", staticFilesFS)
|
||||||
|
@ -29,7 +31,13 @@ func AddPageRoutes(app *pocketbase.PocketBase) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// render and return index page
|
type navInfo struct {
|
||||||
|
Username string
|
||||||
|
IsGuest bool
|
||||||
|
EnabledOauthProviders []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// render and return some page
|
||||||
func getIndexPageRoute(app *pocketbase.PocketBase) func(*core.ServeEvent) error {
|
func getIndexPageRoute(app *pocketbase.PocketBase) func(*core.ServeEvent) error {
|
||||||
return func(e *core.ServeEvent) error {
|
return func(e *core.ServeEvent) error {
|
||||||
e.Router.GET("/", func(c echo.Context) error {
|
e.Router.GET("/", func(c echo.Context) error {
|
||||||
|
@ -63,16 +71,19 @@ func getIndexPageRoute(app *pocketbase.PocketBase) func(*core.ServeEvent) error
|
||||||
IsGuest, IsAdmin bool
|
IsGuest, IsAdmin bool
|
||||||
Username string
|
Username string
|
||||||
EnabledOauthProviders []string
|
EnabledOauthProviders []string
|
||||||
|
NavInfo navInfo
|
||||||
}{
|
}{
|
||||||
IsGuest: isGuest,
|
|
||||||
IsAdmin: admin != nil,
|
IsAdmin: admin != nil,
|
||||||
|
NavInfo: navInfo{
|
||||||
|
IsGuest: isGuest,
|
||||||
Username: username,
|
Username: username,
|
||||||
EnabledOauthProviders: oauthProviderNames,
|
EnabledOauthProviders: oauthProviderNames,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// then render template with it
|
// then render template with it
|
||||||
templateName := "templates/index.gohtml"
|
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
|
var instantiatedTemplate bytes.Buffer
|
||||||
if err := tmpl.Execute(&instantiatedTemplate, indexPageData); err != nil {
|
if err := tmpl.Execute(&instantiatedTemplate, indexPageData); err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]string{"message": "error parsing template"})
|
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
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
|
@ -1,114 +1,17 @@
|
||||||
<!doctype html>
|
{{ define "title" }}
|
||||||
<html class="no-js" lang="">
|
Index page
|
||||||
<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" />
|
|
||||||
|
|
||||||
<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 }}
|
{{ 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) {
|
{{ define "content" }}
|
||||||
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>
|
<h1>Welcome to index page</h1>
|
||||||
{{ if not .IsGuest }}
|
{{ if not .NavInfo.IsGuest }}
|
||||||
<p>This is content only for authenticated users! Congratulations!</p>
|
<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 }}
|
{{ end }}
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
|
@ -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 }}
|
Loading…
Reference in New Issue