From 3225d5719bb6d0337c9d10db65cefe3f366d433a Mon Sep 17 00:00:00 2001 From: Ryan Faerman Date: Mon, 29 Jan 2024 16:17:43 -0500 Subject: [PATCH] add profile viewing and editing --- internal/handlers/account.go | 92 +++++-- internal/models/account.go | 14 +- internal/services/account.go | 22 +- internal/views/account.templ | 131 ++++++++++ internal/views/account_templ.go | 413 ++++++++++++++++++++++++++++++++ 5 files changed, 647 insertions(+), 25 deletions(-) create mode 100644 internal/views/account.templ create mode 100644 internal/views/account_templ.go diff --git a/internal/handlers/account.go b/internal/handlers/account.go index b1661b6..21a5197 100644 --- a/internal/handlers/account.go +++ b/internal/handlers/account.go @@ -2,6 +2,7 @@ package handlers import ( "net/http" + "strings" "github.com/go-chi/chi" @@ -19,7 +20,6 @@ func init() { } func (h account) Routes(r chi.Router) { - r.Use(services.Session.Middleware) r.Group(func(r chi.Router) { r.Use(middleware.HTMXOnly) @@ -27,6 +27,10 @@ func (h account) Routes(r chi.Router) { // r.Post(named.Route("account-setup-apply", "/account/setup"), h.Setup) }) + + r.Get(named.Route("account-profile", "/profile/{callsign}"), h.Show) + r.Get(named.Route("account-edit", "/profile/{callsign}/edit"), h.Edit) + r.Post(named.Route("account-edit-save", "/profile/{callsign}/edit/-/save"), h.Update) } func (h account) Setup(w http.ResponseWriter, r *http.Request) { @@ -54,24 +58,10 @@ func (h account) Setup(w http.ResponseWriter, r *http.Request) { return } - // callsign, err := hamdb.Lookup(r.Context(), input.Callsign) - // if err != nil { , - // inputErrs.Callsign = "Could not validate callsign" - // if err != hamdb.ErrNotFound { - // global.log.Error("hamdb lookup failed", "error", err) - // } - // - // ctx := services.CSRF.GetContext(r.Context(), r) - // - // (views.Dashboard{}).SetupaccountWithErrors(input, inputErrs).Render(ctx, w) - // return - // } - // spew.Dump(callsign) - account, err := services.Session.GetAccount(r.Context()) if err != nil { global.log.Error("unable to get account from session", "error", err) - //views.Errors{}.Internal().Render(r.Context(), w) + // views.Errors{}.Internal().Render(r.Context(), w) return } if err := services.Account.Setup(r.Context(), account.ID, input.Name, input.Callsign); err != nil { @@ -95,3 +85,73 @@ func (h account) Setup(w http.ResponseWriter, r *http.Request) { // TODO: update the profile, kick back errors if there are any } + +func (h account) Show(w http.ResponseWriter, r *http.Request) { + callsign := chi.URLParam(r, "callsign") + ctx := services.CSRF.GetContext(r.Context(), r) + + a, err := services.Account.FindByCallsign(ctx, callsign) + if err != nil { + ErrorHandler(err)(w, r) + return + } + + v := views.Account{ + Account: a, + } + v.Profile().Render(ctx, w) +} + +func (h account) Edit(w http.ResponseWriter, r *http.Request) { + callsign := chi.URLParam(r, "callsign") + ctx := services.CSRF.GetContext(r.Context(), r) + a, err := services.Account.FindByCallsign(ctx, callsign) + if err != nil { + ErrorHandler(err)(w, r) + return + } + v := views.Account{ + Account: a, + } + v.Edit().Render(ctx, w) +} + +func (h account) Update(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + + callsign := chi.URLParam(r, "callsign") + ctx := services.CSRF.GetContext(r.Context(), r) + a, err := services.Account.FindByCallsign(ctx, callsign) + if err != nil { + ErrorHandler(err)(w, r) + return + } + v := views.Account{ + Account: a, + } + + inputErrs := views.AccountEditFormErrors{} + input := views.AccountEditFormInput{ + Name: strings.TrimSpace(r.Form.Get("name")), + } + a.Name = input.Name + err = services.Account.Update(r.Context(), a) + if err != nil { + if errs, ok := err.(services.ValidationError); ok { + for field, e := range errs { + switch field { + case "Account.Name": + inputErrs.Name = e + } + } + v.EditFormWithErrors(input, inputErrs).Render(ctx, w) + return + } + ErrorHandler(err)(w, r) + return + } + // TODO: return this from the account.update method + v.Account = a + + v.EditForm().Render(ctx, w) +} diff --git a/internal/models/account.go b/internal/models/account.go index eb796ae..aa38e4a 100644 --- a/internal/models/account.go +++ b/internal/models/account.go @@ -17,7 +17,7 @@ const ( type Account struct { ID int64 - Name string + Name string `validate:"required"` Kind AccountKind CreatedAt time.Time @@ -122,6 +122,18 @@ func (u *Account) Callsigns() ([]Callsign, error) { return callsigns, nil } +func (m *Account) Callsign() Callsign { + calls, err := m.Callsigns() + if err != nil { + return Callsign{} + } + if len(calls) == 0 { + return Callsign{} + } + + return calls[0] +} + var ( ErrAccountNeedsCallsign = errors.New("user needs a callsign") ErrAccountNeedsName = errors.New("user needs a name") diff --git a/internal/services/account.go b/internal/services/account.go index 58cdd71..52e1d1c 100644 --- a/internal/services/account.go +++ b/internal/services/account.go @@ -22,6 +22,10 @@ func (account) FindByEmail(ctx context.Context, email string) (*models.Account, return models.FindAccountByEmail(ctx, email) } +func (account) FindByCallsign(ctx context.Context, callsign string) (*models.Account, error) { + return models.FindAccountByCallsign(ctx, callsign) +} + func (s account) CreateWithEmail(ctx context.Context, email string) (*models.Account, error) { if u, err := s.FindByEmail(ctx, email); err != nil { if err != sql.ErrNoRows { @@ -67,7 +71,6 @@ var ( ) func (s account) Setup(ctx context.Context, id int64, name, callsign string) error { - account, err := global.dao.UpdateAccount(ctx, dao.UpdateAccountParams{ ID: id, Name: name, @@ -85,7 +88,6 @@ func (s account) Setup(ctx context.Context, id int64, name, callsign string) err if account.ID != accountCallsign.ID { return ErrAccountSetupCallsignTaken } - } fccCallsign, err := hamdb.Lookup(ctx, callsign) @@ -144,9 +146,13 @@ func (s account) Setup(ctx context.Context, id int64, name, callsign string) err return tx.Commit() } -// func (s account) AddCallsign(ctx context.Context, id int64, callsign string) error { -// return global.dao.AddCallsignForaccount(ctx, dao.AddCallsignForaccountParams{ -// accountID: id, -// Callsign: callsign, -// }) -// } +func (s account) Update(ctx context.Context, m *models.Account) error { + if err := Validation.Apply(m); err != nil { + return err + } + _, err := global.dao.UpdateAccount(ctx, dao.UpdateAccountParams{ + ID: m.ID, + Name: m.Name, + }) + return err +} diff --git a/internal/views/account.templ b/internal/views/account.templ new file mode 100644 index 0000000..fc2c6b4 --- /dev/null +++ b/internal/views/account.templ @@ -0,0 +1,131 @@ +package views + +import "github.com/ryanfaerman/netctl/internal/models" +import "github.com/ryanfaerman/netctl/web/named" + +type Account struct { + Account *models.Account +} + +templ (v Account) Profile() { + @Page() { +
+ @v.LocalBar() +
+
+
+ "hello there { v.Account.Name }!" +
+
+ + + Edit Profile + +
+
+ } +} + +templ (v Account) Edit() { + @Page() { +
+ @v.LocalBar() +
+
+
+ "Editing { v.Account.Name }!" + @v.EditForm() +
+
+ + + View Profile + +
+
+ } +} + +type AccountEditFormInput struct { + Name string + About string +} + +type AccountEditFormErrors struct { + Name string + About string +} + +templ (v Account) EditForm() { + @v.EditFormWithErrors( + AccountEditFormInput{ + Name: v.Account.Name, + }, + AccountEditFormErrors{}, + ) +} + +templ (v Account) EditFormWithErrors(input AccountEditFormInput, inputErrs AccountEditFormErrors) { + @Form("edit-account", FormAttrs{ + Action: named.URLFor("account-edit-save", v.Account.Callsign().Call), + }) { + @InputText("name", InputAttrs{ + Label: "Name", + Value: input.Name, + Error: inputErrs.Name, + HelpText: "Your name as you'd like to be called on the air", + }) + @InputSubmit(InputAttrs{ + Value: "Save", + }) + } +} + +templ (v Account) LocalBar() { +
+ + +
+} diff --git a/internal/views/account_templ.go b/internal/views/account_templ.go new file mode 100644 index 0000000..a9d4b56 --- /dev/null +++ b/internal/views/account_templ.go @@ -0,0 +1,413 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.2.513 +package views + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import "context" +import "io" +import "bytes" + +import "github.com/ryanfaerman/netctl/internal/models" +import "github.com/ryanfaerman/netctl/web/named" + +type Account struct { + Account *models.Account +} + +func (v Account) Profile() templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = v.LocalBar().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var3 := `"hello there ` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var3) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(v.Account.Name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/account.templ`, Line: 16, Col: 33} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var5 := `!"` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var5) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var8 := `Edit Profile` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var8) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer) + } + return templ_7745c5c3_Err + }) + templ_7745c5c3_Err = Page().Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func (v Account) Edit() templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var9 := templ.GetChildren(ctx) + if templ_7745c5c3_Var9 == nil { + templ_7745c5c3_Var9 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var10 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = v.LocalBar().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var11 := `"Editing ` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var11) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(v.Account.Name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/views/account.templ`, Line: 35, Col: 29} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var13 := `!"` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var13) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = v.EditForm().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Var16 := `View Profile` + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ_7745c5c3_Var16) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer) + } + return templ_7745c5c3_Err + }) + templ_7745c5c3_Err = Page().Render(templ.WithChildren(ctx, templ_7745c5c3_Var10), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +type AccountEditFormInput struct { + Name string + About string +} + +type AccountEditFormErrors struct { + Name string + About string +} + +func (v Account) EditForm() templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var17 := templ.GetChildren(ctx) + if templ_7745c5c3_Var17 == nil { + templ_7745c5c3_Var17 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = v.EditFormWithErrors( + AccountEditFormInput{ + Name: v.Account.Name, + }, + AccountEditFormErrors{}, + ).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func (v Account) EditFormWithErrors(input AccountEditFormInput, inputErrs AccountEditFormErrors) templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var18 := templ.GetChildren(ctx) + if templ_7745c5c3_Var18 == nil { + templ_7745c5c3_Var18 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var19 := templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + templ_7745c5c3_Err = InputText("name", InputAttrs{ + Label: "Name", + Value: input.Name, + Error: inputErrs.Name, + HelpText: "Your name as you'd like to be called on the air", + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = InputSubmit(InputAttrs{ + Value: "Save", + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = io.Copy(templ_7745c5c3_W, templ_7745c5c3_Buffer) + } + return templ_7745c5c3_Err + }) + templ_7745c5c3_Err = Form("edit-account", FormAttrs{ + Action: named.URLFor("account-edit-save", v.Account.Callsign().Call), + }).Render(templ.WithChildren(ctx, templ_7745c5c3_Var19), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +} + +func (v Account) LocalBar() templ.Component { + return templ.ComponentFunc(func(ctx context.Context, templ_7745c5c3_W io.Writer) (templ_7745c5c3_Err error) { + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templ_7745c5c3_W.(*bytes.Buffer) + if !templ_7745c5c3_IsBuffer { + templ_7745c5c3_Buffer = templ.GetBuffer() + defer templ.ReleaseBuffer(templ_7745c5c3_Buffer) + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var20 := templ.GetChildren(ctx) + if templ_7745c5c3_Var20 == nil { + templ_7745c5c3_Var20 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !templ_7745c5c3_IsBuffer { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) + } + return templ_7745c5c3_Err + }) +}