feat: initial commit
This commit is contained in:
@ -17,15 +17,15 @@ templ LoginPage(vmodel LoginPageVModel) {
|
||||
<div class="is-flex is-justify-content-center is-align-items-center is-fullheight">
|
||||
<nav class="panel is-link" style="min-width: 33%">
|
||||
<p class="panel-heading">
|
||||
<div class="title">ClearCase</div>
|
||||
<span> - choose your provider</span>
|
||||
<span class="title">ClearCase</span>
|
||||
<span> - choose your platform</span>
|
||||
</p>
|
||||
for _, provider := range vmodel.Providers {
|
||||
<a class="panel-block" href={ templ.URL("/auth/providers/" + provider.ID) } hx-boost="false">
|
||||
<a class="panel-block py-5" href={ templ.URL("/auth/providers/" + provider.ID) } hx-boost="false">
|
||||
<span class="panel-icon is-size-3">
|
||||
<i class={ "fab", provider.Icon } aria-hidden="true"></i>
|
||||
</span>
|
||||
{ provider.Label }
|
||||
<span class="is-size-5">{ provider.Label }</span>
|
||||
</a>
|
||||
}
|
||||
</nav>
|
||||
|
@ -53,12 +53,12 @@ func LoginPage(vmodel LoginPageVModel) templ.Component {
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"is-flex is-justify-content-center is-align-items-center is-fullheight\"><nav class=\"panel is-link\" style=\"min-width: 33%\"><p class=\"panel-heading\"><div class=\"title\">ClearCase</div><span> - choose your provider</span></p>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"is-flex is-justify-content-center is-align-items-center is-fullheight\"><nav class=\"panel is-link\" style=\"min-width: 33%\"><p class=\"panel-heading\"><span class=\"title\">ClearCase</span> <span> - choose your platform</span></p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
for _, provider := range vmodel.Providers {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<a class=\"panel-block\" href=\"")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "<a class=\"panel-block py-5\" href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -89,20 +89,20 @@ func LoginPage(vmodel LoginPageVModel) templ.Component {
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" aria-hidden=\"true\"></i></span> ")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "\" aria-hidden=\"true\"></i></span> <span class=\"is-size-5\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var6 string
|
||||
templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(provider.Label)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/auth/component/login_page.templ`, Line: 28, Col: 22}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/auth/component/login_page.templ`, Line: 28, Col: 46}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</a>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "</span></a>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
|
@ -15,7 +15,6 @@ type Handler struct {
|
||||
sessionStore sessions.Store
|
||||
sessionName string
|
||||
providers []Provider
|
||||
defaultAdmin *DefaultAdmin
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler.
|
||||
@ -30,7 +29,6 @@ func NewHandler(sessionStore sessions.Store, funcs ...OptionFunc) *Handler {
|
||||
sessionStore: sessionStore,
|
||||
sessionName: opts.SessionName,
|
||||
providers: opts.Providers,
|
||||
defaultAdmin: opts.DefaultAdmin,
|
||||
}
|
||||
|
||||
h.mux.HandleFunc("GET /login", h.getLoginPage)
|
||||
|
@ -1,14 +1,8 @@
|
||||
package auth
|
||||
|
||||
type DefaultAdmin struct {
|
||||
Provider string
|
||||
Email string
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
Providers []Provider
|
||||
DefaultAdmin *DefaultAdmin
|
||||
SessionName string
|
||||
Providers []Provider
|
||||
SessionName string
|
||||
}
|
||||
|
||||
type OptionFunc func(opts *Options)
|
||||
@ -16,7 +10,7 @@ type OptionFunc func(opts *Options)
|
||||
func NewOptions(funcs ...OptionFunc) *Options {
|
||||
opts := &Options{
|
||||
Providers: make([]Provider, 0),
|
||||
SessionName: "rkvst_auth",
|
||||
SessionName: "clearcase_auth",
|
||||
}
|
||||
|
||||
for _, fn := range funcs {
|
||||
@ -32,12 +26,6 @@ func WithProviders(providers ...Provider) OptionFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func WithDefaultAdmin(defaultAdmin DefaultAdmin) OptionFunc {
|
||||
return func(opts *Options) {
|
||||
opts.DefaultAdmin = &defaultAdmin
|
||||
}
|
||||
}
|
||||
|
||||
func WithSessionName(sessionName string) OptionFunc {
|
||||
return func(opts *Options) {
|
||||
opts.SessionName = sessionName
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/core/model"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/handler/webui/common"
|
||||
"github.com/markbates/goth/gothic"
|
||||
"github.com/pkg/errors"
|
||||
@ -19,7 +20,7 @@ func (h *Handler) handleProvider(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func (h *Handler) handleProviderCallback(w http.ResponseWriter, r *http.Request) {
|
||||
user, err := gothic.CompleteUserAuth(w, r)
|
||||
gothUser, err := gothic.CompleteUserAuth(w, r)
|
||||
if err != nil {
|
||||
slog.ErrorContext(r.Context(), "could not complete user auth", slog.Any("error", errors.WithStack(err)))
|
||||
http.Redirect(w, r, "/auth/logout", http.StatusTemporaryRedirect)
|
||||
@ -28,9 +29,16 @@ func (h *Handler) handleProviderCallback(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
slog.DebugContext(ctx, "authenticated user", slog.Any("user", user))
|
||||
slog.DebugContext(ctx, "authenticated user", slog.Any("user", gothUser))
|
||||
|
||||
if err := h.storeSessionUser(w, r, &user); err != nil {
|
||||
user := &model.User{
|
||||
ID: gothUser.UserID,
|
||||
Provider: gothUser.Provider,
|
||||
AccessToken: gothUser.AccessToken,
|
||||
IDToken: gothUser.IDToken,
|
||||
}
|
||||
|
||||
if err := h.storeSessionUser(w, r, user); err != nil {
|
||||
slog.ErrorContext(r.Context(), "could not store session user", slog.Any("error", errors.WithStack(err)))
|
||||
http.Redirect(w, r, "/auth/logout", http.StatusTemporaryRedirect)
|
||||
return
|
||||
|
@ -1,11 +1,12 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/core/model"
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/markbates/goth"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -14,7 +15,11 @@ const tenantIDAttr = "t"
|
||||
|
||||
var errSessionNotFound = errors.New("session not found")
|
||||
|
||||
func (h *Handler) storeSessionUser(w http.ResponseWriter, r *http.Request, user *goth.User) error {
|
||||
func init() {
|
||||
gob.Register(&model.User{})
|
||||
}
|
||||
|
||||
func (h *Handler) storeSessionUser(w http.ResponseWriter, r *http.Request, user *model.User) error {
|
||||
sess, err := h.getSession(r)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
@ -29,13 +34,13 @@ func (h *Handler) storeSessionUser(w http.ResponseWriter, r *http.Request, user
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) retrieveSessionUser(r *http.Request) (*goth.User, error) {
|
||||
func (h *Handler) retrieveSessionUser(r *http.Request) (*model.User, error) {
|
||||
sess, err := h.getSession(r)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
user, ok := sess.Values[userAttr].(*goth.User)
|
||||
user, ok := sess.Values[userAttr].(*model.User)
|
||||
if !ok {
|
||||
return nil, errors.WithStack(errSessionNotFound)
|
||||
}
|
||||
|
@ -16,3 +16,15 @@ body {
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.htmx-indicator {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.htmx-request .htmx-indicator {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.htmx-request.htmx-indicator {
|
||||
display: block;
|
||||
}
|
||||
|
@ -42,18 +42,99 @@ templ FormTextarea(form *form.Form, id string, name string, label string) {
|
||||
}
|
||||
}
|
||||
|
||||
templ FormSelect(form *form.Form, id string, name string, label string, kvOptions ...string) {
|
||||
type FormSelectOptions struct {
|
||||
Options []formSelectOption
|
||||
Attrs map[string]any
|
||||
}
|
||||
|
||||
type FormSelectOptionFunc func(opts *FormSelectOptions)
|
||||
|
||||
func WithOptions(kvOptions ...string) FormSelectOptionFunc {
|
||||
return func(opts *FormSelectOptions) {
|
||||
opts.Options = keyValuesToOptions(kvOptions)
|
||||
}
|
||||
}
|
||||
|
||||
func WithAttrs(kvAttrs ...any) FormSelectOptionFunc {
|
||||
return func(opts *FormSelectOptions) {
|
||||
opts.Attrs = keyValuesToAttrs(kvAttrs)
|
||||
}
|
||||
}
|
||||
|
||||
func keyValuesToAttrs(kv []any) map[string]any {
|
||||
if len(kv)%2 != 0 {
|
||||
panic(errors.New("expected pair number of key/values"))
|
||||
}
|
||||
|
||||
attrs := make(map[string]any, 0)
|
||||
|
||||
var key string
|
||||
for idx := range kv {
|
||||
if idx%2 == 0 {
|
||||
key = kv[idx].(string)
|
||||
continue
|
||||
}
|
||||
|
||||
attrs[key] = kv[idx]
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
type formSelectOption struct {
|
||||
Value string
|
||||
Label string
|
||||
}
|
||||
|
||||
func keyValuesToOptions(kv []string) []formSelectOption {
|
||||
if len(kv)%2 != 0 {
|
||||
panic(errors.New("expected pair number of key/values"))
|
||||
}
|
||||
|
||||
options := make([]formSelectOption, 0)
|
||||
|
||||
var key string
|
||||
for idx := range kv {
|
||||
if idx%2 == 0 {
|
||||
key = kv[idx]
|
||||
continue
|
||||
}
|
||||
|
||||
options = append(options, formSelectOption{
|
||||
Value: kv[idx],
|
||||
Label: key,
|
||||
})
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
func newFormSelectOptions(funcs ...FormSelectOptionFunc) *FormSelectOptions {
|
||||
opts := &FormSelectOptions{}
|
||||
for _, fn := range funcs {
|
||||
fn(opts)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
templ FormSelect(form *form.Form, id string, name string, label string, funcs ...FormSelectOptionFunc) {
|
||||
{{ opts := newFormSelectOptions(funcs...) }}
|
||||
{{ field := form.Field(name) }}
|
||||
if field != nil {
|
||||
<div class="field">
|
||||
<label class="label" for={ id }>{ label }</label>
|
||||
<div class="control">
|
||||
{{ err, hasErr := form.Error(name) }}
|
||||
{{ value, hasValue := field.Get("value") }}
|
||||
<div class="select is-fullwidth">
|
||||
<select id={ id } name={ field.Name() } { field.Attrs()... }>
|
||||
{{ options := keyValuesToOptions(kvOptions) }}
|
||||
for _, o := range options {
|
||||
<option value={ o.Value }>{ o.Label }</option>
|
||||
<select id={ id } name={ field.Name() } { mergeAttrs(field.Attrs(), opts.Attrs)... }>
|
||||
for _, o := range opts.Options {
|
||||
<option
|
||||
if hasValue && value == o.Value {
|
||||
selected
|
||||
}
|
||||
value={ o.Value }
|
||||
>{ o.Label }</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
@ -65,30 +146,12 @@ templ FormSelect(form *form.Form, id string, name string, label string, kvOption
|
||||
}
|
||||
}
|
||||
|
||||
type SelectOption struct {
|
||||
Value string
|
||||
Label string
|
||||
}
|
||||
|
||||
func keyValuesToOptions(kv []string) []SelectOption {
|
||||
if len(kv)%2 != 0 {
|
||||
panic(errors.New("expected pair number of key/values"))
|
||||
}
|
||||
|
||||
options := make([]SelectOption, 0)
|
||||
|
||||
var key string
|
||||
for idx := range kv {
|
||||
if idx%2 == 0 {
|
||||
key = kv[idx]
|
||||
continue
|
||||
func mergeAttrs(attrs ...map[string]any) map[string]any {
|
||||
merged := make(form.Attrs)
|
||||
for _, a := range attrs {
|
||||
for k, v := range a {
|
||||
merged[k] = v
|
||||
}
|
||||
|
||||
options = append(options, SelectOption{
|
||||
Value: kv[idx],
|
||||
Label: key,
|
||||
})
|
||||
}
|
||||
|
||||
return options
|
||||
return merged
|
||||
}
|
||||
|
@ -327,7 +327,82 @@ func FormTextarea(form *form.Form, id string, name string, label string) templ.C
|
||||
})
|
||||
}
|
||||
|
||||
func FormSelect(form *form.Form, id string, name string, label string, kvOptions ...string) templ.Component {
|
||||
type FormSelectOptions struct {
|
||||
Options []formSelectOption
|
||||
Attrs map[string]any
|
||||
}
|
||||
|
||||
type FormSelectOptionFunc func(opts *FormSelectOptions)
|
||||
|
||||
func WithOptions(kvOptions ...string) FormSelectOptionFunc {
|
||||
return func(opts *FormSelectOptions) {
|
||||
opts.Options = keyValuesToOptions(kvOptions)
|
||||
}
|
||||
}
|
||||
|
||||
func WithAttrs(kvAttrs ...any) FormSelectOptionFunc {
|
||||
return func(opts *FormSelectOptions) {
|
||||
opts.Attrs = keyValuesToAttrs(kvAttrs)
|
||||
}
|
||||
}
|
||||
|
||||
func keyValuesToAttrs(kv []any) map[string]any {
|
||||
if len(kv)%2 != 0 {
|
||||
panic(errors.New("expected pair number of key/values"))
|
||||
}
|
||||
|
||||
attrs := make(map[string]any, 0)
|
||||
|
||||
var key string
|
||||
for idx := range kv {
|
||||
if idx%2 == 0 {
|
||||
key = kv[idx].(string)
|
||||
continue
|
||||
}
|
||||
|
||||
attrs[key] = kv[idx]
|
||||
}
|
||||
|
||||
return attrs
|
||||
}
|
||||
|
||||
type formSelectOption struct {
|
||||
Value string
|
||||
Label string
|
||||
}
|
||||
|
||||
func keyValuesToOptions(kv []string) []formSelectOption {
|
||||
if len(kv)%2 != 0 {
|
||||
panic(errors.New("expected pair number of key/values"))
|
||||
}
|
||||
|
||||
options := make([]formSelectOption, 0)
|
||||
|
||||
var key string
|
||||
for idx := range kv {
|
||||
if idx%2 == 0 {
|
||||
key = kv[idx]
|
||||
continue
|
||||
}
|
||||
|
||||
options = append(options, formSelectOption{
|
||||
Value: kv[idx],
|
||||
Label: key,
|
||||
})
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
func newFormSelectOptions(funcs ...FormSelectOptionFunc) *FormSelectOptions {
|
||||
opts := &FormSelectOptions{}
|
||||
for _, fn := range funcs {
|
||||
fn(opts)
|
||||
}
|
||||
return opts
|
||||
}
|
||||
|
||||
func FormSelect(form *form.Form, id string, name string, label string, funcs ...FormSelectOptionFunc) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
@ -348,6 +423,7 @@ func FormSelect(form *form.Form, id string, name string, label string, kvOptions
|
||||
templ_7745c5c3_Var20 = templ.NopComponent
|
||||
}
|
||||
ctx = templ.ClearChildren(ctx)
|
||||
opts := newFormSelectOptions(funcs...)
|
||||
field := form.Field(name)
|
||||
if field != nil {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "<div class=\"field\"><label class=\"label\" for=\"")
|
||||
@ -357,7 +433,7 @@ func FormSelect(form *form.Form, id string, name string, label string, kvOptions
|
||||
var templ_7745c5c3_Var21 string
|
||||
templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(id)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/field.templ`, Line: 49, Col: 32}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/field.templ`, Line: 125, Col: 32}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -370,7 +446,7 @@ func FormSelect(form *form.Form, id string, name string, label string, kvOptions
|
||||
var templ_7745c5c3_Var22 string
|
||||
templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(label)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/field.templ`, Line: 49, Col: 42}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/field.templ`, Line: 125, Col: 42}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -381,6 +457,7 @@ func FormSelect(form *form.Form, id string, name string, label string, kvOptions
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
err, hasErr := form.Error(name)
|
||||
value, hasValue := field.Get("value")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "<div class=\"select is-fullwidth\"><select id=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
@ -388,7 +465,7 @@ func FormSelect(form *form.Form, id string, name string, label string, kvOptions
|
||||
var templ_7745c5c3_Var23 string
|
||||
templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(id)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/field.templ`, Line: 53, Col: 20}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/field.templ`, Line: 130, Col: 20}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -401,7 +478,7 @@ func FormSelect(form *form.Form, id string, name string, label string, kvOptions
|
||||
var templ_7745c5c3_Var24 string
|
||||
templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(field.Name())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/field.templ`, Line: 53, Col: 42}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/field.templ`, Line: 130, Col: 42}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
@ -411,7 +488,7 @@ func FormSelect(form *form.Form, id string, name string, label string, kvOptions
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, field.Attrs())
|
||||
templ_7745c5c3_Err = templ.RenderAttributes(ctx, templ_7745c5c3_Buffer, mergeAttrs(field.Attrs(), opts.Attrs))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -419,63 +496,72 @@ func FormSelect(form *form.Form, id string, name string, label string, kvOptions
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
options := keyValuesToOptions(kvOptions)
|
||||
for _, o := range options {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<option value=\"")
|
||||
for _, o := range opts.Options {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "<option")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if hasValue && value == o.Value {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, " selected")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, " value=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var25 string
|
||||
templ_7745c5c3_Var25, templ_7745c5c3_Err = templ.JoinStringErrs(o.Value)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/field.templ`, Line: 56, Col: 30}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/field.templ`, Line: 136, Col: 23}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var25))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 34, "\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var26 string
|
||||
templ_7745c5c3_Var26, templ_7745c5c3_Err = templ.JoinStringErrs(o.Label)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/field.templ`, Line: 56, Col: 42}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/field.templ`, Line: 137, Col: 17}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var26))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 35, "</option>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "</option>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "</select></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</select></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
if hasErr {
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "<p class=\"help is-danger\">")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "<p class=\"help is-danger\">")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var27 string
|
||||
templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(err.Message())
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/field.templ`, Line: 61, Col: 46}
|
||||
return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/http/handler/webui/common/component/field.templ`, Line: 142, Col: 46}
|
||||
}
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "</p>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "</p>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "</div></div>")
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "</div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
@ -484,32 +570,14 @@ func FormSelect(form *form.Form, id string, name string, label string, kvOptions
|
||||
})
|
||||
}
|
||||
|
||||
type SelectOption struct {
|
||||
Value string
|
||||
Label string
|
||||
}
|
||||
|
||||
func keyValuesToOptions(kv []string) []SelectOption {
|
||||
if len(kv)%2 != 0 {
|
||||
panic(errors.New("expected pair number of key/values"))
|
||||
}
|
||||
|
||||
options := make([]SelectOption, 0)
|
||||
|
||||
var key string
|
||||
for idx := range kv {
|
||||
if idx%2 == 0 {
|
||||
key = kv[idx]
|
||||
continue
|
||||
func mergeAttrs(attrs ...map[string]any) map[string]any {
|
||||
merged := make(form.Attrs)
|
||||
for _, a := range attrs {
|
||||
for k, v := range a {
|
||||
merged[k] = v
|
||||
}
|
||||
|
||||
options = append(options, SelectOption{
|
||||
Value: kv[idx],
|
||||
Label: key,
|
||||
})
|
||||
}
|
||||
|
||||
return options
|
||||
return merged
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
||||
|
@ -10,9 +10,9 @@ type PageOptionFunc func(opts *PageOptions)
|
||||
func WithTitle(title string) PageOptionFunc {
|
||||
return func(opts *PageOptions) {
|
||||
if title != "" {
|
||||
opts.Title = title + " | Rkvst"
|
||||
opts.Title = title + " | ClearCase"
|
||||
} else {
|
||||
opts.Title = "Rkvst"
|
||||
opts.Title = "ClearCase"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,9 +18,9 @@ type PageOptionFunc func(opts *PageOptions)
|
||||
func WithTitle(title string) PageOptionFunc {
|
||||
return func(opts *PageOptions) {
|
||||
if title != "" {
|
||||
opts.Title = title + " | Rkvst"
|
||||
opts.Title = title + " | ClearCase"
|
||||
} else {
|
||||
opts.Title = "Rkvst"
|
||||
opts.Title = "ClearCase"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
116
internal/http/handler/webui/issue/component/issue_page.templ
Normal file
116
internal/http/handler/webui/issue/component/issue_page.templ
Normal file
@ -0,0 +1,116 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/core/model"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/form"
|
||||
common "forge.cadoles.com/wpetit/clearcase/internal/http/handler/webui/common/component"
|
||||
)
|
||||
|
||||
type IssuePageVModel struct {
|
||||
SummaryForm *form.Form
|
||||
IssueForm *form.Form
|
||||
Projects []*model.Project
|
||||
SelectedProjectID string
|
||||
}
|
||||
|
||||
func NewIssueSummaryForm() *form.Form {
|
||||
return form.New(
|
||||
form.NewField(
|
||||
"project",
|
||||
form.Attrs{},
|
||||
form.NonEmpty("This field should not be empty"),
|
||||
),
|
||||
form.NewField(
|
||||
"summary",
|
||||
form.Attrs{
|
||||
"type": "textarea",
|
||||
"rows": "20",
|
||||
"placeholder": "Write a rapid description of the issue here...",
|
||||
},
|
||||
form.NonEmpty("This field should not be empty"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func NewIssueForm() *form.Form {
|
||||
return form.New(
|
||||
form.NewField(
|
||||
"content",
|
||||
form.Attrs{
|
||||
"type": "textarea",
|
||||
"rows": "25",
|
||||
},
|
||||
form.NonEmpty("This field should not be empty"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
templ IssuePage(vmodel IssuePageVModel) {
|
||||
@common.Page(common.WithTitle("New issue")) {
|
||||
<div class="container is-fluid">
|
||||
<section class="section">
|
||||
<div class="buttons is-right">
|
||||
<a class="button is-medium" href={ common.BaseURL(ctx, common.WithPath("/auth/logout")) }>Logout</a>
|
||||
</div>
|
||||
<div class="columns">
|
||||
<div class="column">
|
||||
<form id="summary-form" action={ common.CurrentURL(ctx) } method="post" hx-disabled-elt="#summary-form textarea, #summary-form select, #summary-form button" hx-indicator="#generation-progress">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<h2 class="title is-size-2 level-item">Describe your request</h2>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="buttons is-right level-item">
|
||||
<button type="submit" class="button is-primary is-large">
|
||||
<span class="icon">
|
||||
<i class="fa fa-robot"></i>
|
||||
</span>
|
||||
<span>Generate</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<progress id="generation-progress" class="htmx-indicator progress"></progress>
|
||||
@common.FormSelect(
|
||||
vmodel.SummaryForm, "issue-project", "project", "Project",
|
||||
common.WithOptions(projectsToOptions(vmodel.Projects)...),
|
||||
common.WithAttrs(
|
||||
"hx-get", string(common.CurrentURL(ctx, common.WithoutValues("project", "*"))),
|
||||
"hx-target", "body",
|
||||
"hx-push-url", "true",
|
||||
),
|
||||
)
|
||||
@common.FormTextarea(vmodel.SummaryForm, "issue-summary", "summary", "Summary")
|
||||
</form>
|
||||
</div>
|
||||
<div class="column">
|
||||
<div class="level">
|
||||
<div class="level-left">
|
||||
<h2 class="title is-size-2">Generated issue</h2>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
<div class="buttons is-right">
|
||||
<button disabled type="submit" class="button is-primary is-large">
|
||||
<span class="icon">
|
||||
<i class="fa fa-rocket"></i>
|
||||
</span>
|
||||
<span>Create issue</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@common.FormTextarea(vmodel.IssueForm, "issue-content", "content", "")
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
func projectsToOptions(projects []*model.Project) []string {
|
||||
options := make([]string, 0, len(projects)*2)
|
||||
for _, p := range projects {
|
||||
options = append(options, p.Label, p.ID)
|
||||
}
|
||||
return options
|
||||
}
|
157
internal/http/handler/webui/issue/component/issue_page_templ.go
Normal file
157
internal/http/handler/webui/issue/component/issue_page_templ.go
Normal file
@ -0,0 +1,157 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.819
|
||||
package component
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import (
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/core/model"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/form"
|
||||
common "forge.cadoles.com/wpetit/clearcase/internal/http/handler/webui/common/component"
|
||||
)
|
||||
|
||||
type IssuePageVModel struct {
|
||||
SummaryForm *form.Form
|
||||
IssueForm *form.Form
|
||||
Projects []*model.Project
|
||||
SelectedProjectID string
|
||||
}
|
||||
|
||||
func NewIssueSummaryForm() *form.Form {
|
||||
return form.New(
|
||||
form.NewField(
|
||||
"project",
|
||||
form.Attrs{},
|
||||
form.NonEmpty("This field should not be empty"),
|
||||
),
|
||||
form.NewField(
|
||||
"summary",
|
||||
form.Attrs{
|
||||
"type": "textarea",
|
||||
"rows": "20",
|
||||
"placeholder": "Write a rapid description of the issue here...",
|
||||
},
|
||||
form.NonEmpty("This field should not be empty"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func NewIssueForm() *form.Form {
|
||||
return form.New(
|
||||
form.NewField(
|
||||
"content",
|
||||
form.Attrs{
|
||||
"type": "textarea",
|
||||
"rows": "25",
|
||||
},
|
||||
form.NonEmpty("This field should not be empty"),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func IssuePage(vmodel IssuePageVModel) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
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 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "<div class=\"container is-fluid\"><section class=\"section\"><div class=\"buttons is-right\"><a class=\"button is-medium\" href=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var3 templ.SafeURL = common.BaseURL(ctx, common.WithPath("/auth/logout"))
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "\">Logout</a></div><div class=\"columns\"><div class=\"column\"><form id=\"summary-form\" action=\"")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
var templ_7745c5c3_Var4 templ.SafeURL = common.CurrentURL(ctx)
|
||||
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "\" method=\"post\" hx-disabled-elt=\"#summary-form textarea, #summary-form select, #summary-form button\" hx-indicator=\"#generation-progress\"><div class=\"level\"><div class=\"level-left\"><h2 class=\"title is-size-2 level-item\">Describe your request</h2></div><div class=\"level-right\"><div class=\"buttons is-right level-item\"><button type=\"submit\" class=\"button is-primary is-large\"><span class=\"icon\"><i class=\"fa fa-robot\"></i></span> <span>Generate</span></button></div></div></div><progress id=\"generation-progress\" class=\"htmx-indicator progress\"></progress>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = common.FormSelect(
|
||||
vmodel.SummaryForm, "issue-project", "project", "Project",
|
||||
common.WithOptions(projectsToOptions(vmodel.Projects)...),
|
||||
common.WithAttrs(
|
||||
"hx-get", string(common.CurrentURL(ctx, common.WithoutValues("project", "*"))),
|
||||
"hx-target", "body",
|
||||
"hx-push-url", "true",
|
||||
),
|
||||
).Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = common.FormTextarea(vmodel.SummaryForm, "issue-summary", "summary", "Summary").Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "</form></div><div class=\"column\"><div class=\"level\"><div class=\"level-left\"><h2 class=\"title is-size-2\">Generated issue</h2></div><div class=\"level-right\"><div class=\"buttons is-right\"><button disabled type=\"submit\" class=\"button is-primary is-large\"><span class=\"icon\"><i class=\"fa fa-rocket\"></i></span> <span>Create issue</span></button></div></div></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = common.FormTextarea(vmodel.IssueForm, "issue-content", "content", "").Render(ctx, templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "</div></div></section></div>")
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = common.Page(common.WithTitle("New issue")).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func projectsToOptions(projects []*model.Project) []string {
|
||||
options := make([]string, 0, len(projects)*2)
|
||||
for _, p := range projects {
|
||||
options = append(options, p.Label, p.ID)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
@ -1,8 +0,0 @@
|
||||
package component
|
||||
|
||||
import common "forge.cadoles.com/wpetit/clearcase/internal/http/handler/webui/common/component"
|
||||
|
||||
templ IssuePage(title string) {
|
||||
@common.Page(common.WithTitle(title)) {
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
// Code generated by templ - DO NOT EDIT.
|
||||
|
||||
// templ: version: v0.3.819
|
||||
package component
|
||||
|
||||
//lint:file-ignore SA4006 This context is only used if a nested component is present.
|
||||
|
||||
import "github.com/a-h/templ"
|
||||
import templruntime "github.com/a-h/templ/runtime"
|
||||
|
||||
import common "forge.cadoles.com/wpetit/clearcase/internal/http/handler/webui/common/component"
|
||||
|
||||
func IssuePage(title string) templ.Component {
|
||||
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
|
||||
return templ_7745c5c3_CtxErr
|
||||
}
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
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 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
|
||||
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
|
||||
templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)
|
||||
if !templ_7745c5c3_IsBuffer {
|
||||
defer func() {
|
||||
templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err == nil {
|
||||
templ_7745c5c3_Err = templ_7745c5c3_BufErr
|
||||
}
|
||||
}()
|
||||
}
|
||||
ctx = templ.InitializeContext(ctx)
|
||||
return nil
|
||||
})
|
||||
templ_7745c5c3_Err = common.Page(common.WithTitle(title)).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer)
|
||||
if templ_7745c5c3_Err != nil {
|
||||
return templ_7745c5c3_Err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
var _ = templruntime.GeneratedTemplate
|
@ -2,10 +2,13 @@ package issue
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/core/service"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
mux *http.ServeMux
|
||||
mux *http.ServeMux
|
||||
issueManager *service.IssueManager
|
||||
}
|
||||
|
||||
// ServeHTTP implements http.Handler.
|
||||
@ -13,12 +16,14 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
h.mux.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func NewHandler() *Handler {
|
||||
func NewHandler(issueManager *service.IssueManager) *Handler {
|
||||
h := &Handler{
|
||||
mux: http.NewServeMux(),
|
||||
mux: http.NewServeMux(),
|
||||
issueManager: issueManager,
|
||||
}
|
||||
|
||||
h.mux.HandleFunc("GET /", h.getIssuePage)
|
||||
h.mux.HandleFunc("POST /", h.handleIssueSummary)
|
||||
|
||||
return h
|
||||
}
|
||||
|
@ -1,7 +1,128 @@
|
||||
package issue
|
||||
|
||||
import "net/http"
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/core/service"
|
||||
httpCtx "forge.cadoles.com/wpetit/clearcase/internal/http/context"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/form"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/handler/webui/common"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/handler/webui/issue/component"
|
||||
"forge.cadoles.com/wpetit/clearcase/internal/http/url"
|
||||
"github.com/a-h/templ"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (h *Handler) getIssuePage(w http.ResponseWriter, r *http.Request) {
|
||||
vmodel, err := h.fillIssuePageVModel(r)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
issue := component.IssuePage(*vmodel)
|
||||
templ.Handler(issue).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) fillIssuePageVModel(r *http.Request) (*component.IssuePageVModel, error) {
|
||||
vmodel := &component.IssuePageVModel{
|
||||
SummaryForm: component.NewIssueSummaryForm(),
|
||||
IssueForm: component.NewIssueForm(),
|
||||
}
|
||||
|
||||
err := common.FillViewModel(
|
||||
r.Context(), vmodel, r,
|
||||
h.fillIssuePageProjects,
|
||||
h.fillIssuePageSelectedProject,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return vmodel, nil
|
||||
}
|
||||
|
||||
func (h *Handler) fillIssuePageProjects(ctx context.Context, vmodel *component.IssuePageVModel, r *http.Request) error {
|
||||
user := httpCtx.User(ctx)
|
||||
|
||||
projects, err := h.issueManager.GetUserProjects(ctx, user)
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
vmodel.Projects = projects
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) fillIssuePageSelectedProject(ctx context.Context, vmodel *component.IssuePageVModel, r *http.Request) error {
|
||||
project := r.URL.Query().Get("project")
|
||||
if project == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
vmodel.SelectedProjectID = project
|
||||
vmodel.SummaryForm.Field("project").Set("value", project)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Handler) handleIssueSummary(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
issueSummaryForm := component.NewIssueSummaryForm()
|
||||
|
||||
if err := issueSummaryForm.Handle(r); err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
vmodel, err := h.fillIssuePageVModel(r)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
vmodel.SummaryForm = issueSummaryForm
|
||||
|
||||
if errs := issueSummaryForm.Validate(); errs != nil {
|
||||
page := component.IssuePage(*vmodel)
|
||||
templ.Handler(page).ServeHTTP(w, r)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
projectID, err := form.FormFieldAttr[string](issueSummaryForm, "project", "value")
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
summary, err := form.FormFieldAttr[string](issueSummaryForm, "summary", "value")
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
issueContent, err := h.issueManager.GenerateIssue(ctx, httpCtx.User(ctx), projectID, summary)
|
||||
if err != nil {
|
||||
h.handleError(w, r, errors.WithStack(err))
|
||||
return
|
||||
}
|
||||
|
||||
vmodel.IssueForm.Field("content").Set("value", issueContent)
|
||||
|
||||
page := component.IssuePage(*vmodel)
|
||||
templ.Handler(page).ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func (h *Handler) handleError(w http.ResponseWriter, r *http.Request, err error) {
|
||||
if errors.Is(err, service.ErrForgeNotAvailable) {
|
||||
baseURL := url.Mutate(httpCtx.BaseURL(r.Context()), url.WithPath("/auth/logout"))
|
||||
http.Redirect(w, r, baseURL.String(), http.StatusTemporaryRedirect)
|
||||
return
|
||||
}
|
||||
|
||||
common.HandleError(w, r, errors.WithStack(err))
|
||||
}
|
||||
|
Reference in New Issue
Block a user