For now, this is almost identical to the campsite types, but this section is for purely informational pages that have no other relation to the database than “belongs to the same company”. Part of #33.
184 lines
4.6 KiB
Go
184 lines
4.6 KiB
Go
/*
|
|
* SPDX-FileCopyrightText: 2023 jordi fita mas <jfita@peritasoft.com>
|
|
* SPDX-License-Identifier: AGPL-3.0-only
|
|
*/
|
|
|
|
package page
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
|
|
"dev.tandem.ws/tandem/camper/pkg/auth"
|
|
"dev.tandem.ws/tandem/camper/pkg/database"
|
|
"dev.tandem.ws/tandem/camper/pkg/form"
|
|
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
|
"dev.tandem.ws/tandem/camper/pkg/locale"
|
|
"dev.tandem.ws/tandem/camper/pkg/template"
|
|
"dev.tandem.ws/tandem/camper/pkg/uuid"
|
|
)
|
|
|
|
type AdminHandler struct {
|
|
}
|
|
|
|
func NewAdminHandler() *AdminHandler {
|
|
return &AdminHandler{}
|
|
}
|
|
|
|
func (h *AdminHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var head string
|
|
head, r.URL.Path = httplib.ShiftPath(r.URL.Path)
|
|
switch head {
|
|
case "new":
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
f := newPageForm()
|
|
f.MustRender(w, r, user, company)
|
|
default:
|
|
httplib.MethodNotAllowed(w, r, http.MethodGet)
|
|
}
|
|
case "":
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
servePageIndex(w, r, user, company, conn)
|
|
case http.MethodPost:
|
|
f := newPageForm()
|
|
f.Handle(w, r, user, company, func(ctx context.Context) {
|
|
conn.MustExec(ctx, "select add_page($1, $2, $3)", company.ID, f.Title, f.Content)
|
|
})
|
|
default:
|
|
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPost)
|
|
}
|
|
default:
|
|
if !uuid.Valid(head) {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
f := newPageForm()
|
|
if err := f.FillFromDatabase(r.Context(), conn, head); err != nil {
|
|
if database.ErrorIsNotFound(err) {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
panic(err)
|
|
}
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
f.MustRender(w, r, user, company)
|
|
case http.MethodPut:
|
|
f.Handle(w, r, user, company, func(ctx context.Context) {
|
|
conn.MustExec(ctx, "select edit_page($1, $2, $3)", f.Slug, f.Title, f.Content)
|
|
})
|
|
default:
|
|
httplib.MethodNotAllowed(w, r, http.MethodGet, http.MethodPut)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func servePageIndex(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, conn *database.Conn) {
|
|
pages, err := collectPageEntries(r.Context(), company, conn)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
page := &pageIndex{
|
|
Pages: pages,
|
|
}
|
|
page.MustRender(w, r, user, company)
|
|
}
|
|
|
|
type pageEntry struct {
|
|
Slug string
|
|
Title string
|
|
}
|
|
|
|
func collectPageEntries(ctx context.Context, company *auth.Company, conn *database.Conn) ([]*pageEntry, error) {
|
|
rows, err := conn.Query(ctx, "select slug, title from page where company_id = $1", company.ID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
|
|
var types []*pageEntry
|
|
for rows.Next() {
|
|
entry := &pageEntry{}
|
|
if err = rows.Scan(&entry.Slug, &entry.Title); err != nil {
|
|
return nil, err
|
|
}
|
|
types = append(types, entry)
|
|
}
|
|
|
|
return types, nil
|
|
}
|
|
|
|
type pageIndex struct {
|
|
Pages []*pageEntry
|
|
}
|
|
|
|
func (index *pageIndex) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
|
template.MustRenderAdmin(w, r, user, company, "page/index.gohtml", index)
|
|
}
|
|
|
|
type pageForm struct {
|
|
Slug string
|
|
Title *form.Input
|
|
Content *form.Input
|
|
}
|
|
|
|
func newPageForm() *pageForm {
|
|
return &pageForm{
|
|
Title: &form.Input{
|
|
Name: "title",
|
|
},
|
|
Content: &form.Input{
|
|
Name: "content",
|
|
},
|
|
}
|
|
}
|
|
|
|
func (f *pageForm) FillFromDatabase(ctx context.Context, conn *database.Conn, slug string) error {
|
|
f.Slug = slug
|
|
row := conn.QueryRow(ctx, "select title, content from page where slug = $1", slug)
|
|
return row.Scan(&f.Title.Val, &f.Content.Val)
|
|
}
|
|
|
|
func (f *pageForm) Parse(r *http.Request) error {
|
|
if err := r.ParseForm(); err != nil {
|
|
return err
|
|
}
|
|
f.Title.FillValue(r)
|
|
f.Content.FillValue(r)
|
|
return nil
|
|
}
|
|
|
|
func (f *pageForm) Valid(l *locale.Locale) bool {
|
|
v := form.NewValidator(l)
|
|
v.CheckRequired(f.Title, l.GettextNoop("Title can not be empty."))
|
|
return v.AllOK
|
|
}
|
|
|
|
func (f *pageForm) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company) {
|
|
template.MustRenderAdmin(w, r, user, company, "page/form.gohtml", f)
|
|
}
|
|
|
|
func (f *pageForm) Handle(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, exec func(ctx context.Context)) {
|
|
if err := f.Parse(r); err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
if err := user.VerifyCSRFToken(r); err != nil {
|
|
http.Error(w, err.Error(), http.StatusForbidden)
|
|
return
|
|
}
|
|
if !f.Valid(user.Locale) {
|
|
if !httplib.IsHTMxRequest(r) {
|
|
w.WriteHeader(http.StatusUnprocessableEntity)
|
|
}
|
|
f.MustRender(w, r, user, company)
|
|
return
|
|
}
|
|
exec(r.Context())
|
|
httplib.Redirect(w, r, "/admin/pages", http.StatusSeeOther)
|
|
}
|