package contenttype
import (
"fmt"
"log"
"net/http"
"strconv"
"strings"
"git.sr.ht/~evanj/cms/internal/c"
"git.sr.ht/~evanj/cms/internal/m/content"
"git.sr.ht/~evanj/cms/internal/m/contenttype"
"git.sr.ht/~evanj/cms/internal/m/space"
"git.sr.ht/~evanj/cms/internal/m/user"
"git.sr.ht/~evanj/cms/internal/s/db"
"git.sr.ht/~evanj/cms/internal/s/tmpl"
)
var (
contenttypeHTML = tmpl.MustParse("html/contenttype.html")
)
type ContentType struct {
*c.Controller
log *log.Logger
db dber
}
type dber interface {
UserGet(username, password string) (user.User, error)
UserGetFromToken(token string) (user.User, error)
SpaceGet(user user.User, spaceID string) (space.Space, error)
ContentTypeNew(space space.Space, name string, params []db.ContentTypeNewParam) (contenttype.ContentType, error)
ContentTypeGet(space space.Space, contenttypeID string) (contenttype.ContentType, error)
ContentTypeUpdate(space space.Space, contenttype contenttype.ContentType, name string, newParams []db.ContentTypeNewParam, updateParams []db.ContentTypeUpdateParam) (contenttype.ContentType, error)
ContentTypeDelete(space space.Space, ct contenttype.ContentType) error
ContentTypeSearch(space space.Space, query string, page int) ([]contenttype.ContentType, error)
ContentPerContentType(space space.Space, ct contenttype.ContentType, before int, order db.OrderType, sortField string) (content.ContentList, error)
}
func New(log *log.Logger, db dber) *ContentType {
return &ContentType{c.New(log, db), log, db}
}
func (c *ContentType) tree(w http.ResponseWriter, r *http.Request) (user.User, space.Space, error) {
user, err := c.GetCookieUser(w, r)
if err != nil {
return nil, nil, fmt.Errorf("must be logged in to perform this action")
}
spaceID := r.FormValue("space")
space, err := c.db.SpaceGet(user, spaceID)
if err != nil {
return nil, nil, fmt.Errorf("failed to retrieve space for id %s", spaceID)
}
return user, space, nil
}
func (c *ContentType) create(w http.ResponseWriter, r *http.Request) {
name := r.FormValue("name")
i := 1
for key := range r.Form {
if strings.Contains(key, "field_type_") || strings.Contains(key, "field_name_") {
i++
}
}
var params []db.ContentTypeNewParam
for e := 0; e < (i / 2); e++ {
keyName := fmt.Sprintf("field_name_%d", e+1)
keyType := fmt.Sprintf("field_type_%d", e+1)
valName := r.FormValue(keyName)
valType := r.FormValue(keyType)
if valName == "" || valType == "" {
c.Error(w, r, http.StatusBadRequest, "form has malformed data")
return
}
params = append(params, db.ContentTypeNewParam{
Name: valName,
Type: valType,
})
}
if len(params) < 1 {
c.Error(w, r, http.StatusBadRequest, "contenttype must have at least one field")
return
}
// Enforce content always has a value for value type of "name"
hasName := false
for _, p := range params {
if p.Name == "name" {
hasName = true
}
}
if !hasName {
c.Error(w, r, http.StatusInternalServerError, "must have field of \"name\" for searchability")
return
}
user, space, err := c.tree(w, r)
if err != nil {
c.Error(w, r, http.StatusBadRequest, err.Error())
return
}
ct, err := c.db.ContentTypeNew(space, name, params)
if err != nil {
c.log.Println(err)
c.Error(w, r, http.StatusInternalServerError, "failed to create contenttype")
return
}
url := fmt.Sprintf("/contenttype/%s/%s", space.ID(), ct.ID())
c.log.Println("successfully created new contenttype for user", user.Name(), "in space", space.Name(), "redirecting to", url)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func (c *ContentType) update(w http.ResponseWriter, r *http.Request) {
name := r.FormValue("name")
ctID := r.FormValue("contenttype")
user, space, err := c.tree(w, r)
if err != nil {
c.Error(w, r, http.StatusBadRequest, err.Error())
return
}
old, err := c.db.ContentTypeGet(space, ctID)
if err != nil {
c.Error(w, r, http.StatusInternalServerError, "failed to find required contenttype")
return
}
// Gather new params.
i := 1
for key := range r.Form {
if strings.Contains(key, "field_type_") || strings.Contains(key, "field_name_") {
i++
}
}
var newParams []db.ContentTypeNewParam
for e := 0; e < (i / 2); e++ {
keyName := fmt.Sprintf("field_name_%d", e+2)
keyType := fmt.Sprintf("field_type_%d", e+2)
valName := r.FormValue(keyName)
valType := r.FormValue(keyType)
if valName == "" || valType == "" {
c.Error(w, r, http.StatusBadRequest, "form has malformed data")
return
}
newParams = append(newParams, db.ContentTypeNewParam{
Name: valName,
Type: valType,
})
}
// Gather updated params.
var updateParams []db.ContentTypeUpdateParam
for e := 0; e < len(old.Fields()); e++ {
keyID := fmt.Sprintf("field_update_id_%d", e+1)
keyName := fmt.Sprintf("field_update_name_%d", e+1)
keyType := fmt.Sprintf("field_update_type_%d", e+1)
valID := r.FormValue(keyID)
valName := r.FormValue(keyName)
valType := r.FormValue(keyType)
if valName == "" && valType == "" && valID == "" {
continue // We're removing a field.
}
if valName == "" || valType == "" || valID == "" {
c.Error(w, r, http.StatusBadRequest, "form has malformed data")
return
}
updateParams = append(updateParams, db.ContentTypeUpdateParam{
ID: valID,
Name: valName,
Type: valType,
})
}
if len(updateParams) < 1 {
c.Error(w, r, http.StatusBadRequest, "contenttype must have at least one field")
return
}
// Enforce content always has a value for value type of "name"
hasName := false
for _, p := range updateParams {
if p.Name == "name" {
hasName = true
}
}
if !hasName {
c.Error(w, r, http.StatusInternalServerError, "must have field of \"name\" for searchability")
return
}
ct, err := c.db.ContentTypeGet(space, ctID)
if err != nil {
c.Error(w, r, http.StatusInternalServerError, "failed to find required contenttype")
return
}
ct, err = c.db.ContentTypeUpdate(space, ct, name, newParams, updateParams)
if err != nil {
c.log.Println(err)
c.Error(w, r, http.StatusInternalServerError, "failed to create contenttype")
return
}
url := fmt.Sprintf("/contenttype/%s/%s", space.ID(), ct.ID())
c.log.Println("successfully updated contenttype for user", user.Name(), "in space", space.Name(), "redirecting to", url)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func (c *ContentType) serve(w http.ResponseWriter, r *http.Request, spaceID, contenttypeID string) {
user, err := c.GetCookieUser(w, r)
if err != nil {
c.Error(w, r, http.StatusBadRequest, "must be logged in")
return
}
space, err := c.db.SpaceGet(user, spaceID)
if err != nil {
c.Error(w, r, http.StatusInternalServerError, "failed to find required space")
return
}
ct, err := c.db.ContentTypeGet(space, contenttypeID)
if err != nil {
c.Error(w, r, http.StatusInternalServerError, "failed to find desired contenttype")
return
}
// How to order by.
var o db.OrderType
switch r.FormValue("order") {
case "asc":
o = db.OrderAsc
case "desc":
o = db.OrderDesc
case "":
o = db.OrderAsc
default:
c.Error(w, r, http.StatusBadRequest, "invalid order value")
return
}
// Order by this field.
f := r.FormValue("field")
if f == "" {
f = "name" // All guaranteed to have.
}
before, _ := strconv.Atoi(r.URL.Query().Get("before"))
list, err := c.db.ContentPerContentType(space, ct, before, o, f)
if err != nil {
c.log.Println(err)
c.Error(w, r, http.StatusInternalServerError, "failed to find content for contenttype")
return
}
c.HTML(w, r, contenttypeHTML, map[string]interface{}{
"User": user,
"Space": space,
"ContentType": ct,
"ContentList": list,
})
}
func (c *ContentType) delete(w http.ResponseWriter, r *http.Request) {
spaceID := r.FormValue("space")
contenttypeID := r.FormValue("contenttype")
user, err := c.GetCookieUser(w, r)
if err != nil {
c.Error(w, r, http.StatusBadRequest, "must be logged in to create contenttype")
return
}
space, err := c.db.SpaceGet(user, spaceID)
if err != nil {
c.Error(w, r, http.StatusInternalServerError, "failed to find desired space")
return
}
ct, err := c.db.ContentTypeGet(space, contenttypeID)
if err != nil {
c.Error(w, r, http.StatusInternalServerError, "failed to find desired contenttype")
return
}
if err := c.db.ContentTypeDelete(space, ct); err != nil {
c.log.Println(err)
c.Error(w, r, http.StatusInternalServerError, "failed to delete contenttype")
return
}
url := fmt.Sprintf("/space/%s", space.ID())
c.log.Println("successfully deleted contenttype for user", user.Name(), "in space", space.Name(), "redirecting to", url)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func (c *ContentType) search(w http.ResponseWriter, r *http.Request) {
spaceID := r.FormValue("space")
query := r.FormValue("query")
page, err := strconv.Atoi(r.URL.Query().Get("page"))
if err != nil || page < 1 {
page = 1
}
page-- // Show one to user but start counting at zero for us.
user, err := c.GetCookieUser(w, r)
if err != nil {
c.Error(w, r, http.StatusBadRequest, "must be logged in to create contenttype")
return
}
space, err := c.db.SpaceGet(user, spaceID)
if err != nil {
c.Error(w, r, http.StatusInternalServerError, "failed to find desired space")
return
}
list, err := c.db.ContentTypeSearch(space, query, page)
if err != nil {
c.Error(w, r, http.StatusInternalServerError, "failed to find desired contenttype")
return
}
c.JSON(w, r, list)
}
func (c *ContentType) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/contenttype/new":
c.create(w, r)
return
case "/contenttype/update":
c.update(w, r)
return
case "/contenttype/delete":
c.delete(w, r)
return
case "/contenttype/search":
c.search(w, r)
return
}
parts := strings.Split(r.URL.Path, "/")
if len(parts) > 3 {
c.serve(w, r, parts[2], parts[3])
return
}
http.NotFound(w, r)
}