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
|
||||
|
||||
** 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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
<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 }}
|
||||
|
|
|
@ -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