~inferiormartin/gravel

1e3cc13a69ff4d6446ad82ba769088f89d2613ac — Maarten Vos 6 months ago d3813b2
move internal actions to own files
15 files changed, 130 insertions(+), 134 deletions(-)

A account_actions.go
M app.go
M examples/example-nodb/cmd/main.go
M middleware.go
M routes.go
M service.go
M service_backend_postgres.go
A sign_in_actions.go
R templates/{attribute/attribute-bool.html => attribute-bool.html}
R templates/{attribute/attribute-datalist.html => attribute-datalist.html}
R templates/{attribute/attribute-number.html => attribute-number.html}
R templates/{attribute/attribute-password.html => attribute-password.html}
R templates/{attribute/attribute-select.html => attribute-select.html}
R templates/{attribute/attribute-string.html => attribute-string.html}
A user_actions.go
A account_actions.go => account_actions.go +45 -0
@@ 0,0 1,45 @@
package badger

import (
	"fmt"
	"git.sr.ht/~inferiormartin/badger/model"
)

func (app *App) addAccount() {
	app.Advanced.AddPersistentObjectActions("account", PersistentObjectActions{
		OnSave: func(args *SaveArgs) error {
			oldPassword := args.PersistentObject.GetAttribute("oldPassword")
			newPassword := args.PersistentObject.GetAttribute("newPassword")
			newPasswordConfirmed := args.PersistentObject.GetAttribute("confirmPassword")

			if !args.Service.App.UserStore.CheckPassword(args.Service.User.Username, oldPassword.Value().(string)) {
				oldPassword.SetErrorText("Your password is incorrect")
				return nil
			}

			if newPassword.Value().(string) != newPasswordConfirmed.Value().(string) {
				newPasswordConfirmed.SetErrorText("Confirmed password does not match new password")
				return nil
			}

			err := args.Service.App.UserStore.ChangePassword(args.Service.User.Username, newPassword.Value().(string))

			if err != nil {
				args.PersistentObject.AddNotification("Sorry, an unexpected error occurred and we were not able to save your new password.", model.NotificationError)
				return nil
			}

			args.PersistentObject.AddNotification("Your password has been updated!", model.NotificationSuccess)

			return nil
		},
		OnConstruct: func(args *ConstructArgs) error {
			//TODO: We should deny this page completely.
			if args.Service.User != nil {
				args.PersistentObject.Breadcrumb = fmt.Sprintf("Welcome, %s. You can change your password here.", args.Service.User.Username)
				args.PersistentObject.GetAttribute("username").SetValue(args.Service.User.Username)
			}
			return nil
		},
	})
}

M app.go => app.go +5 -64
@@ 3,7 3,6 @@ package badger
import (
	"embed"
	"encoding/json"
	"fmt"
	"git.sr.ht/~inferiormartin/badger/config"
	"git.sr.ht/~inferiormartin/badger/model"
	"git.sr.ht/~inferiormartin/badger/user"


@@ 20,7 19,7 @@ import (
	"github.com/vaughan0/go-ini"
)

//go:embed templates/*.html templates/attribute/*.html static/* model.json
//go:embed templates/*.html static/* model.json
var content embed.FS

type App struct {


@@ 121,58 120,9 @@ func (app *App) Run() {
		}
	}

	app.Advanced.AddPersistentObjectActions("account", PersistentObjectActions{
		OnSave: func(args *SaveArgs) error {
			oldPassword := args.PersistentObject.GetAttribute("oldPassword")
			newPassword := args.PersistentObject.GetAttribute("newPassword")
			newPasswordConfirmed := args.PersistentObject.GetAttribute("confirmPassword")

			if !args.Service.App.UserStore.CheckPassword(args.Service.User.Username, oldPassword.Value().(string)) {
				oldPassword.SetErrorText("Your password is incorrect")
				return nil
			}

			if newPassword.Value().(string) != newPasswordConfirmed.Value().(string) {
				newPasswordConfirmed.SetErrorText("Confirmed password does not match new password")
				return nil
			}

			err := args.Service.App.UserStore.ChangePassword(args.Service.User.Username, newPassword.Value().(string))

			if err != nil {
				args.PersistentObject.AddNotification("Sorry, an unexpected error occurred and we were not able to save your new password.", model.NotificationError)
				return nil
			}

			args.PersistentObject.AddNotification("Your password has been updated!", model.NotificationSuccess)

			return nil
		},
		OnConstruct: func(args *ConstructArgs) error {
			//TODO: We should deny this page completely.
			if args.Service.User != nil {
				args.PersistentObject.Breadcrumb = fmt.Sprintf("Welcome, %s. You can change your password here.", args.Service.User.Username)
				args.PersistentObject.GetAttribute("username").SetValue(args.Service.User.Username)
			}
			return nil
		},
	})

	app.Advanced.AddPersistentObjectActions("user", PersistentObjectActions{
		OnSave: func(args *SaveArgs) error {
			pass := args.PersistentObject.GetAttribute("password").Value().(string)

			h, err := args.Service.App.UserStore.HashPassword(pass)

			if err != nil {
				return err
			}

			args.PersistentObject.GetAttribute("passwordHash").SetValue(h)

			return nil
		},
	})
	app.addSignIn()
	app.addAccount()
	app.addUser()

	r := chi.NewRouter()



@@ 199,11 149,8 @@ func (app *App) Run() {
		http.Redirect(w, r, "/static/favicon.ico", http.StatusMovedPermanently)
	})

	r.Get("/sign-in", app.getSignIn())
	r.Post("/sign-in/action/save", app.postSignIn())

	r.Group(func(r chi.Router) {
		r.Use(AuthenticatedOnly)
		//r.Use(AuthenticatedOnly)

		r.Get("/", func(w http.ResponseWriter, r *http.Request) {
			service := serviceOf(r.Context())


@@ 257,10 204,4 @@ func (app *App) initModelDb() {
			modelDb.Merge(other)
		}
	}

	modelDb.BindObject("user", func() any {
		return &user.User{}
	})

	//modelDb.SetQuerySource("users", GetUsersByPostgres)
}

M examples/example-nodb/cmd/main.go => examples/example-nodb/cmd/main.go +1 -1
@@ 17,7 17,7 @@ func main() {
	//app.Advanced.OnIndex = func(args *badger.PersistentObjectActionArgs) http.Handler {
	//	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	//		w.WriteHeader(http.StatusOK)
	//		args.Service.Nav = args.Service.App.ModelDb.NewNav()
	//		args.Service.Nav = args.Service.app.ModelDb.NewNav()
	//		args.Service.Html = "<h1>Welcome!</h1>"
	//		badger.RenderIndex(w, r, args.Service)
	//	})

M middleware.go => middleware.go +5 -4
@@ 30,11 30,11 @@ func AuthenticatedOnly(next http.Handler) http.Handler {

		service := serviceOf(r.Context())

		//valid, err := service.App.UserStore.CheckSession(username, code)
		//valid, err := service.app.UserStore.CheckSession(username, code)

		//if err != nil {
		//	w.WriteHeader(http.StatusInternalServerError)
		//	if service.App.IsDevelopment() {
		//	if service.app.IsDevelopment() {
		//		_, err := w.Write([]byte(err.Error()))
		//		if err != nil {
		//			w.WriteHeader(http.StatusInternalServerError)


@@ 49,7 49,7 @@ func AuthenticatedOnly(next http.Handler) http.Handler {
		//	return
		//}
		//
		//usr, err := service.App.UserStore.GetUser(username)
		//usr, err := service.app.UserStore.GetUser(username)
		//
		//if err != nil {
		//	w.WriteHeader(http.StatusInternalServerError)


@@ 69,7 69,8 @@ func (app *App) UseService() func(http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			service := &Service{
				App:     app,
				backend: &PostgresBackend{App: app},
				backend: &PostgresBackend{app: app},
				Nav:     app.ModelDb.NewNav(),
			}
			r = r.WithContext(context.WithValue(r.Context(), serviceContextKey, service))
			next.ServeHTTP(w, r)

M routes.go => routes.go +2 -56
@@ 16,6 16,7 @@ func RenderIndex(w http.ResponseWriter, r *http.Request, service *Service) {
	err := service.App.Template.ExecuteTemplate(w, "index.html", service)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(err.Error()))
	}
}



@@ 25,6 26,7 @@ func RenderNotFound(w http.ResponseWriter, r *http.Request, service *Service) {
	err := service.App.Template.ExecuteTemplate(w, "index.html", service)
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Write([]byte(err.Error()))
	}
}



@@ 469,59 471,3 @@ func onCustomAction() http.HandlerFunc {
		RenderIndex(w, r, service)
	}
}

func (app *App) getSignIn() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		//TODO: if already signed in on current device, redirect to /
		service := serviceOf(r.Context())

		service.Object = app.ModelDb.NewObject("sign-in")

		err := app.Template.ExecuteTemplate(w, "index.html", service)

		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
	}
}

func (app *App) postSignIn() http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		username := r.PostFormValue("username")
		password := r.PostFormValue("password")

		valid := app.UserStore.CheckPassword(username, password)

		if !valid {
			service := serviceOf(r.Context())

			service.Object = app.ModelDb.NewObject("sign-in")

			//TODO: we want to refill all form fields, excluding passwords, etc.
			service.Object.GetAttribute("username").SetValue(username)
			service.Object.AddNotification("Your username or password is incorrect", model.NotificationError)

			err := app.Template.ExecuteTemplate(w, "index.html", service)
			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				return
			}
			return
		}

		session, err := app.UserStore.CreateSession(username)

		if err != nil {
			log.Fatal(err)
		}

		http.SetCookie(w, &http.Cookie{
			Name:    "session",
			Value:   fmt.Sprintf("%s:%s", username, session.Code),
			Expires: session.ExpiresOn,
		})

		http.Redirect(w, r, "/", http.StatusFound)
	}
}

M service.go => service.go +4 -4
@@ 70,7 70,7 @@ func (service *Service) GetPersistentObject(name string, id string) (*model.Pers
// ExecuteQuery TODO: page should go into something like "ExecuteQueryOptions"
func (service *Service) ExecuteQuery(query *model.Query, page int) (*model.ExecutedQuery, error) {
	// execute the source function
	result := service.App.ModelDb.GetQuerySource(query.Name)(&CustomQueryArgs{
	sourceResult := service.App.ModelDb.GetQuerySource(query.Name)(&CustomQueryArgs{
		Service: service,
		Query:   query,
	})


@@ 78,13 78,13 @@ func (service *Service) ExecuteQuery(query *model.Query, page int) (*model.Execu
	var results []any

	// we're dealing with a Squirrel SelectBuilder, add defaults (pagination, etc) and execute
	if err := service.backend.RetrieveQuery(query, results, result, page); err != nil {
	if err := service.backend.RetrieveQuery(query, results, sourceResult, page); err != nil {
		return nil, err
	}

	// we're dealing with in-memory data, just set our results
	if reflect.TypeOf(result).Kind() == reflect.Slice {
		results = interfaceSlice(result)
	if reflect.TypeOf(sourceResult).Kind() == reflect.Slice {
		results = interfaceSlice(sourceResult)
	}

	executedQuery := &model.ExecutedQuery{

M service_backend_postgres.go => service_backend_postgres.go +5 -5
@@ 6,7 6,7 @@ import (
)

type PostgresBackend struct {
	App *App
	app *App
}

func (backend *PostgresBackend) RetrievePersistentObject(obj *model.PersistentObject, id string) error {


@@ 20,9 20,9 @@ func (backend *PostgresBackend) RetrievePersistentObject(obj *model.PersistentOb
	}

	//TODO: take from original or copy function over
	res := backend.App.ModelDb.GetObject(obj.Name).CreateStruct()
	res := backend.app.ModelDb.GetObject(obj.Name).CreateStruct()

	err = backend.App.DB.Get(res, sql, args...)
	err = backend.app.DB.Get(res, sql, args...)

	if err != nil {
		return err


@@ 46,8 46,8 @@ func (backend *PostgresBackend) RetrieveQuery(query *model.Query, results []any,
			return err
		}

		if rows, err := backend.App.DB.Queryx(toSql, args...); err == nil {
			obj := backend.App.ModelDb.GetObject(query.Object)
		if rows, err := backend.app.DB.Queryx(toSql, args...); err == nil {
			obj := backend.app.ModelDb.GetObject(query.Object)
			for rows.Next() {
				value := obj.CreateStruct()
				if err = rows.StructScan(value); err != nil {

A sign_in_actions.go => sign_in_actions.go +36 -0
@@ 0,0 1,36 @@
package badger

import (
	"git.sr.ht/~inferiormartin/badger/model"
)

func (app *App) addSignIn() {
	app.Advanced.AddPersistentObjectActions("sign-in", PersistentObjectActions{
		OnSave: func(args *SaveArgs) error {
			username := args.Service.Object.GetAttribute("username").Value().(string)
			password := args.Service.Object.GetAttribute("password").Value().(string)

			valid := app.UserStore.CheckPassword(username, password)

			if !valid {
				args.Service.Object.AddNotification("Your username or password is incorrect", model.NotificationError)
				return nil
			}

			//session, err := app.UserStore.CreateSession(username)
			//
			//if err != nil {
			//	log.Fatal(err)
			//}

			//http.SetCookie(w, &http.Cookie{
			//	Name:    "session",
			//	Value:   fmt.Sprintf("%s:%s", username, session.Code),
			//	Expires: session.ExpiresOn,
			//})
			//
			//http.Redirect(w, r, "/", http.StatusFound)
			return nil
		},
	})
}

R templates/attribute/attribute-bool.html => templates/attribute-bool.html +0 -0
R templates/attribute/attribute-datalist.html => templates/attribute-datalist.html +0 -0
R templates/attribute/attribute-number.html => templates/attribute-number.html +0 -0
R templates/attribute/attribute-password.html => templates/attribute-password.html +0 -0
R templates/attribute/attribute-select.html => templates/attribute-select.html +0 -0
R templates/attribute/attribute-string.html => templates/attribute-string.html +0 -0
A user_actions.go => user_actions.go +27 -0
@@ 0,0 1,27 @@
package badger

import "git.sr.ht/~inferiormartin/badger/user"

func (app *App) addUser() {
	app.ModelDb.BindObject("user", func() any {
		return &user.User{}
	})

	//app.ModelDb.SetQuerySource("users", GetUsersByPostgres)

	app.Advanced.AddPersistentObjectActions("user", PersistentObjectActions{
		OnSave: func(args *SaveArgs) error {
			pass := args.PersistentObject.GetAttribute("password").Value().(string)

			h, err := args.Service.App.UserStore.HashPassword(pass)

			if err != nil {
				return err
			}

			args.PersistentObject.GetAttribute("passwordHash").SetValue(h)

			return nil
		},
	})
}