summaryrefslogtreecommitdiff
path: root/api
diff options
context:
space:
mode:
Diffstat (limited to 'api')
-rw-r--r--api/kennel/kennel.go238
-rw-r--r--api/serve.go35
2 files changed, 270 insertions, 3 deletions
diff --git a/api/kennel/kennel.go b/api/kennel/kennel.go
new file mode 100644
index 0000000..a68388d
--- /dev/null
+++ b/api/kennel/kennel.go
@@ -0,0 +1,238 @@
+package kennel
+
+import (
+ "encoding/json"
+ "log"
+ "net/http"
+ "strings"
+
+ "git.hatecomputers.club/hatecomputers/hatecomputers.club/adapters/files"
+ "git.hatecomputers.club/hatecomputers/hatecomputers.club/api/types"
+ "git.hatecomputers.club/hatecomputers/hatecomputers.club/database"
+ "git.hatecomputers.club/hatecomputers/hatecomputers.club/utils"
+)
+
+const MaxCatSize = 1024 * 100 // 60KB
+const CatsPath = "cats/"
+const CatsPrefix = "/uploads/cats/"
+const DefaultCatSpritesheet = "/static/img/cat_spritesheets/default.gif"
+const MaxUserCats = 15
+
+func ListUserCatsContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(success types.Continuation, failure types.Continuation) types.ContinuationChain {
+ userID := context.User.ID
+
+ cats, err := database.GetUserKennelCats(context.DBConn, userID)
+ if err != nil {
+ log.Println(err)
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+
+ (*context.TemplateData)["Cats"] = cats
+ return success(context, req, resp)
+ }
+}
+
+func CreateCatContinuation(fileAdapter files.FilesAdapter, maxUserCats int, maxCatSize int, catsPath string, catsPrefix string, defaultCatSpritesheet string) func(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(success types.Continuation, failure types.Continuation) types.ContinuationChain {
+ formErrors := types.BannerMessages{
+ Messages: []string{},
+ }
+
+ numCats, err := database.CountUserKennelCats(context.DBConn, context.User.ID)
+ if err != nil {
+ log.Println(err)
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+ if numCats >= maxUserCats {
+ formErrors.Messages = append(formErrors.Messages, "max cats reached for user")
+ }
+
+ err = req.ParseMultipartForm(int64(maxCatSize))
+ if err != nil {
+ formErrors.Messages = append(formErrors.Messages, "cat spritesheet too large")
+ }
+
+ catID := utils.RandomId()
+ spritesheetPath := catsPrefix + catID
+
+ if len(formErrors.Messages) == 0 {
+ file, _, err := req.FormFile("spritesheet")
+ if file != nil && err != nil {
+ formErrors.Messages = append(formErrors.Messages, "error uploading spritesheet")
+ } else if file != nil {
+ defer file.Close()
+ reader := http.MaxBytesReader(resp, file, int64(maxCatSize))
+ defer reader.Close()
+
+ _, err = fileAdapter.CreateFile(catsPath+catID, reader)
+ if err != nil {
+ log.Println(err)
+ formErrors.Messages = append(formErrors.Messages, "error saving spritesheet (is it too big?)")
+ }
+ } else if file == nil && err != nil {
+ spritesheetPath = defaultCatSpritesheet
+ }
+ }
+
+ link := req.FormValue("link")
+ description := req.FormValue("description")
+ name := req.FormValue("name")
+
+ cat := &database.KennelCat{
+ ID: catID,
+ UserID: context.User.ID,
+ Name: name,
+ Link: link,
+ Description: description,
+ Spritesheet: spritesheetPath,
+ }
+ formErrors.Messages = append(formErrors.Messages, validateCat(cat)...)
+ if len(formErrors.Messages) == 0 {
+ _, err := database.SaveKennelCat(context.DBConn, cat)
+ if err != nil {
+ log.Println(err)
+ formErrors.Messages = append(formErrors.Messages, "failed to save cat")
+ }
+ }
+
+ if len(formErrors.Messages) > 0 {
+ (*context.TemplateData)["Error"] = formErrors
+ (*context.TemplateData)["CatForm"] = cat
+ resp.WriteHeader(http.StatusBadRequest)
+
+ return failure(context, req, resp)
+ }
+
+ formSuccess := types.BannerMessages{
+ Messages: []string{"cat added."},
+ }
+ (*context.TemplateData)["Success"] = formSuccess
+ return success(context, req, resp)
+ }
+ }
+}
+
+func RemoveCatContinuation(fileAdapter files.FilesAdapter, catsPath string) func(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(success types.Continuation, failure types.Continuation) types.ContinuationChain {
+ catID := req.FormValue("id")
+
+ cat, err := database.GetKennelCat(context.DBConn, catID)
+ if err != nil {
+ log.Println(err)
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+ if cat == nil || cat.UserID != context.User.ID {
+ resp.WriteHeader(http.StatusUnauthorized)
+ return failure(context, req, resp)
+ }
+
+ err = database.DeleteKennelCat(context.DBConn, catID)
+ if err != nil {
+ log.Println(err)
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+
+ err = fileAdapter.DeleteFile(catsPath + catID)
+ if err != nil && fileAdapter.FileExists(catsPath+catID) {
+ log.Println(err)
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+
+ return success(context, req, resp)
+ }
+ }
+}
+
+func RingContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(success types.Continuation, failure types.Continuation) types.ContinuationChain {
+ order := req.URL.Query().Get("order")
+
+ if order == "random" {
+ kennelCat, err := database.GetRandomKennelCat(context.DBConn)
+ if err != nil {
+ log.Println(err)
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+ http.Redirect(resp, req, kennelCat.Link, http.StatusFound)
+ return success(context, req, resp)
+ }
+
+ id := req.URL.Query().Get("id")
+ if id == "" {
+ resp.WriteHeader(http.StatusBadRequest)
+ return failure(context, req, resp)
+ }
+ if order != "random" && order != "next" && order != "prev" {
+ kennelCat, err := database.GetKennelCat(context.DBConn, id)
+ if err != nil {
+ log.Println(err)
+ resp.WriteHeader(http.StatusNotFound)
+ return failure(context, req, resp)
+ }
+ http.Redirect(resp, req, kennelCat.Link, http.StatusFound)
+ return success(context, req, resp)
+ }
+
+ nextCat, err := database.GetNextKennelCat(context.DBConn, id, order == "next")
+ if err != nil {
+ log.Println(err)
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+
+ http.Redirect(resp, req, nextCat.Link, http.StatusFound)
+ return success(context, req, resp)
+ }
+}
+
+func GetKennelContinuation(context *types.RequestContext, req *http.Request, resp http.ResponseWriter) types.ContinuationChain {
+ return func(success types.Continuation, failure types.Continuation) types.ContinuationChain {
+ cats, err := database.GetKennel(context.DBConn)
+ if err != nil {
+ log.Println(err)
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+ json, err := json.Marshal(cats)
+ if err != nil {
+ log.Println(err)
+ resp.WriteHeader(http.StatusInternalServerError)
+ return failure(context, req, resp)
+ }
+
+ resp.Header().Set("Content-Type", "application/json")
+ resp.Write(json)
+ return success(context, req, resp)
+ }
+}
+
+func validateCat(cat *database.KennelCat) []string {
+ errors := []string{}
+
+ if cat.Name == "" {
+ errors = append(errors, "name is required")
+ }
+ if cat.Link == "" {
+ errors = append(errors, "link is required")
+ }
+ if !strings.HasPrefix(cat.Link, "http://") && !strings.HasPrefix(cat.Link, "https://") {
+ errors = append(errors, "link must be a valid URL")
+ }
+ if cat.Description == "" {
+ errors = append(errors, "description is required")
+ }
+ if len(cat.Description) > 100 {
+ errors = append(errors, "description must be less than 100 characters")
+ }
+
+ return errors
+}
diff --git a/api/serve.go b/api/serve.go
index ca8142b..e205ce5 100644
--- a/api/serve.go
+++ b/api/serve.go
@@ -13,6 +13,7 @@ import (
"git.hatecomputers.club/hatecomputers/hatecomputers.club/api/dns"
"git.hatecomputers.club/hatecomputers/hatecomputers.club/api/guestbook"
"git.hatecomputers.club/hatecomputers/hatecomputers.club/api/hcaptcha"
+ "git.hatecomputers.club/hatecomputers/hatecomputers.club/api/kennel"
"git.hatecomputers.club/hatecomputers/hatecomputers.club/api/keys"
"git.hatecomputers.club/hatecomputers/hatecomputers.club/api/profiles"
"git.hatecomputers.club/hatecomputers/hatecomputers.club/api/template"
@@ -172,10 +173,38 @@ func MakeServer(argv *args.Arguments, dbConn *sql.DB) *http.Server {
LogRequestContinuation(requestContext, r, w)(auth.VerifySessionContinuation, FailurePassingContinuation)(hcaptcha.CaptchaVerificationContinuation, hcaptcha.CaptchaVerificationContinuation)(guestbook.SignGuestbookContinuation, FailurePassingContinuation)(guestbook.ListGuestbookContinuation, guestbook.ListGuestbookContinuation)(hcaptcha.CaptchaArgsContinuation, hcaptcha.CaptchaArgsContinuation)(template.TemplateContinuation("guestbook.html", true), template.TemplateContinuation("guestbook.html", true))(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
})
- mux.HandleFunc("GET /{name}", func(w http.ResponseWriter, r *http.Request) {
+ mux.HandleFunc("GET /kennel/", func(w http.ResponseWriter, r *http.Request) {
requestContext := makeRequestContext()
- name := r.PathValue("name")
- LogRequestContinuation(requestContext, r, w)(auth.VerifySessionContinuation, FailurePassingContinuation)(IdContinuation, IdContinuation)(template.TemplateContinuation(name+".html", true), FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ LogRequestContinuation(requestContext, r, w)(kennel.GetKennelContinuation, FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /kennel/cat", func(w http.ResponseWriter, r *http.Request) {
+ requestContext := makeRequestContext()
+ LogRequestContinuation(requestContext, r, w)(kennel.RingContinuation, FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /kennel/cats", func(w http.ResponseWriter, r *http.Request) {
+ requestContext := makeRequestContext()
+ LogRequestContinuation(requestContext, r, w)(auth.VerifySessionContinuation, FailurePassingContinuation)(kennel.ListUserCatsContinuation, auth.GoLoginContinuation)(template.TemplateContinuation("kennel_cats.html", true), FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("POST /kennel/cats", func(w http.ResponseWriter, r *http.Request) {
+ requestContext := makeRequestContext()
+ createCatContinuation := kennel.CreateCatContinuation(uploadAdapter, kennel.MaxUserCats, kennel.MaxCatSize, kennel.CatsPath, kennel.CatsPrefix, kennel.DefaultCatSpritesheet)
+ LogRequestContinuation(requestContext, r, w)(auth.VerifySessionContinuation, FailurePassingContinuation)(createCatContinuation, FailurePassingContinuation)(kennel.ListUserCatsContinuation, kennel.ListUserCatsContinuation)(template.TemplateContinuation("kennel_cats.html", true), template.TemplateContinuation("kennel_cats.html", true))(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("POST /kennel/cats/delete", func(w http.ResponseWriter, r *http.Request) {
+ requestContext := makeRequestContext()
+ deleteCatContinuation := kennel.RemoveCatContinuation(uploadAdapter, kennel.CatsPath)
+
+ LogRequestContinuation(requestContext, r, w)(auth.VerifySessionContinuation, FailurePassingContinuation)(deleteCatContinuation, FailurePassingContinuation)(kennel.ListUserCatsContinuation, FailurePassingContinuation)(template.TemplateContinuation("kennel_cats.html", true), FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
+ })
+
+ mux.HandleFunc("GET /{template}", func(w http.ResponseWriter, r *http.Request) {
+ requestContext := makeRequestContext()
+ templateFile := r.PathValue("template")
+ LogRequestContinuation(requestContext, r, w)(auth.VerifySessionContinuation, FailurePassingContinuation)(IdContinuation, IdContinuation)(template.TemplateContinuation(templateFile+".html", true), FailurePassingContinuation)(LogExecutionTimeContinuation, LogExecutionTimeContinuation)(IdContinuation, IdContinuation)
})
return &http.Server{