Prefill login form when using the demo database

This is to help up “sell” the service: people can look around the demo
to see whether it fits them.  Of course, everyone should have the same
username in the demo.

We talked about having the username and password displayed above the
form in the template, but i think it makes more sense to give users as
little work as necessary.  Plus, that means i do not have to write them
down while developing.

Whether the database is demo or not is not something that directly
depends on the environment, but rather on which database we are
connected to, thus an environment variable would not make much sense—it
has to be something of the database.

PostgreSQL has no PRAGMA application_id or PRAGMA user_version as with
SQLite to include application-specific values to the database.  The
equivalent would be customized options[0], intended for modules
configuration, but that would require me to execute an ALTER DATABASE
in demo.sql with an specific datbase name, or force the use of psql to
run script the script, because then i can use the :DBNAME placeholder.

I guess that the most “standard” way is to just create a function that
returns a know value if the database is demo.  Sqitch does not add that
function, therefore it is unlikely to be there by change unless it is
the demo database.

https://www.postgresql.org/docs/15/runtime-config-custom.html
This commit is contained in:
jordi fita mas 2024-01-20 20:23:18 +01:00
parent 2bd7b2e952
commit 5f7b798eb4
4 changed files with 24 additions and 10 deletions

View File

@ -20,9 +20,12 @@ func main() {
} }
defer db.Close() defer db.Close()
var demo bool
_ = db.QueryRow(context.Background(), "select database_is_numerus_demo()").Scan(&demo)
srv := http.Server{ srv := http.Server{
Addr: ":8080", Addr: ":8080",
Handler: numerus.NewRouter(db), Handler: numerus.NewRouter(db, demo),
ReadTimeout: 5 * time.Second, ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second,
IdleTimeout: 2 * time.Minute, IdleTimeout: 2 * time.Minute,

View File

@ -2,6 +2,8 @@ begin;
set search_path to auth, numerus, public; set search_path to auth, numerus, public;
create or replace function public.database_is_numerus_demo() returns bool as $$ select true $$ language sql;
alter sequence user_user_id_seq restart with 123; alter sequence user_user_id_seq restart with 123;
insert into auth."user" (email, name, password, role) insert into auth."user" (email, name, password, role)
values ('demo@numerus', 'Demo User', 'demo', 'invoicer') values ('demo@numerus', 'Demo User', 'demo', 'invoicer')

View File

@ -29,8 +29,8 @@ type loginForm struct {
Password *InputField Password *InputField
} }
func newLoginForm(locale *Locale) *loginForm { func newLoginForm(demo bool, locale *Locale) *loginForm {
return &loginForm{ form := &loginForm{
locale: locale, locale: locale,
Email: &InputField{ Email: &InputField{
Name: "email", Name: "email",
@ -53,6 +53,11 @@ func newLoginForm(locale *Locale) *loginForm {
}, },
}, },
} }
if demo {
form.Email.Val = "admin@numerus"
form.Password.Val = "admin"
}
return form
} }
func (form *loginForm) Parse(r *http.Request) error { func (form *loginForm) Parse(r *http.Request) error {
@ -74,26 +79,26 @@ func (form *loginForm) Validate() bool {
return validator.AllOK() return validator.AllOK()
} }
func GetLoginForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func GetLoginForm(w http.ResponseWriter, r *http.Request, demo bool) {
user := getUser(r) user := getUser(r)
if user.LoggedIn { if user.LoggedIn {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
} }
locale := getLocale(r) locale := getLocale(r)
form := newLoginForm(locale) form := newLoginForm(demo, locale)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
mustRenderLoginForm(w, r, form) mustRenderLoginForm(w, r, form)
} }
func HandleLoginForm(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { func HandleLoginForm(w http.ResponseWriter, r *http.Request, demo bool) {
user := getUser(r) user := getUser(r)
if user.LoggedIn { if user.LoggedIn {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
} }
locale := getLocale(r) locale := getLocale(r)
form := newLoginForm(locale) form := newLoginForm(demo, locale)
if err := form.Parse(r); err != nil { if err := form.Parse(r); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return

View File

@ -6,7 +6,7 @@ import (
"net/http" "net/http"
) )
func NewRouter(db *Db) http.Handler { func NewRouter(db *Db, demo bool) http.Handler {
companyRouter := httprouter.New() companyRouter := httprouter.New()
companyRouter.GET("/profile", GetProfileForm) companyRouter.GET("/profile", GetProfileForm)
companyRouter.POST("/profile", HandleProfileForm) companyRouter.POST("/profile", HandleProfileForm)
@ -62,8 +62,12 @@ func NewRouter(db *Db) http.Handler {
router := httprouter.New() router := httprouter.New()
router.ServeFiles("/static/*filepath", http.Dir("web/static")) router.ServeFiles("/static/*filepath", http.Dir("web/static"))
router.GET("/login", GetLoginForm) router.GET("/login", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
router.POST("/login", HandleLoginForm) GetLoginForm(w, r, demo)
})
router.POST("/login", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
HandleLoginForm(w, r, demo)
})
router.POST("/logout", Authenticated(HandleLogout)) router.POST("/logout", Authenticated(HandleLogout))
companyHandler := Authenticated(CompanyHandler(companyRouter)) companyHandler := Authenticated(CompanyHandler(companyRouter))