diff options
Diffstat (limited to 'system/admin/handlers.go')
-rw-r--r-- | system/admin/handlers.go | 486 |
1 files changed, 474 insertions, 12 deletions
diff --git a/system/admin/handlers.go b/system/admin/handlers.go index 9ff39c3..3ef0e7d 100644 --- a/system/admin/handlers.go +++ b/system/admin/handlers.go @@ -1,12 +1,18 @@ package admin import ( + "bytes" + "encoding/base64" "encoding/json" "fmt" "net/http" "strings" "time" + "github.com/nilslice/cms/content" + "github.com/nilslice/cms/management/editor" + "github.com/nilslice/cms/management/manager" + "github.com/nilslice/cms/system/admin/config" "github.com/nilslice/cms/system/admin/user" "github.com/nilslice/cms/system/db" "github.com/nilslice/jwt" @@ -24,6 +30,154 @@ func adminHandler(res http.ResponseWriter, req *http.Request) { res.Write(view) } +func initHandler(res http.ResponseWriter, req *http.Request) { + if db.SystemInitComplete() { + http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound) + return + } + + switch req.Method { + case http.MethodGet: + view, err := Init() + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + res.Header().Set("Content-Type", "text/html") + res.Write(view) + + case http.MethodPost: + err := req.ParseForm() + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + // get the site name from post to encode and use as secret + name := []byte(req.FormValue("name")) + secret := base64.StdEncoding.EncodeToString(name) + req.Form.Set("client_secret", secret) + + // generate an Etag to use for response caching + etag := db.NewEtag() + req.Form.Set("etag", etag) + + err = db.SetConfig(req.Form) + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + email := req.FormValue("email") + password := req.FormValue("password") + usr := user.NewUser(email, password) + + _, err = db.SetUser(usr) + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + // add _token cookie for login persistence + week := time.Now().Add(time.Hour * 24 * 7) + claims := map[string]interface{}{ + "exp": week.Unix(), + "user": usr.Email, + } + + jwt.Secret([]byte(secret)) + token, err := jwt.New(claims) + + http.SetCookie(res, &http.Cookie{ + Name: "_token", + Value: token, + Expires: week, + }) + + redir := strings.TrimSuffix(req.URL.String(), "/init") + http.Redirect(res, req, redir, http.StatusFound) + + default: + res.WriteHeader(http.StatusMethodNotAllowed) + } +} + +func configHandler(res http.ResponseWriter, req *http.Request) { + switch req.Method { + case http.MethodGet: + data, err := db.ConfigAll() + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + c := &config.Config{} + + err = json.Unmarshal(data, c) + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + cfg, err := c.MarshalEditor() + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + adminView, err := Admin(cfg) + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + res.Header().Set("Content-Type", "text/html") + res.Write(adminView) + + case http.MethodPost: + err := req.ParseForm() + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + err = db.SetConfig(req.Form) + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + http.Redirect(res, req, req.URL.String(), http.StatusFound) + + default: + res.WriteHeader(http.StatusMethodNotAllowed) + } + +} + +func configUsersHandler(res http.ResponseWriter, req *http.Request) { + switch req.Method { + case http.MethodGet: + // list all users and delete buttons + + case http.MethodPost: + // create new user + + default: + res.WriteHeader(http.StatusMethodNotAllowed) + } +} + func loginHandler(res http.ResponseWriter, req *http.Request) { if !db.SystemInitComplete() { redir := req.URL.Scheme + req.URL.Host + "/admin/init" @@ -57,25 +211,20 @@ func loginHandler(res http.ResponseWriter, req *http.Request) { err := req.ParseForm() if err != nil { fmt.Println(err) - res.WriteHeader(http.StatusInternalServerError) + http.Redirect(res, req, req.URL.String(), http.StatusFound) return } - fmt.Println(req.FormValue("email")) - fmt.Println(req.FormValue("password")) - // check email & password j, err := db.User(req.FormValue("email")) if err != nil { fmt.Println(err) - res.WriteHeader(http.StatusInternalServerError) + http.Redirect(res, req, req.URL.String(), http.StatusFound) return } if j == nil { - fmt.Println(err) - res.WriteHeader(http.StatusBadRequest) - fmt.Println("j == nil") + http.Redirect(res, req, req.URL.String(), http.StatusFound) return } @@ -83,13 +232,12 @@ func loginHandler(res http.ResponseWriter, req *http.Request) { err = json.Unmarshal(j, usr) if err != nil { fmt.Println(err) - res.WriteHeader(http.StatusInternalServerError) + http.Redirect(res, req, req.URL.String(), http.StatusFound) return } if !user.IsUser(usr, req.FormValue("password")) { - res.WriteHeader(http.StatusBadRequest) - fmt.Println("!IsUser") + http.Redirect(res, req, req.URL.String(), http.StatusFound) return } // create new token @@ -101,7 +249,7 @@ func loginHandler(res http.ResponseWriter, req *http.Request) { token, err := jwt.New(claims) if err != nil { fmt.Println(err) - res.WriteHeader(http.StatusInternalServerError) + http.Redirect(res, req, req.URL.String(), http.StatusFound) return } @@ -125,3 +273,317 @@ func logoutHandler(res http.ResponseWriter, req *http.Request) { http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin/login", http.StatusFound) } + +func postsHandler(res http.ResponseWriter, req *http.Request) { + q := req.URL.Query() + t := q.Get("type") + if t == "" { + res.WriteHeader(http.StatusBadRequest) + errView, err := Error405() + if err != nil { + return + } + + res.Write(errView) + return + } + + posts := db.ContentAll(t) + b := &bytes.Buffer{} + p, ok := content.Types[t]().(editor.Editable) + if !ok { + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + return + } + + res.Write(errView) + return + } + + html := `<div class="col s9 card"> + <div class="card-content"> + <div class="row"> + <div class="card-title col s7">` + t + ` Items</div> + <form class="col s5" action="/admin/posts/search" method="get"> + <div class="input-field post-search inline"> + <i class="right material-icons search-icon">search</i> + <input class="search" name="q" type="text" placeholder="Search for ` + t + ` content" class="search"/> + <input type="hidden" name="type" value="` + t + `" /> + </div> + </form> + </div> + <ul class="posts row">` + + for i := range posts { + json.Unmarshal(posts[i], &p) + post := `<li class="col s12"><a href="/admin/edit?type=` + + t + `&id=` + fmt.Sprintf("%d", p.ContentID()) + + `">` + p.ContentName() + `</a></li>` + b.Write([]byte(post)) + } + + b.Write([]byte(`</ul></div></div>`)) + + btn := `<div class="col s3"><a href="/admin/edit?type=` + t + `" class="btn new-post waves-effect waves-light">New ` + t + `</a></div></div>` + html = html + b.String() + btn + + adminView, err := Admin([]byte(html)) + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + res.Header().Set("Content-Type", "text/html") + res.Write(adminView) +} + +func editHandler(res http.ResponseWriter, req *http.Request) { + switch req.Method { + case http.MethodGet: + q := req.URL.Query() + i := q.Get("id") + t := q.Get("type") + contentType, ok := content.Types[t] + if !ok { + fmt.Fprintf(res, content.ErrTypeNotRegistered, t) + return + } + post := contentType() + + if i != "" { + data, err := db.Content(t + ":" + i) + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + return + } + + res.Write(errView) + return + } + + if len(data) < 1 || data == nil { + res.WriteHeader(http.StatusNotFound) + errView, err := Error404() + if err != nil { + return + } + + res.Write(errView) + return + } + + err = json.Unmarshal(data, post) + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + return + } + + res.Write(errView) + return + } + } else { + post.(editor.Editable).SetContentID(-1) + } + + m, err := manager.Manage(post.(editor.Editable), t) + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + return + } + + res.Write(errView) + return + } + + adminView, err := Admin(m) + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + res.Header().Set("Content-Type", "text/html") + res.Write(adminView) + + case http.MethodPost: + err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusBadRequest) + errView, err := Error405() + if err != nil { + return + } + + res.Write(errView) + return + } + + cid := req.FormValue("id") + t := req.FormValue("type") + ts := req.FormValue("timestamp") + + // create a timestamp if one was not set + date := make(map[string]int) + if ts == "" { + now := time.Now() + date["year"] = now.Year() + date["month"] = int(now.Month()) + date["day"] = now.Day() + + // create timestamp format 'yyyy-mm-dd' and set in PostForm for + // db insertion + ts = fmt.Sprintf("%d-%02d-%02d", date["year"], date["month"], date["day"]) + req.PostForm.Set("timestamp", ts) + } + + urlPaths, err := storeFileUploads(req) + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + return + } + + res.Write(errView) + return + } + + for name, urlPath := range urlPaths { + req.PostForm.Add(name, urlPath) + } + + // check for any multi-value fields (ex. checkbox fields) + // and correctly format for db storage. Essentially, we need + // fieldX.0: value1, fieldX.1: value2 => fieldX: []string{value1, value2} + var discardKeys []string + for k, v := range req.PostForm { + if strings.Contains(k, ".") { + key := strings.Split(k, ".")[0] + + if req.PostForm.Get(key) == "" { + req.PostForm.Set(key, v[0]) + discardKeys = append(discardKeys, k) + } else { + req.PostForm.Add(key, v[0]) + } + } + } + + for _, discardKey := range discardKeys { + req.PostForm.Del(discardKey) + } + + id, err := db.SetContent(t+":"+cid, req.PostForm) + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + errView, err := Error500() + if err != nil { + return + } + + res.Write(errView) + return + } + + scheme := req.URL.Scheme + host := req.URL.Host + path := req.URL.Path + sid := fmt.Sprintf("%d", id) + desURL := scheme + host + path + "?type=" + t + "&id=" + sid + http.Redirect(res, req, desURL, http.StatusFound) + + default: + res.WriteHeader(http.StatusMethodNotAllowed) + } +} + +func editUploadHandler(res http.ResponseWriter, req *http.Request) { + if req.Method != http.MethodPost { + res.WriteHeader(http.StatusMethodNotAllowed) + return + } + + urlPaths, err := storeFileUploads(req) + if err != nil { + fmt.Println("Couldn't store file uploads.", err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + res.Header().Set("Content-Type", "application/json") + res.Write([]byte(`{"data": [{"url": "` + urlPaths["file"] + `"}]}`)) +} + +func searchHandler(res http.ResponseWriter, req *http.Request) { + q := req.URL.Query() + t := q.Get("type") + search := q.Get("q") + + if t == "" || search == "" { + http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound) + return + } + + posts := db.ContentAll(t) + b := &bytes.Buffer{} + p := content.Types[t]().(editor.Editable) + + html := `<div class="col s9 card"> + <div class="card-content"> + <div class="row"> + <div class="card-title col s7">` + t + ` Results</div> + <form class="col s5" action="/admin/posts/search" method="get"> + <div class="input-field post-search inline"> + <i class="right material-icons search-icon">search</i> + <input class="search" name="q" type="text" placeholder="Search for ` + t + ` content" class="search"/> + <input type="hidden" name="type" value="` + t + `" /> + </div> + </form> + </div> + <ul class="posts row">` + + for i := range posts { + // skip posts that don't have any matching search criteria + match := strings.ToLower(search) + all := strings.ToLower(string(posts[i])) + if !strings.Contains(all, match) { + continue + } + + json.Unmarshal(posts[i], &p) + post := `<li class="col s12"><a href="/admin/edit?type=` + + t + `&id=` + fmt.Sprintf("%d", p.ContentID()) + + `">` + p.ContentName() + `</a></li>` + b.Write([]byte(post)) + } + + b.Write([]byte(`</ul></div></div>`)) + + btn := `<div class="col s3"><a href="/admin/edit?type=` + t + `" class="btn new-post waves-effect waves-light">New ` + t + `</a></div></div>` + html = html + b.String() + btn + + adminView, err := Admin([]byte(html)) + if err != nil { + fmt.Println(err) + res.WriteHeader(http.StatusInternalServerError) + return + } + + res.Header().Set("Content-Type", "text/html") + res.Write(adminView) +} |