github.com/rpdict/ponzu@v0.10.1-0.20190226054626-477f29d6bf5e/system/admin/handlers.go (about)

     1  package admin
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/base64"
     7  	"encoding/json"
     8  	"fmt"
     9  	"log"
    10  	"net/http"
    11  	"strconv"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/rpdict/ponzu/management/editor"
    16  	"github.com/rpdict/ponzu/management/format"
    17  	"github.com/rpdict/ponzu/management/manager"
    18  	"github.com/rpdict/ponzu/system/addon"
    19  	"github.com/rpdict/ponzu/system/admin/config"
    20  	"github.com/rpdict/ponzu/system/admin/upload"
    21  	"github.com/rpdict/ponzu/system/admin/user"
    22  	"github.com/rpdict/ponzu/system/api"
    23  	"github.com/rpdict/ponzu/system/api/analytics"
    24  	"github.com/rpdict/ponzu/system/db"
    25  	"github.com/rpdict/ponzu/system/item"
    26  	"github.com/rpdict/ponzu/system/search"
    27  
    28  	"github.com/gorilla/schema"
    29  	emailer "github.com/nilslice/email"
    30  	"github.com/nilslice/jwt"
    31  	"github.com/tidwall/gjson"
    32  )
    33  
    34  func adminHandler(res http.ResponseWriter, req *http.Request) {
    35  	view, err := Dashboard()
    36  	if err != nil {
    37  		log.Println(err)
    38  		res.WriteHeader(http.StatusInternalServerError)
    39  		return
    40  	}
    41  
    42  	res.Header().Set("Content-Type", "text/html")
    43  	res.Write(view)
    44  }
    45  
    46  func initHandler(res http.ResponseWriter, req *http.Request) {
    47  	if db.SystemInitComplete() {
    48  		http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound)
    49  		return
    50  	}
    51  
    52  	switch req.Method {
    53  	case http.MethodGet:
    54  		view, err := Init()
    55  		if err != nil {
    56  			log.Println(err)
    57  			res.WriteHeader(http.StatusInternalServerError)
    58  			return
    59  		}
    60  		res.Header().Set("Content-Type", "text/html")
    61  		res.Write(view)
    62  
    63  	case http.MethodPost:
    64  		err := req.ParseForm()
    65  		if err != nil {
    66  			log.Println(err)
    67  			res.WriteHeader(http.StatusInternalServerError)
    68  			return
    69  		}
    70  
    71  		// get the site name from post to encode and use as secret
    72  		name := []byte(req.FormValue("name") + db.NewEtag())
    73  		secret := base64.StdEncoding.EncodeToString(name)
    74  		req.Form.Set("client_secret", secret)
    75  
    76  		// generate an Etag to use for response caching
    77  		etag := db.NewEtag()
    78  		req.Form.Set("etag", etag)
    79  
    80  		// create and save admin user
    81  		email := strings.ToLower(req.FormValue("email"))
    82  		password := req.FormValue("password")
    83  		usr, err := user.New(email, password)
    84  		if err != nil {
    85  			log.Println(err)
    86  			res.WriteHeader(http.StatusInternalServerError)
    87  			return
    88  		}
    89  
    90  		_, err = db.SetUser(usr)
    91  		if err != nil {
    92  			log.Println(err)
    93  			res.WriteHeader(http.StatusInternalServerError)
    94  			return
    95  		}
    96  
    97  		// set HTTP port which should be previously added to config cache
    98  		port := db.ConfigCache("http_port").(string)
    99  		req.Form.Set("http_port", port)
   100  
   101  		// set initial user email as admin_email and make config
   102  		req.Form.Set("admin_email", email)
   103  		err = db.SetConfig(req.Form)
   104  		if err != nil {
   105  			log.Println(err)
   106  			res.WriteHeader(http.StatusInternalServerError)
   107  			return
   108  		}
   109  
   110  		// add _token cookie for login persistence
   111  		week := time.Now().Add(time.Hour * 24 * 7)
   112  		claims := map[string]interface{}{
   113  			"exp":  week.Unix(),
   114  			"user": usr.Email,
   115  		}
   116  
   117  		jwt.Secret([]byte(secret))
   118  		token, err := jwt.New(claims)
   119  		if err != nil {
   120  			log.Println(err)
   121  			res.WriteHeader(http.StatusInternalServerError)
   122  			return
   123  		}
   124  
   125  		http.SetCookie(res, &http.Cookie{
   126  			Name:    "_token",
   127  			Value:   token,
   128  			Expires: week,
   129  			Path:    "/",
   130  		})
   131  
   132  		redir := strings.TrimSuffix(req.URL.String(), "/init")
   133  		http.Redirect(res, req, redir, http.StatusFound)
   134  
   135  	default:
   136  		res.WriteHeader(http.StatusMethodNotAllowed)
   137  	}
   138  }
   139  
   140  func configHandler(res http.ResponseWriter, req *http.Request) {
   141  	switch req.Method {
   142  	case http.MethodGet:
   143  		data, err := db.ConfigAll()
   144  		if err != nil {
   145  			log.Println(err)
   146  			res.WriteHeader(http.StatusInternalServerError)
   147  			return
   148  		}
   149  
   150  		c := &config.Config{}
   151  
   152  		err = json.Unmarshal(data, c)
   153  		if err != nil {
   154  			log.Println(err)
   155  			res.WriteHeader(http.StatusInternalServerError)
   156  			return
   157  		}
   158  
   159  		cfg, err := c.MarshalEditor()
   160  		if err != nil {
   161  			log.Println(err)
   162  			res.WriteHeader(http.StatusInternalServerError)
   163  			return
   164  		}
   165  
   166  		adminView, err := Admin(cfg)
   167  		if err != nil {
   168  			log.Println(err)
   169  			res.WriteHeader(http.StatusInternalServerError)
   170  			return
   171  		}
   172  
   173  		res.Header().Set("Content-Type", "text/html")
   174  		res.Write(adminView)
   175  
   176  	case http.MethodPost:
   177  		err := req.ParseForm()
   178  		if err != nil {
   179  			log.Println(err)
   180  			res.WriteHeader(http.StatusInternalServerError)
   181  			return
   182  		}
   183  
   184  		err = db.SetConfig(req.Form)
   185  		if err != nil {
   186  			log.Println(err)
   187  			res.WriteHeader(http.StatusInternalServerError)
   188  			return
   189  		}
   190  
   191  		http.Redirect(res, req, req.URL.String(), http.StatusFound)
   192  
   193  	default:
   194  		res.WriteHeader(http.StatusMethodNotAllowed)
   195  	}
   196  
   197  }
   198  
   199  func backupHandler(res http.ResponseWriter, req *http.Request) {
   200  	ctx, cancel := context.WithCancel(context.Background())
   201  	defer cancel()
   202  
   203  	switch req.URL.Query().Get("source") {
   204  	case "system":
   205  		err := db.Backup(ctx, res)
   206  		if err != nil {
   207  			log.Println("Failed to run backup on system:", err)
   208  			res.WriteHeader(http.StatusInternalServerError)
   209  			return
   210  		}
   211  
   212  	case "analytics":
   213  		err := analytics.Backup(ctx, res)
   214  		if err != nil {
   215  			log.Println("Failed to run backup on analytics:", err)
   216  			res.WriteHeader(http.StatusInternalServerError)
   217  			return
   218  		}
   219  
   220  	case "uploads":
   221  		err := upload.Backup(ctx, res)
   222  		if err != nil {
   223  			log.Println("Failed to run backup on uploads:", err)
   224  			res.WriteHeader(http.StatusInternalServerError)
   225  			return
   226  		}
   227  
   228  	case "search":
   229  		err := search.Backup(ctx, res)
   230  		if err != nil {
   231  			log.Println("Failed to run backup on search:", err)
   232  			res.WriteHeader(http.StatusInternalServerError)
   233  			return
   234  		}
   235  
   236  	default:
   237  		res.WriteHeader(http.StatusBadRequest)
   238  	}
   239  }
   240  
   241  func configUsersHandler(res http.ResponseWriter, req *http.Request) {
   242  	switch req.Method {
   243  	case http.MethodGet:
   244  		view, err := UsersList(req)
   245  		if err != nil {
   246  			log.Println(err)
   247  			res.WriteHeader(http.StatusInternalServerError)
   248  			errView, err := Error500()
   249  			if err != nil {
   250  				return
   251  			}
   252  
   253  			res.Write(errView)
   254  			return
   255  		}
   256  
   257  		res.Write(view)
   258  
   259  	case http.MethodPost:
   260  		// create new user
   261  		err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
   262  		if err != nil {
   263  			log.Println(err)
   264  			res.WriteHeader(http.StatusInternalServerError)
   265  			errView, err := Error500()
   266  			if err != nil {
   267  				return
   268  			}
   269  
   270  			res.Write(errView)
   271  			return
   272  		}
   273  
   274  		email := strings.ToLower(req.FormValue("email"))
   275  		password := req.PostFormValue("password")
   276  
   277  		if email == "" || password == "" {
   278  			res.WriteHeader(http.StatusInternalServerError)
   279  			errView, err := Error500()
   280  			if err != nil {
   281  				return
   282  			}
   283  
   284  			res.Write(errView)
   285  			return
   286  		}
   287  
   288  		usr, err := user.New(email, password)
   289  		if err != nil {
   290  			log.Println(err)
   291  			res.WriteHeader(http.StatusInternalServerError)
   292  			return
   293  		}
   294  
   295  		_, err = db.SetUser(usr)
   296  		if err != nil {
   297  			log.Println(err)
   298  			res.WriteHeader(http.StatusInternalServerError)
   299  			errView, err := Error500()
   300  			if err != nil {
   301  				return
   302  			}
   303  
   304  			res.Write(errView)
   305  			return
   306  		}
   307  
   308  		http.Redirect(res, req, req.URL.String(), http.StatusFound)
   309  
   310  	default:
   311  		res.WriteHeader(http.StatusMethodNotAllowed)
   312  	}
   313  }
   314  
   315  func configUsersEditHandler(res http.ResponseWriter, req *http.Request) {
   316  	switch req.Method {
   317  	case http.MethodPost:
   318  		err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
   319  		if err != nil {
   320  			log.Println(err)
   321  			res.WriteHeader(http.StatusInternalServerError)
   322  			errView, err := Error500()
   323  			if err != nil {
   324  				return
   325  			}
   326  
   327  			res.Write(errView)
   328  			return
   329  		}
   330  
   331  		// check if user to be edited is current user
   332  		j, err := db.CurrentUser(req)
   333  		if err != nil {
   334  			log.Println(err)
   335  			res.WriteHeader(http.StatusInternalServerError)
   336  			errView, err := Error500()
   337  			if err != nil {
   338  				return
   339  			}
   340  
   341  			res.Write(errView)
   342  			return
   343  		}
   344  
   345  		usr := &user.User{}
   346  		err = json.Unmarshal(j, usr)
   347  		if err != nil {
   348  			log.Println(err)
   349  			res.WriteHeader(http.StatusInternalServerError)
   350  			errView, err := Error500()
   351  			if err != nil {
   352  				return
   353  			}
   354  
   355  			res.Write(errView)
   356  			return
   357  		}
   358  
   359  		// check if password matches
   360  		password := req.PostFormValue("password")
   361  
   362  		if !user.IsUser(usr, password) {
   363  			log.Println("Unexpected user/password combination for", usr.Email)
   364  			res.WriteHeader(http.StatusBadRequest)
   365  			errView, err := Error405()
   366  			if err != nil {
   367  				return
   368  			}
   369  
   370  			res.Write(errView)
   371  			return
   372  		}
   373  
   374  		email := strings.ToLower(req.PostFormValue("email"))
   375  		newPassword := req.PostFormValue("new_password")
   376  		var updatedUser *user.User
   377  		if newPassword != "" {
   378  			updatedUser, err = user.New(email, newPassword)
   379  			if err != nil {
   380  				log.Println(err)
   381  				res.WriteHeader(http.StatusInternalServerError)
   382  				return
   383  			}
   384  		} else {
   385  			updatedUser, err = user.New(email, password)
   386  			if err != nil {
   387  				log.Println(err)
   388  				res.WriteHeader(http.StatusInternalServerError)
   389  				return
   390  			}
   391  		}
   392  
   393  		// set the ID to the same ID as current user
   394  		updatedUser.ID = usr.ID
   395  
   396  		// set user in db
   397  		err = db.UpdateUser(usr, updatedUser)
   398  		if err != nil {
   399  			log.Println(err)
   400  			res.WriteHeader(http.StatusInternalServerError)
   401  			errView, err := Error500()
   402  			if err != nil {
   403  				return
   404  			}
   405  
   406  			res.Write(errView)
   407  			return
   408  		}
   409  
   410  		// create new token
   411  		week := time.Now().Add(time.Hour * 24 * 7)
   412  		claims := map[string]interface{}{
   413  			"exp":  week,
   414  			"user": updatedUser.Email,
   415  		}
   416  		token, err := jwt.New(claims)
   417  		if err != nil {
   418  			log.Println(err)
   419  			res.WriteHeader(http.StatusInternalServerError)
   420  			errView, err := Error500()
   421  			if err != nil {
   422  				return
   423  			}
   424  
   425  			res.Write(errView)
   426  			return
   427  		}
   428  
   429  		// add token to cookie +1 week expiration
   430  		cookie := &http.Cookie{
   431  			Name:    "_token",
   432  			Value:   token,
   433  			Expires: week,
   434  			Path:    "/",
   435  		}
   436  		http.SetCookie(res, cookie)
   437  
   438  		// add new token cookie to the request
   439  		req.AddCookie(cookie)
   440  
   441  		http.Redirect(res, req, strings.TrimSuffix(req.URL.String(), "/edit"), http.StatusFound)
   442  
   443  	default:
   444  		res.WriteHeader(http.StatusMethodNotAllowed)
   445  	}
   446  }
   447  
   448  func configUsersDeleteHandler(res http.ResponseWriter, req *http.Request) {
   449  	switch req.Method {
   450  	case http.MethodPost:
   451  		err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
   452  		if err != nil {
   453  			log.Println(err)
   454  			res.WriteHeader(http.StatusInternalServerError)
   455  			errView, err := Error500()
   456  			if err != nil {
   457  				return
   458  			}
   459  
   460  			res.Write(errView)
   461  			return
   462  		}
   463  
   464  		// do not allow current user to delete themselves
   465  		j, err := db.CurrentUser(req)
   466  		if err != nil {
   467  			log.Println(err)
   468  			res.WriteHeader(http.StatusInternalServerError)
   469  			errView, err := Error500()
   470  			if err != nil {
   471  				return
   472  			}
   473  
   474  			res.Write(errView)
   475  			return
   476  		}
   477  
   478  		usr := &user.User{}
   479  		err = json.Unmarshal(j, &usr)
   480  		if err != nil {
   481  			log.Println(err)
   482  			res.WriteHeader(http.StatusInternalServerError)
   483  			errView, err := Error500()
   484  			if err != nil {
   485  				return
   486  			}
   487  
   488  			res.Write(errView)
   489  			return
   490  		}
   491  
   492  		email := strings.ToLower(req.PostFormValue("email"))
   493  
   494  		if usr.Email == email {
   495  			log.Println(err)
   496  			res.WriteHeader(http.StatusBadRequest)
   497  			errView, err := Error405()
   498  			if err != nil {
   499  				return
   500  			}
   501  
   502  			res.Write(errView)
   503  			return
   504  		}
   505  
   506  		// delete existing user
   507  		err = db.DeleteUser(email)
   508  		if err != nil {
   509  			log.Println(err)
   510  			res.WriteHeader(http.StatusInternalServerError)
   511  			errView, err := Error500()
   512  			if err != nil {
   513  				return
   514  			}
   515  
   516  			res.Write(errView)
   517  			return
   518  		}
   519  
   520  		http.Redirect(res, req, strings.TrimSuffix(req.URL.String(), "/delete"), http.StatusFound)
   521  
   522  	default:
   523  		res.WriteHeader(http.StatusMethodNotAllowed)
   524  	}
   525  }
   526  
   527  func loginHandler(res http.ResponseWriter, req *http.Request) {
   528  	if !db.SystemInitComplete() {
   529  		redir := req.URL.Scheme + req.URL.Host + "/admin/init"
   530  		http.Redirect(res, req, redir, http.StatusFound)
   531  		return
   532  	}
   533  
   534  	switch req.Method {
   535  	case http.MethodGet:
   536  		if user.IsValid(req) {
   537  			http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound)
   538  			return
   539  		}
   540  
   541  		view, err := Login()
   542  		if err != nil {
   543  			log.Println(err)
   544  			res.WriteHeader(http.StatusInternalServerError)
   545  			return
   546  		}
   547  
   548  		res.Header().Set("Content-Type", "text/html")
   549  		res.Write(view)
   550  
   551  	case http.MethodPost:
   552  		if user.IsValid(req) {
   553  			http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound)
   554  			return
   555  		}
   556  
   557  		err := req.ParseForm()
   558  		if err != nil {
   559  			log.Println(err)
   560  			http.Redirect(res, req, req.URL.String(), http.StatusFound)
   561  			return
   562  		}
   563  
   564  		// check email & password
   565  		j, err := db.User(strings.ToLower(req.FormValue("email")))
   566  		if err != nil {
   567  			log.Println(err)
   568  			http.Redirect(res, req, req.URL.String(), http.StatusFound)
   569  			return
   570  		}
   571  
   572  		if j == nil {
   573  			http.Redirect(res, req, req.URL.String(), http.StatusFound)
   574  			return
   575  		}
   576  
   577  		usr := &user.User{}
   578  		err = json.Unmarshal(j, usr)
   579  		if err != nil {
   580  			log.Println(err)
   581  			http.Redirect(res, req, req.URL.String(), http.StatusFound)
   582  			return
   583  		}
   584  
   585  		if !user.IsUser(usr, req.FormValue("password")) {
   586  			http.Redirect(res, req, req.URL.String(), http.StatusFound)
   587  			return
   588  		}
   589  		// create new token
   590  		week := time.Now().Add(time.Hour * 24 * 7)
   591  		claims := map[string]interface{}{
   592  			"exp":  week,
   593  			"user": usr.Email,
   594  		}
   595  		token, err := jwt.New(claims)
   596  		if err != nil {
   597  			log.Println(err)
   598  			http.Redirect(res, req, req.URL.String(), http.StatusFound)
   599  			return
   600  		}
   601  
   602  		// add it to cookie +1 week expiration
   603  		http.SetCookie(res, &http.Cookie{
   604  			Name:    "_token",
   605  			Value:   token,
   606  			Expires: week,
   607  			Path:    "/",
   608  		})
   609  
   610  		http.Redirect(res, req, strings.TrimSuffix(req.URL.String(), "/login"), http.StatusFound)
   611  	}
   612  }
   613  
   614  func logoutHandler(res http.ResponseWriter, req *http.Request) {
   615  	http.SetCookie(res, &http.Cookie{
   616  		Name:    "_token",
   617  		Expires: time.Unix(0, 0),
   618  		Value:   "",
   619  		Path:    "/",
   620  	})
   621  
   622  	http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin/login", http.StatusFound)
   623  }
   624  
   625  func forgotPasswordHandler(res http.ResponseWriter, req *http.Request) {
   626  	switch req.Method {
   627  	case http.MethodGet:
   628  		view, err := ForgotPassword()
   629  		if err != nil {
   630  			res.WriteHeader(http.StatusInternalServerError)
   631  			errView, err := Error500()
   632  			if err != nil {
   633  				return
   634  			}
   635  
   636  			res.Write(errView)
   637  			return
   638  		}
   639  
   640  		res.Write(view)
   641  
   642  	case http.MethodPost:
   643  		err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
   644  		if err != nil {
   645  			log.Println(err)
   646  			res.WriteHeader(http.StatusInternalServerError)
   647  			errView, err := Error500()
   648  			if err != nil {
   649  				return
   650  			}
   651  
   652  			res.Write(errView)
   653  			return
   654  		}
   655  
   656  		// check email for user, if no user return Error
   657  		email := strings.ToLower(req.FormValue("email"))
   658  		if email == "" {
   659  			res.WriteHeader(http.StatusBadRequest)
   660  			log.Println("Failed account recovery. No email address submitted.")
   661  			return
   662  		}
   663  
   664  		_, err = db.User(email)
   665  		if err == db.ErrNoUserExists {
   666  			res.WriteHeader(http.StatusBadRequest)
   667  			log.Println("No user exists.", err)
   668  			return
   669  		}
   670  
   671  		if err != db.ErrNoUserExists && err != nil {
   672  			res.WriteHeader(http.StatusInternalServerError)
   673  			log.Println("Error:", err)
   674  			return
   675  		}
   676  
   677  		// create temporary key to verify user
   678  		key, err := db.SetRecoveryKey(email)
   679  		if err != nil {
   680  			res.WriteHeader(http.StatusInternalServerError)
   681  			log.Println("Failed to set account recovery key.", err)
   682  			return
   683  		}
   684  
   685  		domain, err := db.Config("domain")
   686  		if err != nil {
   687  			res.WriteHeader(http.StatusInternalServerError)
   688  			log.Println("Failed to get domain from configuration.", err)
   689  			return
   690  		}
   691  
   692  		body := fmt.Sprintf(`
   693  There has been an account recovery request made for the user with email:
   694  %s
   695  
   696  To recover your account, please go to http://%s/admin/recover/key and enter 
   697  this email address along with the following secret key:
   698  
   699  %s
   700  
   701  If you did not make the request, ignore this message and your password 
   702  will remain as-is.
   703  
   704  
   705  Thank you,
   706  Ponzu CMS at %s
   707  
   708  `, email, domain, key, domain)
   709  
   710  		msg := emailer.Message{
   711  			To:      email,
   712  			From:    fmt.Sprintf("ponzu@%s", domain),
   713  			Subject: fmt.Sprintf("Account Recovery [%s]", domain),
   714  			Body:    body,
   715  		}
   716  
   717  		go func() {
   718  			err = msg.Send()
   719  			if err != nil {
   720  				log.Println("Failed to send message to:", msg.To, "about", msg.Subject, "Error:", err)
   721  			}
   722  		}()
   723  
   724  		// redirect to /admin/recover/key and send email with key and URL
   725  		http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin/recover/key", http.StatusFound)
   726  
   727  	default:
   728  		res.WriteHeader(http.StatusMethodNotAllowed)
   729  		errView, err := Error405()
   730  		if err != nil {
   731  			return
   732  		}
   733  
   734  		res.Write(errView)
   735  		return
   736  	}
   737  }
   738  
   739  func recoveryKeyHandler(res http.ResponseWriter, req *http.Request) {
   740  	switch req.Method {
   741  	case http.MethodGet:
   742  		view, err := RecoveryKey()
   743  		if err != nil {
   744  			res.WriteHeader(http.StatusInternalServerError)
   745  			return
   746  		}
   747  
   748  		res.Write(view)
   749  
   750  	case http.MethodPost:
   751  		err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
   752  		if err != nil {
   753  			log.Println("Error parsing recovery key form:", err)
   754  
   755  			res.WriteHeader(http.StatusInternalServerError)
   756  			res.Write([]byte("Error, please go back and try again."))
   757  			return
   758  		}
   759  
   760  		// check for email & key match
   761  		email := strings.ToLower(req.FormValue("email"))
   762  		key := req.FormValue("key")
   763  
   764  		var actual string
   765  		if actual, err = db.RecoveryKey(email); err != nil || actual == "" {
   766  			log.Println("Error getting recovery key from database:", err)
   767  
   768  			res.WriteHeader(http.StatusInternalServerError)
   769  			res.Write([]byte("Error, please go back and try again."))
   770  			return
   771  		}
   772  
   773  		if key != actual {
   774  			log.Println("Bad recovery key submitted:", key)
   775  
   776  			res.WriteHeader(http.StatusBadRequest)
   777  			res.Write([]byte("Error, please go back and try again."))
   778  			return
   779  		}
   780  
   781  		// set user with new password
   782  		password := req.FormValue("password")
   783  		usr := &user.User{}
   784  		u, err := db.User(email)
   785  		if err != nil {
   786  			log.Println("Error finding user by email:", email, err)
   787  
   788  			res.WriteHeader(http.StatusInternalServerError)
   789  			res.Write([]byte("Error, please go back and try again."))
   790  			return
   791  		}
   792  
   793  		if u == nil {
   794  			log.Println("No user found with email:", email)
   795  
   796  			res.WriteHeader(http.StatusBadRequest)
   797  			res.Write([]byte("Error, please go back and try again."))
   798  			return
   799  		}
   800  
   801  		err = json.Unmarshal(u, usr)
   802  		if err != nil {
   803  			log.Println("Error decoding user from database:", err)
   804  
   805  			res.WriteHeader(http.StatusInternalServerError)
   806  			res.Write([]byte("Error, please go back and try again."))
   807  			return
   808  		}
   809  
   810  		update, err := user.New(email, password)
   811  		if err != nil {
   812  			log.Println(err)
   813  
   814  			res.WriteHeader(http.StatusInternalServerError)
   815  			res.Write([]byte("Error, please go back and try again."))
   816  			return
   817  		}
   818  
   819  		update.ID = usr.ID
   820  
   821  		err = db.UpdateUser(usr, update)
   822  		if err != nil {
   823  			log.Println("Error updating user:", err)
   824  
   825  			res.WriteHeader(http.StatusInternalServerError)
   826  			res.Write([]byte("Error, please go back and try again."))
   827  			return
   828  		}
   829  
   830  		// redirect to /admin/login
   831  		redir := req.URL.Scheme + req.URL.Host + "/admin/login"
   832  		http.Redirect(res, req, redir, http.StatusFound)
   833  
   834  	default:
   835  		res.WriteHeader(http.StatusMethodNotAllowed)
   836  		return
   837  	}
   838  }
   839  
   840  func uploadContentsHandler(res http.ResponseWriter, req *http.Request) {
   841  	q := req.URL.Query()
   842  
   843  	order := strings.ToLower(q.Get("order"))
   844  	if order != "asc" {
   845  		order = "desc"
   846  	}
   847  
   848  	pt := interface{}(&item.FileUpload{})
   849  
   850  	p, ok := pt.(editor.Editable)
   851  	if !ok {
   852  		res.WriteHeader(http.StatusInternalServerError)
   853  		errView, err := Error500()
   854  		if err != nil {
   855  			return
   856  		}
   857  
   858  		res.Write(errView)
   859  		return
   860  	}
   861  
   862  	count, err := strconv.Atoi(q.Get("count")) // int: determines number of posts to return (10 default, -1 is all)
   863  	if err != nil {
   864  		if q.Get("count") == "" {
   865  			count = 10
   866  		} else {
   867  			res.WriteHeader(http.StatusInternalServerError)
   868  			errView, err := Error500()
   869  			if err != nil {
   870  				return
   871  			}
   872  
   873  			res.Write(errView)
   874  			return
   875  		}
   876  	}
   877  
   878  	offset, err := strconv.Atoi(q.Get("offset")) // int: multiplier of count for pagination (0 default)
   879  	if err != nil {
   880  		if q.Get("offset") == "" {
   881  			offset = 0
   882  		} else {
   883  			res.WriteHeader(http.StatusInternalServerError)
   884  			errView, err := Error500()
   885  			if err != nil {
   886  				return
   887  			}
   888  
   889  			res.Write(errView)
   890  			return
   891  		}
   892  	}
   893  
   894  	opts := db.QueryOptions{
   895  		Count:  count,
   896  		Offset: offset,
   897  		Order:  order,
   898  	}
   899  
   900  	b := &bytes.Buffer{}
   901  	var total int
   902  	var posts [][]byte
   903  
   904  	html := `<div class="col s9 card">		
   905  					<div class="card-content">
   906  					<div class="row">
   907  					<div class="col s8">
   908  						<div class="row">
   909  							<div class="card-title col s7">Uploaded Items</div>
   910  							<div class="col s5 input-field inline">
   911  								<select class="browser-default __ponzu sort-order">
   912  									<option value="DESC">New to Old</option>
   913  									<option value="ASC">Old to New</option>
   914  								</select>
   915  								<label class="active">Sort:</label>
   916  							</div>	
   917  							<script>
   918  								$(function() {
   919  									var sort = $('select.__ponzu.sort-order');
   920  
   921  									sort.on('change', function() {
   922  										var path = window.location.pathname;
   923  										var s = sort.val();
   924  
   925  										window.location.replace(path + '?order=' + s);
   926  									});
   927  
   928  									var order = getParam('order');
   929  									if (order !== '') {
   930  										sort.val(order);
   931  									}
   932  									
   933  								});
   934  							</script>
   935  						</div>
   936  					</div>
   937  					<form class="col s4" action="/admin/uploads/search" method="get">
   938  						<div class="input-field post-search inline">
   939  							<label class="active">Search:</label>
   940  							<i class="right material-icons search-icon">search</i>
   941  							<input class="search" name="q" type="text" placeholder="Within all Upload fields" class="search"/>
   942  							<input type="hidden" name="type" value="__uploads" />
   943  						</div>
   944                      </form>	
   945  					</div>`
   946  
   947  	t := "__uploads"
   948  	status := ""
   949  	total, posts = db.Query(t, opts)
   950  
   951  	for i := range posts {
   952  		err := json.Unmarshal(posts[i], &p)
   953  		if err != nil {
   954  			log.Println("Error unmarshal json into", t, err, string(posts[i]))
   955  
   956  			post := `<li class="col s12">Error decoding data. Possible file corruption.</li>`
   957  			_, err := b.Write([]byte(post))
   958  			if err != nil {
   959  				log.Println(err)
   960  
   961  				res.WriteHeader(http.StatusInternalServerError)
   962  				errView, err := Error500()
   963  				if err != nil {
   964  					log.Println(err)
   965  				}
   966  
   967  				res.Write(errView)
   968  				return
   969  			}
   970  			continue
   971  		}
   972  
   973  		post := adminPostListItem(p, t, status)
   974  		_, err = b.Write(post)
   975  		if err != nil {
   976  			log.Println(err)
   977  
   978  			res.WriteHeader(http.StatusInternalServerError)
   979  			errView, err := Error500()
   980  			if err != nil {
   981  				log.Println(err)
   982  			}
   983  
   984  			res.Write(errView)
   985  			return
   986  		}
   987  	}
   988  
   989  	html += `<ul class="posts row">`
   990  
   991  	_, err = b.Write([]byte(`</ul>`))
   992  	if err != nil {
   993  		log.Println(err)
   994  
   995  		res.WriteHeader(http.StatusInternalServerError)
   996  		errView, err := Error500()
   997  		if err != nil {
   998  			log.Println(err)
   999  		}
  1000  
  1001  		res.Write(errView)
  1002  		return
  1003  	}
  1004  
  1005  	statusDisabled := "disabled"
  1006  	prevStatus := ""
  1007  	nextStatus := ""
  1008  	// total may be less than 10 (default count), so reset count to match total
  1009  	if total < count {
  1010  		count = total
  1011  	}
  1012  	// nothing previous to current list
  1013  	if offset == 0 {
  1014  		prevStatus = statusDisabled
  1015  	}
  1016  	// nothing after current list
  1017  	if (offset+1)*count >= total {
  1018  		nextStatus = statusDisabled
  1019  	}
  1020  
  1021  	// set up pagination values
  1022  	urlFmt := req.URL.Path + "?count=%d&offset=%d&&order=%s"
  1023  	prevURL := fmt.Sprintf(urlFmt, count, offset-1, order)
  1024  	nextURL := fmt.Sprintf(urlFmt, count, offset+1, order)
  1025  	start := 1 + count*offset
  1026  	end := start + count - 1
  1027  
  1028  	if total < end {
  1029  		end = total
  1030  	}
  1031  
  1032  	pagination := fmt.Sprintf(`
  1033  	<ul class="pagination row">
  1034  		<li class="col s2 waves-effect %s"><a href="%s"><i class="material-icons">chevron_left</i></a></li>
  1035  		<li class="col s8">%d to %d of %d</li>
  1036  		<li class="col s2 waves-effect %s"><a href="%s"><i class="material-icons">chevron_right</i></a></li>
  1037  	</ul>
  1038  	`, prevStatus, prevURL, start, end, total, nextStatus, nextURL)
  1039  
  1040  	// show indicator that a collection of items will be listed implicitly, but
  1041  	// that none are created yet
  1042  	if total < 1 {
  1043  		pagination = `
  1044  		<ul class="pagination row">
  1045  			<li class="col s2 waves-effect disabled"><a href="#"><i class="material-icons">chevron_left</i></a></li>
  1046  			<li class="col s8">0 to 0 of 0</li>
  1047  			<li class="col s2 waves-effect disabled"><a href="#"><i class="material-icons">chevron_right</i></a></li>
  1048  		</ul>
  1049  		`
  1050  	}
  1051  
  1052  	_, err = b.Write([]byte(pagination + `</div></div>`))
  1053  	if err != nil {
  1054  		log.Println(err)
  1055  
  1056  		res.WriteHeader(http.StatusInternalServerError)
  1057  		errView, err := Error500()
  1058  		if err != nil {
  1059  			log.Println(err)
  1060  		}
  1061  
  1062  		res.Write(errView)
  1063  		return
  1064  	}
  1065  
  1066  	script := `
  1067  	<script>
  1068  		$(function() {
  1069  			var del = $('.quick-delete-post.__ponzu span');
  1070  			del.on('click', function(e) {
  1071  				if (confirm("[Ponzu] Please confirm:\n\nAre you sure you want to delete this post?\nThis cannot be undone.")) {
  1072  					$(e.target).parent().submit();
  1073  				}
  1074  			});
  1075  		});
  1076  
  1077  		// disable link from being clicked if parent is 'disabled'
  1078  		$(function() {
  1079  			$('ul.pagination li.disabled a').on('click', function(e) {
  1080  				e.preventDefault();
  1081  			});
  1082  		});
  1083  	</script>
  1084  	`
  1085  
  1086  	btn := `<div class="col s3"><a href="/admin/edit/upload" class="btn new-post waves-effect waves-light">New Upload</a></div></div>`
  1087  	html = html + b.String() + script + btn
  1088  
  1089  	adminView, err := Admin([]byte(html))
  1090  	if err != nil {
  1091  		log.Println(err)
  1092  		res.WriteHeader(http.StatusInternalServerError)
  1093  		return
  1094  	}
  1095  
  1096  	res.Header().Set("Content-Type", "text/html")
  1097  	res.Write(adminView)
  1098  }
  1099  
  1100  func contentsHandler(res http.ResponseWriter, req *http.Request) {
  1101  	q := req.URL.Query()
  1102  	t := q.Get("type")
  1103  	if t == "" {
  1104  		res.WriteHeader(http.StatusBadRequest)
  1105  		errView, err := Error400()
  1106  		if err != nil {
  1107  			return
  1108  		}
  1109  
  1110  		res.Write(errView)
  1111  		return
  1112  	}
  1113  
  1114  	order := strings.ToLower(q.Get("order"))
  1115  	if order != "asc" {
  1116  		order = "desc"
  1117  	}
  1118  
  1119  	status := q.Get("status")
  1120  
  1121  	if _, ok := item.Types[t]; !ok {
  1122  		res.WriteHeader(http.StatusBadRequest)
  1123  		errView, err := Error400()
  1124  		if err != nil {
  1125  			return
  1126  		}
  1127  
  1128  		res.Write(errView)
  1129  		return
  1130  	}
  1131  
  1132  	pt := item.Types[t]()
  1133  
  1134  	p, ok := pt.(editor.Editable)
  1135  	if !ok {
  1136  		res.WriteHeader(http.StatusInternalServerError)
  1137  		errView, err := Error500()
  1138  		if err != nil {
  1139  			return
  1140  		}
  1141  
  1142  		res.Write(errView)
  1143  		return
  1144  	}
  1145  
  1146  	var hasExt bool
  1147  	_, ok = pt.(api.Createable)
  1148  	if ok {
  1149  		hasExt = true
  1150  	}
  1151  
  1152  	count, err := strconv.Atoi(q.Get("count")) // int: determines number of posts to return (10 default, -1 is all)
  1153  	if err != nil {
  1154  		if q.Get("count") == "" {
  1155  			count = 10
  1156  		} else {
  1157  			res.WriteHeader(http.StatusInternalServerError)
  1158  			errView, err := Error500()
  1159  			if err != nil {
  1160  				return
  1161  			}
  1162  
  1163  			res.Write(errView)
  1164  			return
  1165  		}
  1166  	}
  1167  
  1168  	offset, err := strconv.Atoi(q.Get("offset")) // int: multiplier of count for pagination (0 default)
  1169  	if err != nil {
  1170  		if q.Get("offset") == "" {
  1171  			offset = 0
  1172  		} else {
  1173  			res.WriteHeader(http.StatusInternalServerError)
  1174  			errView, err := Error500()
  1175  			if err != nil {
  1176  				return
  1177  			}
  1178  
  1179  			res.Write(errView)
  1180  			return
  1181  		}
  1182  	}
  1183  
  1184  	opts := db.QueryOptions{
  1185  		Count:  count,
  1186  		Offset: offset,
  1187  		Order:  order,
  1188  	}
  1189  
  1190  	var specifier string
  1191  	if status == "public" || status == "" {
  1192  		specifier = "__sorted"
  1193  	} else if status == "pending" {
  1194  		specifier = "__pending"
  1195  	}
  1196  
  1197  	b := &bytes.Buffer{}
  1198  	var total int
  1199  	var posts [][]byte
  1200  
  1201  	html := `<div class="col s9 card">		
  1202  					<div class="card-content">
  1203  					<div class="row">
  1204  					<div class="col s8">
  1205  						<div class="row">
  1206  							<div class="card-title col s7">` + t + ` Items</div>
  1207  							<div class="col s5 input-field inline">
  1208  								<select class="browser-default __ponzu sort-order">
  1209  									<option value="DESC">New to Old</option>
  1210  									<option value="ASC">Old to New</option>
  1211  								</select>
  1212  								<label class="active">Sort:</label>
  1213  							</div>	
  1214  							<script>
  1215  								$(function() {
  1216  									var sort = $('select.__ponzu.sort-order');
  1217  
  1218  									sort.on('change', function() {
  1219  										var path = window.location.pathname;
  1220  										var s = sort.val();
  1221  										var t = getParam('type');
  1222  										var status = getParam('status');
  1223  
  1224  										if (status == "") {
  1225  											status = "public";
  1226  										}
  1227  
  1228  										window.location.replace(path + '?type=' + t + '&order=' + s + '&status=' + status);
  1229  									});
  1230  
  1231  									var order = getParam('order');
  1232  									if (order !== '') {
  1233  										sort.val(order);
  1234  									}
  1235  									
  1236  								});
  1237  							</script>
  1238  						</div>
  1239  					</div>
  1240  					<form class="col s4" action="/admin/contents/search" method="get">
  1241  						<div class="input-field post-search inline">
  1242  							<label class="active">Search:</label>
  1243  							<i class="right material-icons search-icon">search</i>
  1244  							<input class="search" name="q" type="text" placeholder="Within all ` + t + ` fields" class="search"/>
  1245  							<input type="hidden" name="type" value="` + t + `" />
  1246  							<input type="hidden" name="status" value="` + status + `" />
  1247  						</div>
  1248                      </form>	
  1249  					</div>`
  1250  	if hasExt {
  1251  		if status == "" {
  1252  			q.Set("status", "public")
  1253  		}
  1254  
  1255  		// always start from top of results when changing public/pending
  1256  		q.Del("count")
  1257  		q.Del("offset")
  1258  
  1259  		q.Set("status", "public")
  1260  		publicURL := req.URL.Path + "?" + q.Encode()
  1261  
  1262  		q.Set("status", "pending")
  1263  		pendingURL := req.URL.Path + "?" + q.Encode()
  1264  
  1265  		switch status {
  1266  		case "public", "":
  1267  			// get __sorted posts of type t from the db
  1268  			total, posts = db.Query(t+specifier, opts)
  1269  
  1270  			html += `<div class="row externalable">
  1271  					<span class="description">Status:</span> 
  1272  					<span class="active">Public</span>
  1273  					&nbsp;&vert;&nbsp;
  1274  					<a href="` + pendingURL + `">Pending</a>
  1275  				</div>`
  1276  
  1277  			for i := range posts {
  1278  				err := json.Unmarshal(posts[i], &p)
  1279  				if err != nil {
  1280  					log.Println("Error unmarshal json into", t, err, string(posts[i]))
  1281  
  1282  					post := `<li class="col s12">Error decoding data. Possible file corruption.</li>`
  1283  					_, err := b.Write([]byte(post))
  1284  					if err != nil {
  1285  						log.Println(err)
  1286  
  1287  						res.WriteHeader(http.StatusInternalServerError)
  1288  						errView, err := Error500()
  1289  						if err != nil {
  1290  							log.Println(err)
  1291  						}
  1292  
  1293  						res.Write(errView)
  1294  						return
  1295  					}
  1296  
  1297  					continue
  1298  				}
  1299  
  1300  				post := adminPostListItem(p, t, status)
  1301  				_, err = b.Write(post)
  1302  				if err != nil {
  1303  					log.Println(err)
  1304  
  1305  					res.WriteHeader(http.StatusInternalServerError)
  1306  					errView, err := Error500()
  1307  					if err != nil {
  1308  						log.Println(err)
  1309  					}
  1310  
  1311  					res.Write(errView)
  1312  					return
  1313  				}
  1314  			}
  1315  
  1316  		case "pending":
  1317  			// get __pending posts of type t from the db
  1318  			total, posts = db.Query(t+"__pending", opts)
  1319  
  1320  			html += `<div class="row externalable">
  1321  					<span class="description">Status:</span> 
  1322  					<a href="` + publicURL + `">Public</a>
  1323  					&nbsp;&vert;&nbsp;
  1324  					<span class="active">Pending</span>					
  1325  				</div>`
  1326  
  1327  			for i := len(posts) - 1; i >= 0; i-- {
  1328  				err := json.Unmarshal(posts[i], &p)
  1329  				if err != nil {
  1330  					log.Println("Error unmarshal json into", t, err, string(posts[i]))
  1331  
  1332  					post := `<li class="col s12">Error decoding data. Possible file corruption.</li>`
  1333  					_, err := b.Write([]byte(post))
  1334  					if err != nil {
  1335  						log.Println(err)
  1336  
  1337  						res.WriteHeader(http.StatusInternalServerError)
  1338  						errView, err := Error500()
  1339  						if err != nil {
  1340  							log.Println(err)
  1341  						}
  1342  
  1343  						res.Write(errView)
  1344  						return
  1345  					}
  1346  					continue
  1347  				}
  1348  
  1349  				post := adminPostListItem(p, t, status)
  1350  				_, err = b.Write(post)
  1351  				if err != nil {
  1352  					log.Println(err)
  1353  
  1354  					res.WriteHeader(http.StatusInternalServerError)
  1355  					errView, err := Error500()
  1356  					if err != nil {
  1357  						log.Println(err)
  1358  					}
  1359  
  1360  					res.Write(errView)
  1361  					return
  1362  				}
  1363  			}
  1364  		}
  1365  
  1366  	} else {
  1367  		total, posts = db.Query(t+specifier, opts)
  1368  
  1369  		for i := range posts {
  1370  			err := json.Unmarshal(posts[i], &p)
  1371  			if err != nil {
  1372  				log.Println("Error unmarshal json into", t, err, string(posts[i]))
  1373  
  1374  				post := `<li class="col s12">Error decoding data. Possible file corruption.</li>`
  1375  				_, err := b.Write([]byte(post))
  1376  				if err != nil {
  1377  					log.Println(err)
  1378  
  1379  					res.WriteHeader(http.StatusInternalServerError)
  1380  					errView, err := Error500()
  1381  					if err != nil {
  1382  						log.Println(err)
  1383  					}
  1384  
  1385  					res.Write(errView)
  1386  					return
  1387  				}
  1388  				continue
  1389  			}
  1390  
  1391  			post := adminPostListItem(p, t, status)
  1392  			_, err = b.Write(post)
  1393  			if err != nil {
  1394  				log.Println(err)
  1395  
  1396  				res.WriteHeader(http.StatusInternalServerError)
  1397  				errView, err := Error500()
  1398  				if err != nil {
  1399  					log.Println(err)
  1400  				}
  1401  
  1402  				res.Write(errView)
  1403  				return
  1404  			}
  1405  		}
  1406  	}
  1407  
  1408  	html += `<ul class="posts row">`
  1409  
  1410  	_, err = b.Write([]byte(`</ul>`))
  1411  	if err != nil {
  1412  		log.Println(err)
  1413  
  1414  		res.WriteHeader(http.StatusInternalServerError)
  1415  		errView, err := Error500()
  1416  		if err != nil {
  1417  			log.Println(err)
  1418  		}
  1419  
  1420  		res.Write(errView)
  1421  		return
  1422  	}
  1423  
  1424  	statusDisabled := "disabled"
  1425  	prevStatus := ""
  1426  	nextStatus := ""
  1427  	// total may be less than 10 (default count), so reset count to match total
  1428  	if total < count {
  1429  		count = total
  1430  	}
  1431  	// nothing previous to current list
  1432  	if offset == 0 {
  1433  		prevStatus = statusDisabled
  1434  	}
  1435  	// nothing after current list
  1436  	if (offset+1)*count >= total {
  1437  		nextStatus = statusDisabled
  1438  	}
  1439  
  1440  	// set up pagination values
  1441  	urlFmt := req.URL.Path + "?count=%d&offset=%d&&order=%s&status=%s&type=%s"
  1442  	prevURL := fmt.Sprintf(urlFmt, count, offset-1, order, status, t)
  1443  	nextURL := fmt.Sprintf(urlFmt, count, offset+1, order, status, t)
  1444  	start := 1 + count*offset
  1445  	end := start + count - 1
  1446  
  1447  	if total < end {
  1448  		end = total
  1449  	}
  1450  
  1451  	pagination := fmt.Sprintf(`
  1452  	<ul class="pagination row">
  1453  		<li class="col s2 waves-effect %s"><a href="%s"><i class="material-icons">chevron_left</i></a></li>
  1454  		<li class="col s8">%d to %d of %d</li>
  1455  		<li class="col s2 waves-effect %s"><a href="%s"><i class="material-icons">chevron_right</i></a></li>
  1456  	</ul>
  1457  	`, prevStatus, prevURL, start, end, total, nextStatus, nextURL)
  1458  
  1459  	// show indicator that a collection of items will be listed implicitly, but
  1460  	// that none are created yet
  1461  	if total < 1 {
  1462  		pagination = `
  1463  		<ul class="pagination row">
  1464  			<li class="col s2 waves-effect disabled"><a href="#"><i class="material-icons">chevron_left</i></a></li>
  1465  			<li class="col s8">0 to 0 of 0</li>
  1466  			<li class="col s2 waves-effect disabled"><a href="#"><i class="material-icons">chevron_right</i></a></li>
  1467  		</ul>
  1468  		`
  1469  	}
  1470  
  1471  	_, err = b.Write([]byte(pagination + `</div></div>`))
  1472  	if err != nil {
  1473  		log.Println(err)
  1474  
  1475  		res.WriteHeader(http.StatusInternalServerError)
  1476  		errView, err := Error500()
  1477  		if err != nil {
  1478  			log.Println(err)
  1479  		}
  1480  
  1481  		res.Write(errView)
  1482  		return
  1483  	}
  1484  
  1485  	script := `
  1486  	<script>
  1487  		$(function() {
  1488  			var del = $('.quick-delete-post.__ponzu span');
  1489  			del.on('click', function(e) {
  1490  				if (confirm("[Ponzu] Please confirm:\n\nAre you sure you want to delete this post?\nThis cannot be undone.")) {
  1491  					$(e.target).parent().submit();
  1492  				}
  1493  			});
  1494  		});
  1495  
  1496  		// disable link from being clicked if parent is 'disabled'
  1497  		$(function() {
  1498  			$('ul.pagination li.disabled a').on('click', function(e) {
  1499  				e.preventDefault();
  1500  			});
  1501  		});
  1502  	</script>
  1503  	`
  1504  
  1505  	btn := `<div class="col s3">
  1506  		<a href="/admin/edit?type=` + t + `" class="btn new-post waves-effect waves-light">
  1507  			New ` + t + `
  1508  		</a>`
  1509  
  1510  	if _, ok := pt.(format.CSVFormattable); ok {
  1511  		btn += `<br/>
  1512  				<a href="/admin/contents/export?type=` + t + `&format=csv" class="green darken-4 btn export-post waves-effect waves-light">
  1513  					<i class="material-icons left">system_update_alt</i>
  1514  					CSV
  1515  				</a>`
  1516  	}
  1517  
  1518  	html += b.String() + script + btn + `</div></div>`
  1519  
  1520  	adminView, err := Admin([]byte(html))
  1521  	if err != nil {
  1522  		log.Println(err)
  1523  		res.WriteHeader(http.StatusInternalServerError)
  1524  		return
  1525  	}
  1526  
  1527  	res.Header().Set("Content-Type", "text/html")
  1528  	res.Write(adminView)
  1529  }
  1530  
  1531  // adminPostListItem is a helper to create the li containing a post.
  1532  // p is the asserted post as an Editable, t is the Type of the post.
  1533  // specifier is passed to append a name to a namespace like __pending
  1534  func adminPostListItem(e editor.Editable, typeName, status string) []byte {
  1535  	s, ok := e.(item.Sortable)
  1536  	if !ok {
  1537  		log.Println("Content type", typeName, "doesn't implement item.Sortable")
  1538  		post := `<li class="col s12">Error retreiving data. Your data type doesn't implement necessary interfaces. (item.Sortable)</li>`
  1539  		return []byte(post)
  1540  	}
  1541  
  1542  	i, ok := e.(item.Identifiable)
  1543  	if !ok {
  1544  		log.Println("Content type", typeName, "doesn't implement item.Identifiable")
  1545  		post := `<li class="col s12">Error retreiving data. Your data type doesn't implement necessary interfaces. (item.Identifiable)</li>`
  1546  		return []byte(post)
  1547  	}
  1548  
  1549  	// use sort to get other info to display in admin UI post list
  1550  	tsTime := time.Unix(int64(s.Time()/1000), 0)
  1551  	upTime := time.Unix(int64(s.Touch()/1000), 0)
  1552  	updatedTime := upTime.Format("01/02/06 03:04 PM")
  1553  	publishTime := tsTime.Format("01/02/06")
  1554  
  1555  	cid := fmt.Sprintf("%d", i.ItemID())
  1556  
  1557  	switch status {
  1558  	case "public", "":
  1559  		status = ""
  1560  	default:
  1561  		status = "__" + status
  1562  	}
  1563  
  1564  	link := `<a href="/admin/edit?type=` + typeName + `&status=` + strings.TrimPrefix(status, "__") + `&id=` + cid + `">` + i.String() + `</a>`
  1565  	if strings.HasPrefix(typeName, "__") {
  1566  		link = `<a href="/admin/edit/upload?id=` + cid + `">` + i.String() + `</a>`
  1567  	}
  1568  
  1569  	post := `
  1570  			<li class="col s12">
  1571  				` + link + `
  1572  				<span class="post-detail">Updated: ` + updatedTime + `</span>
  1573  				<span class="publish-date right">` + publishTime + `</span>
  1574  
  1575  				<form enctype="multipart/form-data" class="quick-delete-post __ponzu right" action="/admin/edit/delete" method="post">
  1576  					<span>Delete</span>
  1577  					<input type="hidden" name="id" value="` + cid + `" />
  1578  					<input type="hidden" name="type" value="` + typeName + status + `" />
  1579  				</form>
  1580  			</li>`
  1581  
  1582  	return []byte(post)
  1583  }
  1584  
  1585  func approveContentHandler(res http.ResponseWriter, req *http.Request) {
  1586  	if req.Method != http.MethodPost {
  1587  		res.WriteHeader(http.StatusMethodNotAllowed)
  1588  		errView, err := Error405()
  1589  		if err != nil {
  1590  			return
  1591  		}
  1592  
  1593  		res.Write(errView)
  1594  		return
  1595  	}
  1596  
  1597  	err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
  1598  	if err != nil {
  1599  		res.WriteHeader(http.StatusInternalServerError)
  1600  		errView, err := Error500()
  1601  		if err != nil {
  1602  			return
  1603  		}
  1604  
  1605  		res.Write(errView)
  1606  		return
  1607  	}
  1608  
  1609  	pendingID := req.FormValue("id")
  1610  
  1611  	t := req.FormValue("type")
  1612  	if strings.Contains(t, "__") {
  1613  		t = strings.Split(t, "__")[0]
  1614  	}
  1615  
  1616  	post := item.Types[t]()
  1617  
  1618  	// run hooks
  1619  	hook, ok := post.(item.Hookable)
  1620  	if !ok {
  1621  		log.Println("Type", t, "does not implement item.Hookable or embed item.Item.")
  1622  		res.WriteHeader(http.StatusBadRequest)
  1623  		errView, err := Error400()
  1624  		if err != nil {
  1625  			return
  1626  		}
  1627  
  1628  		res.Write(errView)
  1629  		return
  1630  	}
  1631  
  1632  	// check if we have a Mergeable
  1633  	m, ok := post.(editor.Mergeable)
  1634  	if !ok {
  1635  		log.Println("Content type", t, "must implement editor.Mergeable before it can be approved.")
  1636  		res.WriteHeader(http.StatusBadRequest)
  1637  		errView, err := Error400()
  1638  		if err != nil {
  1639  			return
  1640  		}
  1641  
  1642  		res.Write(errView)
  1643  		return
  1644  	}
  1645  
  1646  	dec := schema.NewDecoder()
  1647  	dec.IgnoreUnknownKeys(true)
  1648  	dec.SetAliasTag("json")
  1649  	err = dec.Decode(post, req.Form)
  1650  	if err != nil {
  1651  		log.Println("Error decoding post form for content approval:", t, err)
  1652  		res.WriteHeader(http.StatusInternalServerError)
  1653  		errView, err := Error500()
  1654  		if err != nil {
  1655  			return
  1656  		}
  1657  
  1658  		res.Write(errView)
  1659  		return
  1660  	}
  1661  
  1662  	err = hook.BeforeApprove(res, req)
  1663  	if err != nil {
  1664  		log.Println("Error running BeforeApprove hook in approveContentHandler for:", t, err)
  1665  		return
  1666  	}
  1667  
  1668  	// call its Approve method
  1669  	err = m.Approve(res, req)
  1670  	if err != nil {
  1671  		log.Println("Error running Approve method in approveContentHandler for:", t, err)
  1672  		return
  1673  	}
  1674  
  1675  	err = hook.AfterApprove(res, req)
  1676  	if err != nil {
  1677  		log.Println("Error running AfterApprove hook in approveContentHandler for:", t, err)
  1678  		return
  1679  	}
  1680  
  1681  	err = hook.BeforeSave(res, req)
  1682  	if err != nil {
  1683  		log.Println("Error running BeforeSave hook in approveContentHandler for:", t, err)
  1684  		return
  1685  	}
  1686  
  1687  	// Store the content in the bucket t
  1688  	id, err := db.SetContent(t+":-1", req.Form)
  1689  	if err != nil {
  1690  		log.Println("Error storing content in approveContentHandler for:", t, err)
  1691  		res.WriteHeader(http.StatusInternalServerError)
  1692  		errView, err := Error500()
  1693  		if err != nil {
  1694  			return
  1695  		}
  1696  
  1697  		res.Write(errView)
  1698  		return
  1699  	}
  1700  
  1701  	// set the target in the context so user can get saved value from db in hook
  1702  	ctx := context.WithValue(req.Context(), "target", fmt.Sprintf("%s:%d", t, id))
  1703  	req = req.WithContext(ctx)
  1704  
  1705  	err = hook.AfterSave(res, req)
  1706  	if err != nil {
  1707  		log.Println("Error running AfterSave hook in approveContentHandler for:", t, err)
  1708  		return
  1709  	}
  1710  
  1711  	if pendingID != "" {
  1712  		err = db.DeleteContent(req.FormValue("type") + ":" + pendingID)
  1713  		if err != nil {
  1714  			log.Println("Failed to remove content after approval:", err)
  1715  		}
  1716  	}
  1717  
  1718  	// redirect to the new approved content's editor
  1719  	redir := req.URL.Scheme + req.URL.Host + strings.TrimSuffix(req.URL.Path, "/approve")
  1720  	redir += fmt.Sprintf("?type=%s&id=%d", t, id)
  1721  	http.Redirect(res, req, redir, http.StatusFound)
  1722  }
  1723  
  1724  func editHandler(res http.ResponseWriter, req *http.Request) {
  1725  	switch req.Method {
  1726  	case http.MethodGet:
  1727  		q := req.URL.Query()
  1728  		i := q.Get("id")
  1729  		t := q.Get("type")
  1730  		status := q.Get("status")
  1731  
  1732  		contentType, ok := item.Types[t]
  1733  		if !ok {
  1734  			fmt.Fprintf(res, item.ErrTypeNotRegistered.Error(), t)
  1735  			return
  1736  		}
  1737  		post := contentType()
  1738  
  1739  		if i != "" {
  1740  			if status == "pending" {
  1741  				t = t + "__pending"
  1742  			}
  1743  
  1744  			data, err := db.Content(t + ":" + i)
  1745  			if err != nil {
  1746  				log.Println(err)
  1747  				res.WriteHeader(http.StatusInternalServerError)
  1748  				errView, err := Error500()
  1749  				if err != nil {
  1750  					return
  1751  				}
  1752  
  1753  				res.Write(errView)
  1754  				return
  1755  			}
  1756  
  1757  			if len(data) < 1 || data == nil {
  1758  				res.WriteHeader(http.StatusNotFound)
  1759  				errView, err := Error404()
  1760  				if err != nil {
  1761  					return
  1762  				}
  1763  
  1764  				res.Write(errView)
  1765  				return
  1766  			}
  1767  
  1768  			err = json.Unmarshal(data, post)
  1769  			if err != nil {
  1770  				log.Println(err)
  1771  				res.WriteHeader(http.StatusInternalServerError)
  1772  				errView, err := Error500()
  1773  				if err != nil {
  1774  					return
  1775  				}
  1776  
  1777  				res.Write(errView)
  1778  				return
  1779  			}
  1780  		} else {
  1781  			item, ok := post.(item.Identifiable)
  1782  			if !ok {
  1783  				log.Println("Content type", t, "doesn't implement item.Identifiable")
  1784  				return
  1785  			}
  1786  
  1787  			item.SetItemID(-1)
  1788  		}
  1789  
  1790  		m, err := manager.Manage(post.(editor.Editable), t)
  1791  		if err != nil {
  1792  			log.Println(err)
  1793  			res.WriteHeader(http.StatusInternalServerError)
  1794  			errView, err := Error500()
  1795  			if err != nil {
  1796  				return
  1797  			}
  1798  
  1799  			res.Write(errView)
  1800  			return
  1801  		}
  1802  
  1803  		adminView, err := Admin(m)
  1804  		if err != nil {
  1805  			log.Println(err)
  1806  			res.WriteHeader(http.StatusInternalServerError)
  1807  			return
  1808  		}
  1809  
  1810  		res.Header().Set("Content-Type", "text/html")
  1811  		res.Write(adminView)
  1812  
  1813  	case http.MethodPost:
  1814  		err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
  1815  		if err != nil {
  1816  			log.Println(err)
  1817  			res.WriteHeader(http.StatusInternalServerError)
  1818  			errView, err := Error500()
  1819  			if err != nil {
  1820  				return
  1821  			}
  1822  
  1823  			res.Write(errView)
  1824  			return
  1825  		}
  1826  
  1827  		cid := req.FormValue("id")
  1828  		t := req.FormValue("type")
  1829  		ts := req.FormValue("timestamp")
  1830  		up := req.FormValue("updated")
  1831  
  1832  		// create a timestamp if one was not set
  1833  		if ts == "" {
  1834  			ts = fmt.Sprintf("%d", int64(time.Nanosecond)*time.Now().UTC().UnixNano()/int64(time.Millisecond))
  1835  			req.PostForm.Set("timestamp", ts)
  1836  		}
  1837  
  1838  		if up == "" {
  1839  			req.PostForm.Set("updated", ts)
  1840  		}
  1841  
  1842  		urlPaths, err := upload.StoreFiles(req)
  1843  		if err != nil {
  1844  			log.Println(err)
  1845  			res.WriteHeader(http.StatusInternalServerError)
  1846  			errView, err := Error500()
  1847  			if err != nil {
  1848  				return
  1849  			}
  1850  
  1851  			res.Write(errView)
  1852  			return
  1853  		}
  1854  
  1855  		for name, urlPath := range urlPaths {
  1856  			req.PostForm.Set(name, urlPath)
  1857  		}
  1858  
  1859  		// check for any multi-value fields (ex. checkbox fields)
  1860  		// and correctly format for db storage. Essentially, we need
  1861  		// fieldX.0: value1, fieldX.1: value2 => fieldX: []string{value1, value2}
  1862  		fieldOrderValue := make(map[string]map[string][]string)
  1863  		for k, v := range req.PostForm {
  1864  			if strings.Contains(k, ".") {
  1865  				fo := strings.Split(k, ".")
  1866  
  1867  				// put the order and the field value into map
  1868  				field := string(fo[0])
  1869  				order := string(fo[1])
  1870  				if len(fieldOrderValue[field]) == 0 {
  1871  					fieldOrderValue[field] = make(map[string][]string)
  1872  				}
  1873  
  1874  				// orderValue is 0:[?type=Thing&id=1]
  1875  				orderValue := fieldOrderValue[field]
  1876  				orderValue[order] = v
  1877  				fieldOrderValue[field] = orderValue
  1878  
  1879  				// discard the post form value with name.N
  1880  				req.PostForm.Del(k)
  1881  			}
  1882  
  1883  		}
  1884  
  1885  		// add/set the key & value to the post form in order
  1886  		for f, ov := range fieldOrderValue {
  1887  			for i := 0; i < len(ov); i++ {
  1888  				position := fmt.Sprintf("%d", i)
  1889  				fieldValue := ov[position]
  1890  
  1891  				if req.PostForm.Get(f) == "" {
  1892  					for i, fv := range fieldValue {
  1893  						if i == 0 {
  1894  							req.PostForm.Set(f, fv)
  1895  						} else {
  1896  							req.PostForm.Add(f, fv)
  1897  						}
  1898  					}
  1899  				} else {
  1900  					for _, fv := range fieldValue {
  1901  						req.PostForm.Add(f, fv)
  1902  					}
  1903  				}
  1904  			}
  1905  		}
  1906  
  1907  		pt := t
  1908  		if strings.Contains(t, "__") {
  1909  			pt = strings.Split(t, "__")[0]
  1910  		}
  1911  
  1912  		p, ok := item.Types[pt]
  1913  		if !ok {
  1914  			log.Println("Type", t, "is not a content type. Cannot edit or save.")
  1915  			res.WriteHeader(http.StatusBadRequest)
  1916  			errView, err := Error400()
  1917  			if err != nil {
  1918  				return
  1919  			}
  1920  
  1921  			res.Write(errView)
  1922  			return
  1923  		}
  1924  
  1925  		post := p()
  1926  		hook, ok := post.(item.Hookable)
  1927  		if !ok {
  1928  			log.Println("Type", pt, "does not implement item.Hookable or embed item.Item.")
  1929  			res.WriteHeader(http.StatusBadRequest)
  1930  			errView, err := Error400()
  1931  			if err != nil {
  1932  				return
  1933  			}
  1934  
  1935  			res.Write(errView)
  1936  			return
  1937  		}
  1938  
  1939  		// Let's be nice and make a proper item for the Hookable methods
  1940  		dec := schema.NewDecoder()
  1941  		dec.IgnoreUnknownKeys(true)
  1942  		dec.SetAliasTag("json")
  1943  		err = dec.Decode(post, req.PostForm)
  1944  		if err != nil {
  1945  			log.Println("Error decoding post form for edit handler:", t, err)
  1946  			res.WriteHeader(http.StatusBadRequest)
  1947  			errView, err := Error400()
  1948  			if err != nil {
  1949  				return
  1950  			}
  1951  
  1952  			res.Write(errView)
  1953  			return
  1954  		}
  1955  
  1956  		if cid == "-1" {
  1957  			err = hook.BeforeAdminCreate(res, req)
  1958  			if err != nil {
  1959  				log.Println("Error running BeforeAdminCreate method in editHandler for:", t, err)
  1960  				return
  1961  			}
  1962  		} else {
  1963  			err = hook.BeforeAdminUpdate(res, req)
  1964  			if err != nil {
  1965  				log.Println("Error running BeforeAdminUpdate method in editHandler for:", t, err)
  1966  				return
  1967  			}
  1968  		}
  1969  
  1970  		err = hook.BeforeSave(res, req)
  1971  		if err != nil {
  1972  			log.Println("Error running BeforeSave method in editHandler for:", t, err)
  1973  			return
  1974  		}
  1975  
  1976  		id, err := db.SetContent(t+":"+cid, req.PostForm)
  1977  		if err != nil {
  1978  			log.Println(err)
  1979  			res.WriteHeader(http.StatusInternalServerError)
  1980  			errView, err := Error500()
  1981  			if err != nil {
  1982  				return
  1983  			}
  1984  
  1985  			res.Write(errView)
  1986  			return
  1987  		}
  1988  
  1989  		// set the target in the context so user can get saved value from db in hook
  1990  		ctx := context.WithValue(req.Context(), "target", fmt.Sprintf("%s:%d", t, id))
  1991  		req = req.WithContext(ctx)
  1992  
  1993  		err = hook.AfterSave(res, req)
  1994  		if err != nil {
  1995  			log.Println("Error running AfterSave method in editHandler for:", t, err)
  1996  			return
  1997  		}
  1998  
  1999  		if cid == "-1" {
  2000  			err = hook.AfterAdminCreate(res, req)
  2001  			if err != nil {
  2002  				log.Println("Error running AfterAdminUpdate method in editHandler for:", t, err)
  2003  				return
  2004  			}
  2005  		} else {
  2006  			err = hook.AfterAdminUpdate(res, req)
  2007  			if err != nil {
  2008  				log.Println("Error running AfterAdminUpdate method in editHandler for:", t, err)
  2009  				return
  2010  			}
  2011  		}
  2012  
  2013  		scheme := req.URL.Scheme
  2014  		host := req.URL.Host
  2015  		path := req.URL.Path
  2016  		sid := fmt.Sprintf("%d", id)
  2017  		redir := scheme + host + path + "?type=" + pt + "&id=" + sid
  2018  
  2019  		if req.URL.Query().Get("status") == "pending" {
  2020  			redir += "&status=pending"
  2021  		}
  2022  
  2023  		http.Redirect(res, req, redir, http.StatusFound)
  2024  
  2025  	default:
  2026  		res.WriteHeader(http.StatusMethodNotAllowed)
  2027  	}
  2028  }
  2029  
  2030  func deleteHandler(res http.ResponseWriter, req *http.Request) {
  2031  	if req.Method != http.MethodPost {
  2032  		res.WriteHeader(http.StatusMethodNotAllowed)
  2033  		return
  2034  	}
  2035  
  2036  	err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
  2037  	if err != nil {
  2038  		log.Println(err)
  2039  		res.WriteHeader(http.StatusInternalServerError)
  2040  		errView, err := Error500()
  2041  		if err != nil {
  2042  			return
  2043  		}
  2044  
  2045  		res.Write(errView)
  2046  		return
  2047  	}
  2048  
  2049  	id := req.FormValue("id")
  2050  	t := req.FormValue("type")
  2051  	ct := t
  2052  
  2053  	if id == "" || t == "" {
  2054  		res.WriteHeader(http.StatusBadRequest)
  2055  		return
  2056  	}
  2057  
  2058  	// catch specifier suffix from delete form value
  2059  	if strings.Contains(t, "__") {
  2060  		spec := strings.Split(t, "__")
  2061  		ct = spec[0]
  2062  	}
  2063  
  2064  	p, ok := item.Types[ct]
  2065  	if !ok {
  2066  		log.Println("Type", t, "does not implement item.Hookable or embed item.Item.")
  2067  		res.WriteHeader(http.StatusBadRequest)
  2068  		errView, err := Error400()
  2069  		if err != nil {
  2070  			return
  2071  		}
  2072  
  2073  		res.Write(errView)
  2074  		return
  2075  	}
  2076  
  2077  	post := p()
  2078  	hook, ok := post.(item.Hookable)
  2079  	if !ok {
  2080  		log.Println("Type", t, "does not implement item.Hookable or embed item.Item.")
  2081  		res.WriteHeader(http.StatusBadRequest)
  2082  		errView, err := Error400()
  2083  		if err != nil {
  2084  			return
  2085  		}
  2086  
  2087  		res.Write(errView)
  2088  		return
  2089  	}
  2090  
  2091  	data, err := db.Content(t + ":" + id)
  2092  	if err != nil {
  2093  		log.Println("Error in db.Content ", t+":"+id, err)
  2094  		return
  2095  	}
  2096  
  2097  	err = json.Unmarshal(data, post)
  2098  	if err != nil {
  2099  		log.Println("Error unmarshalling ", t, "=", id, err, " Hooks will be called on a zero-value.")
  2100  	}
  2101  
  2102  	reject := req.URL.Query().Get("reject")
  2103  	if reject == "true" {
  2104  		err = hook.BeforeReject(res, req)
  2105  		if err != nil {
  2106  			log.Println("Error running BeforeReject method in deleteHandler for:", t, err)
  2107  			return
  2108  		}
  2109  	}
  2110  
  2111  	err = hook.BeforeAdminDelete(res, req)
  2112  	if err != nil {
  2113  		log.Println("Error running BeforeAdminDelete method in deleteHandler for:", t, err)
  2114  		return
  2115  	}
  2116  
  2117  	err = hook.BeforeDelete(res, req)
  2118  	if err != nil {
  2119  		log.Println("Error running BeforeDelete method in deleteHandler for:", t, err)
  2120  		return
  2121  	}
  2122  
  2123  	err = db.DeleteContent(t + ":" + id)
  2124  	if err != nil {
  2125  		log.Println(err)
  2126  		res.WriteHeader(http.StatusInternalServerError)
  2127  		return
  2128  	}
  2129  
  2130  	err = hook.AfterDelete(res, req)
  2131  	if err != nil {
  2132  		log.Println("Error running AfterDelete method in deleteHandler for:", t, err)
  2133  		return
  2134  	}
  2135  
  2136  	err = hook.AfterAdminDelete(res, req)
  2137  	if err != nil {
  2138  		log.Println("Error running AfterDelete method in deleteHandler for:", t, err)
  2139  		return
  2140  	}
  2141  
  2142  	if reject == "true" {
  2143  		err = hook.AfterReject(res, req)
  2144  		if err != nil {
  2145  			log.Println("Error running AfterReject method in deleteHandler for:", t, err)
  2146  			return
  2147  		}
  2148  	}
  2149  
  2150  	redir := strings.TrimSuffix(req.URL.Scheme+req.URL.Host+req.URL.Path, "/edit/delete")
  2151  	redir = redir + "/contents?type=" + ct
  2152  	http.Redirect(res, req, redir, http.StatusFound)
  2153  }
  2154  
  2155  func deleteUploadHandler(res http.ResponseWriter, req *http.Request) {
  2156  	if req.Method != http.MethodPost {
  2157  		res.WriteHeader(http.StatusMethodNotAllowed)
  2158  		return
  2159  	}
  2160  
  2161  	err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
  2162  	if err != nil {
  2163  		log.Println(err)
  2164  		res.WriteHeader(http.StatusInternalServerError)
  2165  		errView, err := Error500()
  2166  		if err != nil {
  2167  			return
  2168  		}
  2169  
  2170  		res.Write(errView)
  2171  		return
  2172  	}
  2173  
  2174  	id := req.FormValue("id")
  2175  	t := "__uploads"
  2176  
  2177  	if id == "" || t == "" {
  2178  		res.WriteHeader(http.StatusBadRequest)
  2179  		return
  2180  	}
  2181  
  2182  	post := interface{}(&item.FileUpload{})
  2183  	hook, ok := post.(item.Hookable)
  2184  	if !ok {
  2185  		log.Println("Type", t, "does not implement item.Hookable or embed item.Item.")
  2186  		res.WriteHeader(http.StatusBadRequest)
  2187  		errView, err := Error400()
  2188  		if err != nil {
  2189  			return
  2190  		}
  2191  
  2192  		res.Write(errView)
  2193  		return
  2194  	}
  2195  
  2196  	err = hook.BeforeDelete(res, req)
  2197  	if err != nil {
  2198  		log.Println("Error running BeforeDelete method in deleteHandler for:", t, err)
  2199  		return
  2200  	}
  2201  
  2202  	err = db.DeleteUpload(t + ":" + id)
  2203  	if err != nil {
  2204  		log.Println(err)
  2205  		res.WriteHeader(http.StatusInternalServerError)
  2206  		return
  2207  	}
  2208  
  2209  	err = hook.AfterDelete(res, req)
  2210  	if err != nil {
  2211  		log.Println("Error running AfterDelete method in deleteHandler for:", t, err)
  2212  		return
  2213  	}
  2214  
  2215  	redir := "/admin/uploads"
  2216  	http.Redirect(res, req, redir, http.StatusFound)
  2217  }
  2218  
  2219  func editUploadHandler(res http.ResponseWriter, req *http.Request) {
  2220  	switch req.Method {
  2221  	case http.MethodGet:
  2222  		q := req.URL.Query()
  2223  		i := q.Get("id")
  2224  		t := "__uploads"
  2225  
  2226  		post := &item.FileUpload{}
  2227  
  2228  		if i != "" {
  2229  			data, err := db.Upload(t + ":" + i)
  2230  			if err != nil {
  2231  				log.Println(err)
  2232  				res.WriteHeader(http.StatusInternalServerError)
  2233  				errView, err := Error500()
  2234  				if err != nil {
  2235  					return
  2236  				}
  2237  
  2238  				res.Write(errView)
  2239  				return
  2240  			}
  2241  
  2242  			if len(data) < 1 || data == nil {
  2243  				res.WriteHeader(http.StatusNotFound)
  2244  				errView, err := Error404()
  2245  				if err != nil {
  2246  					return
  2247  				}
  2248  
  2249  				res.Write(errView)
  2250  				return
  2251  			}
  2252  
  2253  			err = json.Unmarshal(data, post)
  2254  			if err != nil {
  2255  				log.Println(err)
  2256  				res.WriteHeader(http.StatusInternalServerError)
  2257  				errView, err := Error500()
  2258  				if err != nil {
  2259  					return
  2260  				}
  2261  
  2262  				res.Write(errView)
  2263  				return
  2264  			}
  2265  		} else {
  2266  			it, ok := interface{}(post).(item.Identifiable)
  2267  			if !ok {
  2268  				log.Println("Content type", t, "doesn't implement item.Identifiable")
  2269  				return
  2270  			}
  2271  
  2272  			it.SetItemID(-1)
  2273  		}
  2274  
  2275  		m, err := manager.Manage(interface{}(post).(editor.Editable), t)
  2276  		if err != nil {
  2277  			log.Println(err)
  2278  			res.WriteHeader(http.StatusInternalServerError)
  2279  			errView, err := Error500()
  2280  			if err != nil {
  2281  				return
  2282  			}
  2283  
  2284  			res.Write(errView)
  2285  			return
  2286  		}
  2287  
  2288  		adminView, err := Admin(m)
  2289  		if err != nil {
  2290  			log.Println(err)
  2291  			res.WriteHeader(http.StatusInternalServerError)
  2292  			return
  2293  		}
  2294  
  2295  		res.Header().Set("Content-Type", "text/html")
  2296  		res.Write(adminView)
  2297  
  2298  	case http.MethodPost:
  2299  		err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
  2300  		if err != nil {
  2301  			log.Println(err)
  2302  			res.WriteHeader(http.StatusInternalServerError)
  2303  			errView, err := Error500()
  2304  			if err != nil {
  2305  				return
  2306  			}
  2307  
  2308  			res.Write(errView)
  2309  			return
  2310  		}
  2311  
  2312  		t := req.FormValue("type")
  2313  		pt := "__uploads"
  2314  		ts := req.FormValue("timestamp")
  2315  		up := req.FormValue("updated")
  2316  
  2317  		// create a timestamp if one was not set
  2318  		if ts == "" {
  2319  			ts = fmt.Sprintf("%d", int64(time.Nanosecond)*time.Now().UTC().UnixNano()/int64(time.Millisecond))
  2320  			req.PostForm.Set("timestamp", ts)
  2321  		}
  2322  
  2323  		if up == "" {
  2324  			req.PostForm.Set("updated", ts)
  2325  		}
  2326  
  2327  		post := interface{}(&item.FileUpload{})
  2328  		hook, ok := post.(item.Hookable)
  2329  		if !ok {
  2330  			log.Println("Type", pt, "does not implement item.Hookable or embed item.Item.")
  2331  			res.WriteHeader(http.StatusBadRequest)
  2332  			errView, err := Error400()
  2333  			if err != nil {
  2334  				return
  2335  			}
  2336  
  2337  			res.Write(errView)
  2338  			return
  2339  		}
  2340  
  2341  		err = hook.BeforeSave(res, req)
  2342  		if err != nil {
  2343  			log.Println("Error running BeforeSave method in editHandler for:", t, err)
  2344  			return
  2345  		}
  2346  
  2347  		// StoreFiles has the SetUpload call (which is equivalent of SetContent in other handlers)
  2348  		urlPaths, err := upload.StoreFiles(req)
  2349  		if err != nil {
  2350  			log.Println(err)
  2351  			res.WriteHeader(http.StatusInternalServerError)
  2352  			errView, err := Error500()
  2353  			if err != nil {
  2354  				return
  2355  			}
  2356  
  2357  			res.Write(errView)
  2358  			return
  2359  		}
  2360  
  2361  		for name, urlPath := range urlPaths {
  2362  			req.PostForm.Set(name, urlPath)
  2363  		}
  2364  
  2365  		// check for any multi-value fields (ex. checkbox fields)
  2366  		// and correctly format for db storage. Essentially, we need
  2367  		// fieldX.0: value1, fieldX.1: value2 => fieldX: []string{value1, value2}
  2368  		fieldOrderValue := make(map[string]map[string][]string)
  2369  		ordVal := make(map[string][]string)
  2370  		for k, v := range req.PostForm {
  2371  			if strings.Contains(k, ".") {
  2372  				fo := strings.Split(k, ".")
  2373  
  2374  				// put the order and the field value into map
  2375  				field := string(fo[0])
  2376  				order := string(fo[1])
  2377  				fieldOrderValue[field] = ordVal
  2378  
  2379  				// orderValue is 0:[?type=Thing&id=1]
  2380  				orderValue := fieldOrderValue[field]
  2381  				orderValue[order] = v
  2382  				fieldOrderValue[field] = orderValue
  2383  
  2384  				// discard the post form value with name.N
  2385  				req.PostForm.Del(k)
  2386  			}
  2387  
  2388  		}
  2389  
  2390  		// add/set the key & value to the post form in order
  2391  		for f, ov := range fieldOrderValue {
  2392  			for i := 0; i < len(ov); i++ {
  2393  				position := fmt.Sprintf("%d", i)
  2394  				fieldValue := ov[position]
  2395  
  2396  				if req.PostForm.Get(f) == "" {
  2397  					for i, fv := range fieldValue {
  2398  						if i == 0 {
  2399  							req.PostForm.Set(f, fv)
  2400  						} else {
  2401  							req.PostForm.Add(f, fv)
  2402  						}
  2403  					}
  2404  				} else {
  2405  					for _, fv := range fieldValue {
  2406  						req.PostForm.Add(f, fv)
  2407  					}
  2408  				}
  2409  			}
  2410  		}
  2411  
  2412  		err = hook.AfterSave(res, req)
  2413  		if err != nil {
  2414  			log.Println("Error running AfterSave method in editHandler for:", t, err)
  2415  			return
  2416  		}
  2417  
  2418  		scheme := req.URL.Scheme
  2419  		host := req.URL.Host
  2420  		redir := scheme + host + "/admin/uploads"
  2421  		http.Redirect(res, req, redir, http.StatusFound)
  2422  
  2423  	case http.MethodPut:
  2424  		urlPaths, err := upload.StoreFiles(req)
  2425  		if err != nil {
  2426  			log.Println("Couldn't store file uploads.", err)
  2427  			res.WriteHeader(http.StatusInternalServerError)
  2428  			return
  2429  		}
  2430  
  2431  		res.Header().Set("Content-Type", "application/json")
  2432  		res.Write([]byte(`{"data": [{"url": "` + urlPaths["file"] + `"}]}`))
  2433  	default:
  2434  		res.WriteHeader(http.StatusMethodNotAllowed)
  2435  		return
  2436  	}
  2437  }
  2438  
  2439  /*
  2440  func editUploadHandler(res http.ResponseWriter, req *http.Request) {
  2441  	if req.Method != http.MethodPost {
  2442  		res.WriteHeader(http.StatusMethodNotAllowed)
  2443  		return
  2444  	}
  2445  
  2446  	urlPaths, err := upload.StoreFiles(req)
  2447  	if err != nil {
  2448  		log.Println("Couldn't store file uploads.", err)
  2449  		res.WriteHeader(http.StatusInternalServerError)
  2450  		return
  2451  	}
  2452  
  2453  	res.Header().Set("Content-Type", "application/json")
  2454  	res.Write([]byte(`{"data": [{"url": "` + urlPaths["file"] + `"}]}`))
  2455  }
  2456  */
  2457  
  2458  func searchHandler(res http.ResponseWriter, req *http.Request) {
  2459  	q := req.URL.Query()
  2460  	t := q.Get("type")
  2461  	search := q.Get("q")
  2462  	status := q.Get("status")
  2463  	var specifier string
  2464  
  2465  	if t == "" || search == "" {
  2466  		http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound)
  2467  		return
  2468  	}
  2469  
  2470  	if status == "pending" {
  2471  		specifier = "__" + status
  2472  	}
  2473  
  2474  	posts := db.ContentAll(t + specifier)
  2475  	b := &bytes.Buffer{}
  2476  	pt, ok := item.Types[t]
  2477  	if !ok {
  2478  		res.WriteHeader(http.StatusBadRequest)
  2479  		return
  2480  	}
  2481  
  2482  	post := pt()
  2483  
  2484  	p := post.(editor.Editable)
  2485  
  2486  	html := `<div class="col s9 card">		
  2487  					<div class="card-content">
  2488  					<div class="row">
  2489  					<div class="card-title col s7">` + t + ` Results</div>	
  2490  					<form class="col s4" action="/admin/contents/search" method="get">
  2491  						<div class="input-field post-search inline">
  2492  							<label class="active">Search:</label>
  2493  							<i class="right material-icons search-icon">search</i>
  2494  							<input class="search" name="q" type="text" placeholder="Within all ` + t + ` fields" class="search"/>
  2495  							<input type="hidden" name="type" value="` + t + `" />
  2496  							<input type="hidden" name="status" value="` + status + `" />
  2497  						</div>
  2498                      </form>	
  2499  					</div>
  2500  					<ul class="posts row">`
  2501  
  2502  	for i := range posts {
  2503  		// skip posts that don't have any matching search criteria
  2504  		match := strings.ToLower(search)
  2505  		all := strings.ToLower(string(posts[i]))
  2506  		if !strings.Contains(all, match) {
  2507  			continue
  2508  		}
  2509  
  2510  		err := json.Unmarshal(posts[i], &p)
  2511  		if err != nil {
  2512  			log.Println("Error unmarshal search result json into", t, err, posts[i])
  2513  
  2514  			post := `<li class="col s12">Error decoding data. Possible file corruption.</li>`
  2515  			_, err = b.Write([]byte(post))
  2516  			if err != nil {
  2517  				log.Println(err)
  2518  
  2519  				res.WriteHeader(http.StatusInternalServerError)
  2520  				errView, err := Error500()
  2521  				if err != nil {
  2522  					log.Println(err)
  2523  				}
  2524  
  2525  				res.Write(errView)
  2526  				return
  2527  			}
  2528  			continue
  2529  		}
  2530  
  2531  		post := adminPostListItem(p, t, status)
  2532  		_, err = b.Write([]byte(post))
  2533  		if err != nil {
  2534  			log.Println(err)
  2535  
  2536  			res.WriteHeader(http.StatusInternalServerError)
  2537  			errView, err := Error500()
  2538  			if err != nil {
  2539  				log.Println(err)
  2540  			}
  2541  
  2542  			res.Write(errView)
  2543  			return
  2544  		}
  2545  	}
  2546  
  2547  	_, err := b.WriteString(`</ul></div></div>`)
  2548  	if err != nil {
  2549  		log.Println(err)
  2550  
  2551  		res.WriteHeader(http.StatusInternalServerError)
  2552  		errView, err := Error500()
  2553  		if err != nil {
  2554  			log.Println(err)
  2555  		}
  2556  
  2557  		res.Write(errView)
  2558  		return
  2559  	}
  2560  
  2561  	script := `
  2562  	<script>
  2563  		$(function() {
  2564  			var del = $('.quick-delete-post.__ponzu span');
  2565  			del.on('click', function(e) {
  2566  				if (confirm("[Ponzu] Please confirm:\n\nAre you sure you want to delete this post?\nThis cannot be undone.")) {
  2567  					$(e.target).parent().submit();
  2568  				}
  2569  			});
  2570  		});
  2571  
  2572  		// disable link from being clicked if parent is 'disabled'
  2573  		$(function() {
  2574  			$('ul.pagination li.disabled a').on('click', function(e) {
  2575  				e.preventDefault();
  2576  			});
  2577  		});
  2578  	</script>
  2579  	`
  2580  
  2581  	btn := `<div class="col s3">
  2582  		<a href="/admin/edit?type=` + t + `" class="btn new-post waves-effect waves-light">
  2583  			New ` + t + `
  2584  		</a>`
  2585  
  2586  	html += b.String() + script + btn + `</div></div>`
  2587  
  2588  	adminView, err := Admin([]byte(html))
  2589  	if err != nil {
  2590  		log.Println(err)
  2591  		res.WriteHeader(http.StatusInternalServerError)
  2592  		return
  2593  	}
  2594  
  2595  	res.Header().Set("Content-Type", "text/html")
  2596  	res.Write(adminView)
  2597  }
  2598  
  2599  func uploadSearchHandler(res http.ResponseWriter, req *http.Request) {
  2600  	q := req.URL.Query()
  2601  	t := "__uploads"
  2602  	search := q.Get("q")
  2603  	status := q.Get("status")
  2604  
  2605  	if t == "" || search == "" {
  2606  		http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound)
  2607  		return
  2608  	}
  2609  
  2610  	posts := db.UploadAll()
  2611  	b := &bytes.Buffer{}
  2612  	p := interface{}(&item.FileUpload{}).(editor.Editable)
  2613  
  2614  	html := `<div class="col s9 card">		
  2615  					<div class="card-content">
  2616  					<div class="row">
  2617  					<div class="card-title col s7">Uploads Results</div>	
  2618  					<form class="col s4" action="/admin/uploads/search" method="get">
  2619  						<div class="input-field post-search inline">
  2620  							<label class="active">Search:</label>
  2621  							<i class="right material-icons search-icon">search</i>
  2622  							<input class="search" name="q" type="text" placeholder="Within all Upload fields" class="search"/>
  2623  							<input type="hidden" name="type" value="` + t + `" />
  2624  						</div>
  2625                      </form>	
  2626  					</div>
  2627  					<ul class="posts row">`
  2628  
  2629  	for i := range posts {
  2630  		// skip posts that don't have any matching search criteria
  2631  		match := strings.ToLower(search)
  2632  		all := strings.ToLower(string(posts[i]))
  2633  		if !strings.Contains(all, match) {
  2634  			continue
  2635  		}
  2636  
  2637  		err := json.Unmarshal(posts[i], &p)
  2638  		if err != nil {
  2639  			log.Println("Error unmarshal search result json into", t, err, posts[i])
  2640  
  2641  			post := `<li class="col s12">Error decoding data. Possible file corruption.</li>`
  2642  			_, err = b.Write([]byte(post))
  2643  			if err != nil {
  2644  				log.Println(err)
  2645  
  2646  				res.WriteHeader(http.StatusInternalServerError)
  2647  				errView, err := Error500()
  2648  				if err != nil {
  2649  					log.Println(err)
  2650  				}
  2651  
  2652  				res.Write(errView)
  2653  				return
  2654  			}
  2655  			continue
  2656  		}
  2657  
  2658  		post := adminPostListItem(p, t, status)
  2659  		_, err = b.Write([]byte(post))
  2660  		if err != nil {
  2661  			log.Println(err)
  2662  
  2663  			res.WriteHeader(http.StatusInternalServerError)
  2664  			errView, err := Error500()
  2665  			if err != nil {
  2666  				log.Println(err)
  2667  			}
  2668  
  2669  			res.Write(errView)
  2670  			return
  2671  		}
  2672  	}
  2673  
  2674  	_, err := b.WriteString(`</ul></div></div>`)
  2675  	if err != nil {
  2676  		log.Println(err)
  2677  
  2678  		res.WriteHeader(http.StatusInternalServerError)
  2679  		errView, err := Error500()
  2680  		if err != nil {
  2681  			log.Println(err)
  2682  		}
  2683  
  2684  		res.Write(errView)
  2685  		return
  2686  	}
  2687  
  2688  	btn := `<div class="col s3"><a href="/admin/edit/upload" class="btn new-post waves-effect waves-light">New Upload</a></div></div>`
  2689  	html = html + b.String() + btn
  2690  
  2691  	adminView, err := Admin([]byte(html))
  2692  	if err != nil {
  2693  		log.Println(err)
  2694  		res.WriteHeader(http.StatusInternalServerError)
  2695  		return
  2696  	}
  2697  
  2698  	res.Header().Set("Content-Type", "text/html")
  2699  	res.Write(adminView)
  2700  }
  2701  
  2702  func addonsHandler(res http.ResponseWriter, req *http.Request) {
  2703  	switch req.Method {
  2704  	case http.MethodGet:
  2705  		all := db.AddonAll()
  2706  		list := &bytes.Buffer{}
  2707  
  2708  		for i := range all {
  2709  			v := adminAddonListItem(all[i])
  2710  			_, err := list.Write(v)
  2711  			if err != nil {
  2712  				log.Println("Error writing bytes to addon list view:", err)
  2713  				res.WriteHeader(http.StatusInternalServerError)
  2714  				errView, err := Error500()
  2715  				if err != nil {
  2716  					log.Println(err)
  2717  					return
  2718  				}
  2719  
  2720  				res.Write(errView)
  2721  				return
  2722  			}
  2723  		}
  2724  
  2725  		html := &bytes.Buffer{}
  2726  		open := `<div class="col s9 card">		
  2727  				<div class="card-content">
  2728  				<div class="row">
  2729  				<div class="card-title col s7">Addons</div>	
  2730  				</div>
  2731  				<ul class="posts row">`
  2732  
  2733  		_, err := html.WriteString(open)
  2734  		if err != nil {
  2735  			log.Println("Error writing open html to addon html view:", err)
  2736  			res.WriteHeader(http.StatusInternalServerError)
  2737  			errView, err := Error500()
  2738  			if err != nil {
  2739  				log.Println(err)
  2740  				return
  2741  			}
  2742  
  2743  			res.Write(errView)
  2744  			return
  2745  		}
  2746  
  2747  		_, err = html.Write(list.Bytes())
  2748  		if err != nil {
  2749  			log.Println("Error writing list bytes to addon html view:", err)
  2750  			res.WriteHeader(http.StatusInternalServerError)
  2751  			errView, err := Error500()
  2752  			if err != nil {
  2753  				log.Println(err)
  2754  				return
  2755  			}
  2756  
  2757  			res.Write(errView)
  2758  			return
  2759  		}
  2760  
  2761  		_, err = html.WriteString(`</ul></div></div>`)
  2762  		if err != nil {
  2763  			log.Println("Error writing close html to addon html view:", err)
  2764  			res.WriteHeader(http.StatusInternalServerError)
  2765  			errView, err := Error500()
  2766  			if err != nil {
  2767  				log.Println(err)
  2768  				return
  2769  			}
  2770  
  2771  			res.Write(errView)
  2772  			return
  2773  		}
  2774  
  2775  		if html.Len() == 0 {
  2776  			_, err := html.WriteString(`<p>No addons available.</p>`)
  2777  			if err != nil {
  2778  				log.Println("Error writing default addon html to admin view:", err)
  2779  				res.WriteHeader(http.StatusInternalServerError)
  2780  				errView, err := Error500()
  2781  				if err != nil {
  2782  					log.Println(err)
  2783  					return
  2784  				}
  2785  
  2786  				res.Write(errView)
  2787  				return
  2788  			}
  2789  		}
  2790  
  2791  		view, err := Admin(html.Bytes())
  2792  		if err != nil {
  2793  			log.Println("Error writing addon html to admin view:", err)
  2794  			res.WriteHeader(http.StatusInternalServerError)
  2795  			errView, err := Error500()
  2796  			if err != nil {
  2797  				log.Println(err)
  2798  				return
  2799  			}
  2800  
  2801  			res.Write(errView)
  2802  			return
  2803  		}
  2804  
  2805  		res.Write(view)
  2806  
  2807  	case http.MethodPost:
  2808  		err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
  2809  		if err != nil {
  2810  			log.Println(err)
  2811  			res.WriteHeader(http.StatusInternalServerError)
  2812  			errView, err := Error500()
  2813  			if err != nil {
  2814  				return
  2815  			}
  2816  
  2817  			res.Write(errView)
  2818  			return
  2819  		}
  2820  
  2821  		id := req.PostFormValue("id")
  2822  		action := strings.ToLower(req.PostFormValue("action"))
  2823  
  2824  		at, ok := addon.Types[id]
  2825  		if !ok {
  2826  			log.Println("Error: no addon type found for:", id)
  2827  			log.Println(err)
  2828  			res.WriteHeader(http.StatusNotFound)
  2829  			errView, err := Error404()
  2830  			if err != nil {
  2831  				return
  2832  			}
  2833  
  2834  			res.Write(errView)
  2835  			return
  2836  		}
  2837  
  2838  		b, err := db.Addon(id)
  2839  		if err == db.ErrNoAddonExists {
  2840  			log.Println(err)
  2841  			res.WriteHeader(http.StatusNotFound)
  2842  			errView, err := Error404()
  2843  			if err != nil {
  2844  				return
  2845  			}
  2846  
  2847  			res.Write(errView)
  2848  			return
  2849  		}
  2850  		if err != nil {
  2851  			log.Println(err)
  2852  			res.WriteHeader(http.StatusInternalServerError)
  2853  			errView, err := Error500()
  2854  			if err != nil {
  2855  				return
  2856  			}
  2857  
  2858  			res.Write(errView)
  2859  			return
  2860  		}
  2861  
  2862  		adn := at()
  2863  		err = json.Unmarshal(b, adn)
  2864  		if err != nil {
  2865  			log.Println(err)
  2866  			res.WriteHeader(http.StatusInternalServerError)
  2867  			errView, err := Error500()
  2868  			if err != nil {
  2869  				return
  2870  			}
  2871  
  2872  			res.Write(errView)
  2873  			return
  2874  		}
  2875  
  2876  		h, ok := adn.(item.Hookable)
  2877  		if !ok {
  2878  			log.Println("Addon", adn, "does not implement the item.Hookable interface or embed item.Item")
  2879  			return
  2880  		}
  2881  
  2882  		switch action {
  2883  		case "enable":
  2884  			err := h.BeforeEnable(res, req)
  2885  			if err != nil {
  2886  				log.Println(err)
  2887  				res.WriteHeader(http.StatusInternalServerError)
  2888  				errView, err := Error500()
  2889  				if err != nil {
  2890  					return
  2891  				}
  2892  
  2893  				res.Write(errView)
  2894  				return
  2895  			}
  2896  
  2897  			err = addon.Enable(id)
  2898  			if err != nil {
  2899  				log.Println(err)
  2900  				res.WriteHeader(http.StatusInternalServerError)
  2901  				errView, err := Error500()
  2902  				if err != nil {
  2903  					return
  2904  				}
  2905  
  2906  				res.Write(errView)
  2907  				return
  2908  			}
  2909  
  2910  			err = h.AfterEnable(res, req)
  2911  			if err != nil {
  2912  				log.Println(err)
  2913  				res.WriteHeader(http.StatusInternalServerError)
  2914  				errView, err := Error500()
  2915  				if err != nil {
  2916  					return
  2917  				}
  2918  
  2919  				res.Write(errView)
  2920  				return
  2921  			}
  2922  
  2923  		case "disable":
  2924  			err := h.BeforeDisable(res, req)
  2925  			if err != nil {
  2926  				log.Println(err)
  2927  				res.WriteHeader(http.StatusInternalServerError)
  2928  				errView, err := Error500()
  2929  				if err != nil {
  2930  					return
  2931  				}
  2932  
  2933  				res.Write(errView)
  2934  				return
  2935  			}
  2936  
  2937  			err = addon.Disable(id)
  2938  			if err != nil {
  2939  				log.Println(err)
  2940  				res.WriteHeader(http.StatusInternalServerError)
  2941  				errView, err := Error500()
  2942  				if err != nil {
  2943  					return
  2944  				}
  2945  
  2946  				res.Write(errView)
  2947  				return
  2948  			}
  2949  
  2950  			err = h.AfterDisable(res, req)
  2951  			if err != nil {
  2952  				log.Println(err)
  2953  				res.WriteHeader(http.StatusInternalServerError)
  2954  				errView, err := Error500()
  2955  				if err != nil {
  2956  					return
  2957  				}
  2958  
  2959  				res.Write(errView)
  2960  				return
  2961  			}
  2962  		default:
  2963  			res.WriteHeader(http.StatusBadRequest)
  2964  			errView, err := Error400()
  2965  			if err != nil {
  2966  				return
  2967  			}
  2968  
  2969  			res.Write(errView)
  2970  			return
  2971  		}
  2972  
  2973  		http.Redirect(res, req, req.URL.String(), http.StatusFound)
  2974  
  2975  	default:
  2976  		res.WriteHeader(http.StatusBadRequest)
  2977  		errView, err := Error400()
  2978  		if err != nil {
  2979  			log.Println(err)
  2980  			return
  2981  		}
  2982  
  2983  		res.Write(errView)
  2984  		return
  2985  	}
  2986  }
  2987  
  2988  func addonHandler(res http.ResponseWriter, req *http.Request) {
  2989  	switch req.Method {
  2990  	case http.MethodGet:
  2991  		id := req.FormValue("id")
  2992  
  2993  		data, err := db.Addon(id)
  2994  		if err != nil {
  2995  			log.Println(err)
  2996  			res.WriteHeader(http.StatusInternalServerError)
  2997  			errView, err := Error500()
  2998  			if err != nil {
  2999  				return
  3000  			}
  3001  
  3002  			res.Write(errView)
  3003  			return
  3004  		}
  3005  
  3006  		_, ok := addon.Types[id]
  3007  		if !ok {
  3008  			log.Println("Addon: ", id, "is not found in addon.Types map")
  3009  			res.WriteHeader(http.StatusNotFound)
  3010  			errView, err := Error404()
  3011  			if err != nil {
  3012  				return
  3013  			}
  3014  
  3015  			res.Write(errView)
  3016  			return
  3017  		}
  3018  
  3019  		m, err := addon.Manage(data, id)
  3020  		if err != nil {
  3021  			log.Println(err)
  3022  			res.WriteHeader(http.StatusInternalServerError)
  3023  			errView, err := Error500()
  3024  			if err != nil {
  3025  				return
  3026  			}
  3027  
  3028  			res.Write(errView)
  3029  			return
  3030  		}
  3031  
  3032  		addonView, err := Admin(m)
  3033  		if err != nil {
  3034  			log.Println(err)
  3035  			res.WriteHeader(http.StatusInternalServerError)
  3036  			return
  3037  		}
  3038  
  3039  		res.Header().Set("Content-Type", "text/html")
  3040  		res.Write(addonView)
  3041  
  3042  	case http.MethodPost:
  3043  		// save req.Form
  3044  		err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
  3045  		if err != nil {
  3046  			log.Println(err)
  3047  			res.WriteHeader(http.StatusInternalServerError)
  3048  			errView, err := Error500()
  3049  			if err != nil {
  3050  				return
  3051  			}
  3052  
  3053  			res.Write(errView)
  3054  			return
  3055  		}
  3056  
  3057  		name := req.FormValue("addon_name")
  3058  		id := req.FormValue("addon_reverse_dns")
  3059  
  3060  		at, ok := addon.Types[id]
  3061  		if !ok {
  3062  			log.Println("Error: addon", name, "has no record in addon.Types map at", id)
  3063  			res.WriteHeader(http.StatusBadRequest)
  3064  			errView, err := Error400()
  3065  			if err != nil {
  3066  				return
  3067  			}
  3068  
  3069  			res.Write(errView)
  3070  			return
  3071  		}
  3072  
  3073  		// if Hookable, call BeforeSave prior to saving
  3074  		h, ok := at().(item.Hookable)
  3075  		if ok {
  3076  			err := h.BeforeSave(res, req)
  3077  			if err != nil {
  3078  				log.Println("Error running BeforeSave method in addonHandler for:", id, err)
  3079  				return
  3080  			}
  3081  		}
  3082  
  3083  		err = db.SetAddon(req.Form, at())
  3084  		if err != nil {
  3085  			log.Println("Error saving addon:", name, err)
  3086  			res.WriteHeader(http.StatusInternalServerError)
  3087  			errView, err := Error500()
  3088  			if err != nil {
  3089  				return
  3090  			}
  3091  
  3092  			res.Write(errView)
  3093  			return
  3094  		}
  3095  
  3096  		http.Redirect(res, req, "/admin/addon?id="+id, http.StatusFound)
  3097  
  3098  	default:
  3099  		res.WriteHeader(http.StatusBadRequest)
  3100  		errView, err := Error405()
  3101  		if err != nil {
  3102  			log.Println(err)
  3103  			return
  3104  		}
  3105  
  3106  		res.Write(errView)
  3107  		return
  3108  	}
  3109  }
  3110  
  3111  func adminAddonListItem(data []byte) []byte {
  3112  	id := gjson.GetBytes(data, "addon_reverse_dns").String()
  3113  	status := gjson.GetBytes(data, "addon_status").String()
  3114  	name := gjson.GetBytes(data, "addon_name").String()
  3115  	author := gjson.GetBytes(data, "addon_author").String()
  3116  	authorURL := gjson.GetBytes(data, "addon_author_url").String()
  3117  	version := gjson.GetBytes(data, "addon_version").String()
  3118  
  3119  	var action string
  3120  	var buttonClass string
  3121  	if status != addon.StatusEnabled {
  3122  		action = "Enable"
  3123  		buttonClass = "green"
  3124  	} else {
  3125  		action = "Disable"
  3126  		buttonClass = "red"
  3127  	}
  3128  
  3129  	a := `
  3130  			<li class="col s12">
  3131  				<div class="row">
  3132  					<div class="col s9">
  3133  						<a class="addon-name" href="/admin/addon?id=` + id + `" alt="Configure '` + name + `'">` + name + `</a>
  3134  						<span class="addon-meta addon-author">by: <a href="` + authorURL + `">` + author + `</a></span>
  3135  						<span class="addon-meta addon-version">version: ` + version + `</span>
  3136  					</div>
  3137  
  3138  					<div class="col s3">					
  3139  						<form enctype="multipart/form-data" class="quick-` + strings.ToLower(action) + `-addon __ponzu right" action="/admin/addons" method="post">
  3140  							<button class="btn waves-effect waves-effect-light ` + buttonClass + `">` + action + `</button>
  3141  							<input type="hidden" name="id" value="` + id + `" />
  3142  							<input type="hidden" name="action" value="` + action + `" />
  3143  						</form>
  3144  					</div>
  3145  				</div>
  3146  			</li>`
  3147  
  3148  	return []byte(a)
  3149  }