github.com/bosssauce/ponzu@v0.11.1-0.20200102001432-9bc41b703131/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/ponzu-cms/ponzu/management/editor"
    16  	"github.com/ponzu-cms/ponzu/management/format"
    17  	"github.com/ponzu-cms/ponzu/management/manager"
    18  	"github.com/ponzu-cms/ponzu/system/addon"
    19  	"github.com/ponzu-cms/ponzu/system/admin/config"
    20  	"github.com/ponzu-cms/ponzu/system/admin/upload"
    21  	"github.com/ponzu-cms/ponzu/system/admin/user"
    22  	"github.com/ponzu-cms/ponzu/system/api"
    23  	"github.com/ponzu-cms/ponzu/system/api/analytics"
    24  	"github.com/ponzu-cms/ponzu/system/db"
    25  	"github.com/ponzu-cms/ponzu/system/item"
    26  	"github.com/ponzu-cms/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  	action := "/admin/edit/delete"
  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  		action = "/admin/edit/upload/delete"
  1568  	}
  1569  
  1570  	post := `
  1571  			<li class="col s12">
  1572  				` + link + `
  1573  				<span class="post-detail">Updated: ` + updatedTime + `</span>
  1574  				<span class="publish-date right">` + publishTime + `</span>
  1575  
  1576  				<form enctype="multipart/form-data" class="quick-delete-post __ponzu right" action="` + action + `" method="post">
  1577  					<span>Delete</span>
  1578  					<input type="hidden" name="id" value="` + cid + `" />
  1579  					<input type="hidden" name="type" value="` + typeName + status + `" />
  1580  				</form>
  1581  			</li>`
  1582  
  1583  	return []byte(post)
  1584  }
  1585  
  1586  func approveContentHandler(res http.ResponseWriter, req *http.Request) {
  1587  	if req.Method != http.MethodPost {
  1588  		res.WriteHeader(http.StatusMethodNotAllowed)
  1589  		errView, err := Error405()
  1590  		if err != nil {
  1591  			return
  1592  		}
  1593  
  1594  		res.Write(errView)
  1595  		return
  1596  	}
  1597  
  1598  	err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
  1599  	if err != nil {
  1600  		res.WriteHeader(http.StatusInternalServerError)
  1601  		errView, err := Error500()
  1602  		if err != nil {
  1603  			return
  1604  		}
  1605  
  1606  		res.Write(errView)
  1607  		return
  1608  	}
  1609  
  1610  	pendingID := req.FormValue("id")
  1611  
  1612  	t := req.FormValue("type")
  1613  	if strings.Contains(t, "__") {
  1614  		t = strings.Split(t, "__")[0]
  1615  	}
  1616  
  1617  	post := item.Types[t]()
  1618  
  1619  	// run hooks
  1620  	hook, ok := post.(item.Hookable)
  1621  	if !ok {
  1622  		log.Println("Type", t, "does not implement item.Hookable or embed item.Item.")
  1623  		res.WriteHeader(http.StatusBadRequest)
  1624  		errView, err := Error400()
  1625  		if err != nil {
  1626  			return
  1627  		}
  1628  
  1629  		res.Write(errView)
  1630  		return
  1631  	}
  1632  
  1633  	// check if we have a Mergeable
  1634  	m, ok := post.(editor.Mergeable)
  1635  	if !ok {
  1636  		log.Println("Content type", t, "must implement editor.Mergeable before it can be approved.")
  1637  		res.WriteHeader(http.StatusBadRequest)
  1638  		errView, err := Error400()
  1639  		if err != nil {
  1640  			return
  1641  		}
  1642  
  1643  		res.Write(errView)
  1644  		return
  1645  	}
  1646  
  1647  	dec := schema.NewDecoder()
  1648  	dec.IgnoreUnknownKeys(true)
  1649  	dec.SetAliasTag("json")
  1650  	err = dec.Decode(post, req.Form)
  1651  	if err != nil {
  1652  		log.Println("Error decoding post form for content approval:", t, err)
  1653  		res.WriteHeader(http.StatusInternalServerError)
  1654  		errView, err := Error500()
  1655  		if err != nil {
  1656  			return
  1657  		}
  1658  
  1659  		res.Write(errView)
  1660  		return
  1661  	}
  1662  
  1663  	err = hook.BeforeApprove(res, req)
  1664  	if err != nil {
  1665  		log.Println("Error running BeforeApprove hook in approveContentHandler for:", t, err)
  1666  		return
  1667  	}
  1668  
  1669  	// call its Approve method
  1670  	err = m.Approve(res, req)
  1671  	if err != nil {
  1672  		log.Println("Error running Approve method in approveContentHandler for:", t, err)
  1673  		return
  1674  	}
  1675  
  1676  	err = hook.AfterApprove(res, req)
  1677  	if err != nil {
  1678  		log.Println("Error running AfterApprove hook in approveContentHandler for:", t, err)
  1679  		return
  1680  	}
  1681  
  1682  	err = hook.BeforeSave(res, req)
  1683  	if err != nil {
  1684  		log.Println("Error running BeforeSave hook in approveContentHandler for:", t, err)
  1685  		return
  1686  	}
  1687  
  1688  	// Store the content in the bucket t
  1689  	id, err := db.SetContent(t+":-1", req.Form)
  1690  	if err != nil {
  1691  		log.Println("Error storing content in approveContentHandler for:", t, err)
  1692  		res.WriteHeader(http.StatusInternalServerError)
  1693  		errView, err := Error500()
  1694  		if err != nil {
  1695  			return
  1696  		}
  1697  
  1698  		res.Write(errView)
  1699  		return
  1700  	}
  1701  
  1702  	// set the target in the context so user can get saved value from db in hook
  1703  	ctx := context.WithValue(req.Context(), "target", fmt.Sprintf("%s:%d", t, id))
  1704  	req = req.WithContext(ctx)
  1705  
  1706  	err = hook.AfterSave(res, req)
  1707  	if err != nil {
  1708  		log.Println("Error running AfterSave hook in approveContentHandler for:", t, err)
  1709  		return
  1710  	}
  1711  
  1712  	if pendingID != "" {
  1713  		err = db.DeleteContent(req.FormValue("type") + ":" + pendingID)
  1714  		if err != nil {
  1715  			log.Println("Failed to remove content after approval:", err)
  1716  		}
  1717  	}
  1718  
  1719  	// redirect to the new approved content's editor
  1720  	redir := req.URL.Scheme + req.URL.Host + strings.TrimSuffix(req.URL.Path, "/approve")
  1721  	redir += fmt.Sprintf("?type=%s&id=%d", t, id)
  1722  	http.Redirect(res, req, redir, http.StatusFound)
  1723  }
  1724  
  1725  func editHandler(res http.ResponseWriter, req *http.Request) {
  1726  	switch req.Method {
  1727  	case http.MethodGet:
  1728  		q := req.URL.Query()
  1729  		i := q.Get("id")
  1730  		t := q.Get("type")
  1731  		status := q.Get("status")
  1732  
  1733  		contentType, ok := item.Types[t]
  1734  		if !ok {
  1735  			fmt.Fprintf(res, item.ErrTypeNotRegistered.Error(), t)
  1736  			return
  1737  		}
  1738  		post := contentType()
  1739  
  1740  		if i != "" {
  1741  			if status == "pending" {
  1742  				t = t + "__pending"
  1743  			}
  1744  
  1745  			data, err := db.Content(t + ":" + i)
  1746  			if err != nil {
  1747  				log.Println(err)
  1748  				res.WriteHeader(http.StatusInternalServerError)
  1749  				errView, err := Error500()
  1750  				if err != nil {
  1751  					return
  1752  				}
  1753  
  1754  				res.Write(errView)
  1755  				return
  1756  			}
  1757  
  1758  			if len(data) < 1 || data == nil {
  1759  				res.WriteHeader(http.StatusNotFound)
  1760  				errView, err := Error404()
  1761  				if err != nil {
  1762  					return
  1763  				}
  1764  
  1765  				res.Write(errView)
  1766  				return
  1767  			}
  1768  
  1769  			err = json.Unmarshal(data, post)
  1770  			if err != nil {
  1771  				log.Println(err)
  1772  				res.WriteHeader(http.StatusInternalServerError)
  1773  				errView, err := Error500()
  1774  				if err != nil {
  1775  					return
  1776  				}
  1777  
  1778  				res.Write(errView)
  1779  				return
  1780  			}
  1781  		} else {
  1782  			item, ok := post.(item.Identifiable)
  1783  			if !ok {
  1784  				log.Println("Content type", t, "doesn't implement item.Identifiable")
  1785  				return
  1786  			}
  1787  
  1788  			item.SetItemID(-1)
  1789  		}
  1790  
  1791  		m, err := manager.Manage(post.(editor.Editable), t)
  1792  		if err != nil {
  1793  			log.Println(err)
  1794  			res.WriteHeader(http.StatusInternalServerError)
  1795  			errView, err := Error500()
  1796  			if err != nil {
  1797  				return
  1798  			}
  1799  
  1800  			res.Write(errView)
  1801  			return
  1802  		}
  1803  
  1804  		adminView, err := Admin(m)
  1805  		if err != nil {
  1806  			log.Println(err)
  1807  			res.WriteHeader(http.StatusInternalServerError)
  1808  			return
  1809  		}
  1810  
  1811  		res.Header().Set("Content-Type", "text/html")
  1812  		res.Write(adminView)
  1813  
  1814  	case http.MethodPost:
  1815  		err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
  1816  		if err != nil {
  1817  			log.Println(err)
  1818  			res.WriteHeader(http.StatusInternalServerError)
  1819  			errView, err := Error500()
  1820  			if err != nil {
  1821  				return
  1822  			}
  1823  
  1824  			res.Write(errView)
  1825  			return
  1826  		}
  1827  
  1828  		cid := req.FormValue("id")
  1829  		t := req.FormValue("type")
  1830  		ts := req.FormValue("timestamp")
  1831  		up := req.FormValue("updated")
  1832  
  1833  		// create a timestamp if one was not set
  1834  		if ts == "" {
  1835  			ts = fmt.Sprintf("%d", int64(time.Nanosecond)*time.Now().UTC().UnixNano()/int64(time.Millisecond))
  1836  			req.PostForm.Set("timestamp", ts)
  1837  		}
  1838  
  1839  		if up == "" {
  1840  			req.PostForm.Set("updated", ts)
  1841  		}
  1842  
  1843  		urlPaths, err := upload.StoreFiles(req)
  1844  		if err != nil {
  1845  			log.Println(err)
  1846  			res.WriteHeader(http.StatusInternalServerError)
  1847  			errView, err := Error500()
  1848  			if err != nil {
  1849  				return
  1850  			}
  1851  
  1852  			res.Write(errView)
  1853  			return
  1854  		}
  1855  
  1856  		for name, urlPath := range urlPaths {
  1857  			req.PostForm.Set(name, urlPath)
  1858  		}
  1859  
  1860  		// check for any multi-value fields (ex. checkbox fields)
  1861  		// and correctly format for db storage. Essentially, we need
  1862  		// fieldX.0: value1, fieldX.1: value2 => fieldX: []string{value1, value2}
  1863  		fieldOrderValue := make(map[string]map[string][]string)
  1864  		for k, v := range req.PostForm {
  1865  			if strings.Contains(k, ".") {
  1866  				fo := strings.Split(k, ".")
  1867  
  1868  				// put the order and the field value into map
  1869  				field := string(fo[0])
  1870  				order := string(fo[1])
  1871  				if len(fieldOrderValue[field]) == 0 {
  1872  					fieldOrderValue[field] = make(map[string][]string)
  1873  				}
  1874  
  1875  				// orderValue is 0:[?type=Thing&id=1]
  1876  				orderValue := fieldOrderValue[field]
  1877  				orderValue[order] = v
  1878  				fieldOrderValue[field] = orderValue
  1879  
  1880  				// discard the post form value with name.N
  1881  				req.PostForm.Del(k)
  1882  			}
  1883  
  1884  		}
  1885  
  1886  		// add/set the key & value to the post form in order
  1887  		for f, ov := range fieldOrderValue {
  1888  			for i := 0; i < len(ov); i++ {
  1889  				position := fmt.Sprintf("%d", i)
  1890  				fieldValue := ov[position]
  1891  
  1892  				if req.PostForm.Get(f) == "" {
  1893  					for i, fv := range fieldValue {
  1894  						if i == 0 {
  1895  							req.PostForm.Set(f, fv)
  1896  						} else {
  1897  							req.PostForm.Add(f, fv)
  1898  						}
  1899  					}
  1900  				} else {
  1901  					for _, fv := range fieldValue {
  1902  						req.PostForm.Add(f, fv)
  1903  					}
  1904  				}
  1905  			}
  1906  		}
  1907  
  1908  		pt := t
  1909  		if strings.Contains(t, "__") {
  1910  			pt = strings.Split(t, "__")[0]
  1911  		}
  1912  
  1913  		p, ok := item.Types[pt]
  1914  		if !ok {
  1915  			log.Println("Type", t, "is not a content type. Cannot edit or save.")
  1916  			res.WriteHeader(http.StatusBadRequest)
  1917  			errView, err := Error400()
  1918  			if err != nil {
  1919  				return
  1920  			}
  1921  
  1922  			res.Write(errView)
  1923  			return
  1924  		}
  1925  
  1926  		post := p()
  1927  		hook, ok := post.(item.Hookable)
  1928  		if !ok {
  1929  			log.Println("Type", pt, "does not implement item.Hookable or embed item.Item.")
  1930  			res.WriteHeader(http.StatusBadRequest)
  1931  			errView, err := Error400()
  1932  			if err != nil {
  1933  				return
  1934  			}
  1935  
  1936  			res.Write(errView)
  1937  			return
  1938  		}
  1939  
  1940  		// Let's be nice and make a proper item for the Hookable methods
  1941  		dec := schema.NewDecoder()
  1942  		dec.IgnoreUnknownKeys(true)
  1943  		dec.SetAliasTag("json")
  1944  		err = dec.Decode(post, req.PostForm)
  1945  		if err != nil {
  1946  			log.Println("Error decoding post form for edit handler:", t, err)
  1947  			res.WriteHeader(http.StatusBadRequest)
  1948  			errView, err := Error400()
  1949  			if err != nil {
  1950  				return
  1951  			}
  1952  
  1953  			res.Write(errView)
  1954  			return
  1955  		}
  1956  
  1957  		if cid == "-1" {
  1958  			err = hook.BeforeAdminCreate(res, req)
  1959  			if err != nil {
  1960  				log.Println("Error running BeforeAdminCreate method in editHandler for:", t, err)
  1961  				return
  1962  			}
  1963  		} else {
  1964  			err = hook.BeforeAdminUpdate(res, req)
  1965  			if err != nil {
  1966  				log.Println("Error running BeforeAdminUpdate method in editHandler for:", t, err)
  1967  				return
  1968  			}
  1969  		}
  1970  
  1971  		err = hook.BeforeSave(res, req)
  1972  		if err != nil {
  1973  			log.Println("Error running BeforeSave method in editHandler for:", t, err)
  1974  			return
  1975  		}
  1976  
  1977  		id, err := db.SetContent(t+":"+cid, req.PostForm)
  1978  		if err != nil {
  1979  			log.Println(err)
  1980  			res.WriteHeader(http.StatusInternalServerError)
  1981  			errView, err := Error500()
  1982  			if err != nil {
  1983  				return
  1984  			}
  1985  
  1986  			res.Write(errView)
  1987  			return
  1988  		}
  1989  
  1990  		// set the target in the context so user can get saved value from db in hook
  1991  		ctx := context.WithValue(req.Context(), "target", fmt.Sprintf("%s:%d", t, id))
  1992  		req = req.WithContext(ctx)
  1993  
  1994  		err = hook.AfterSave(res, req)
  1995  		if err != nil {
  1996  			log.Println("Error running AfterSave method in editHandler for:", t, err)
  1997  			return
  1998  		}
  1999  
  2000  		if cid == "-1" {
  2001  			err = hook.AfterAdminCreate(res, req)
  2002  			if err != nil {
  2003  				log.Println("Error running AfterAdminUpdate method in editHandler for:", t, err)
  2004  				return
  2005  			}
  2006  		} else {
  2007  			err = hook.AfterAdminUpdate(res, req)
  2008  			if err != nil {
  2009  				log.Println("Error running AfterAdminUpdate method in editHandler for:", t, err)
  2010  				return
  2011  			}
  2012  		}
  2013  
  2014  		scheme := req.URL.Scheme
  2015  		host := req.URL.Host
  2016  		path := req.URL.Path
  2017  		sid := fmt.Sprintf("%d", id)
  2018  		redir := scheme + host + path + "?type=" + pt + "&id=" + sid
  2019  
  2020  		if req.URL.Query().Get("status") == "pending" {
  2021  			redir += "&status=pending"
  2022  		}
  2023  
  2024  		http.Redirect(res, req, redir, http.StatusFound)
  2025  
  2026  	default:
  2027  		res.WriteHeader(http.StatusMethodNotAllowed)
  2028  	}
  2029  }
  2030  
  2031  func deleteHandler(res http.ResponseWriter, req *http.Request) {
  2032  	if req.Method != http.MethodPost {
  2033  		res.WriteHeader(http.StatusMethodNotAllowed)
  2034  		return
  2035  	}
  2036  
  2037  	err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
  2038  	if err != nil {
  2039  		log.Println(err)
  2040  		res.WriteHeader(http.StatusInternalServerError)
  2041  		errView, err := Error500()
  2042  		if err != nil {
  2043  			return
  2044  		}
  2045  
  2046  		res.Write(errView)
  2047  		return
  2048  	}
  2049  
  2050  	id := req.FormValue("id")
  2051  	t := req.FormValue("type")
  2052  	ct := t
  2053  
  2054  	if id == "" || t == "" {
  2055  		res.WriteHeader(http.StatusBadRequest)
  2056  		return
  2057  	}
  2058  
  2059  	// catch specifier suffix from delete form value
  2060  	if strings.Contains(t, "__") {
  2061  		spec := strings.Split(t, "__")
  2062  		ct = spec[0]
  2063  	}
  2064  
  2065  	p, ok := item.Types[ct]
  2066  	if !ok {
  2067  		log.Println("Type", t, "does not implement item.Hookable or embed item.Item.")
  2068  		res.WriteHeader(http.StatusBadRequest)
  2069  		errView, err := Error400()
  2070  		if err != nil {
  2071  			return
  2072  		}
  2073  
  2074  		res.Write(errView)
  2075  		return
  2076  	}
  2077  
  2078  	post := p()
  2079  	hook, ok := post.(item.Hookable)
  2080  	if !ok {
  2081  		log.Println("Type", t, "does not implement item.Hookable or embed item.Item.")
  2082  		res.WriteHeader(http.StatusBadRequest)
  2083  		errView, err := Error400()
  2084  		if err != nil {
  2085  			return
  2086  		}
  2087  
  2088  		res.Write(errView)
  2089  		return
  2090  	}
  2091  
  2092  	data, err := db.Content(t + ":" + id)
  2093  	if err != nil {
  2094  		log.Println("Error in db.Content ", t+":"+id, err)
  2095  		return
  2096  	}
  2097  
  2098  	err = json.Unmarshal(data, post)
  2099  	if err != nil {
  2100  		log.Println("Error unmarshalling ", t, "=", id, err, " Hooks will be called on a zero-value.")
  2101  	}
  2102  
  2103  	reject := req.URL.Query().Get("reject")
  2104  	if reject == "true" {
  2105  		err = hook.BeforeReject(res, req)
  2106  		if err != nil {
  2107  			log.Println("Error running BeforeReject method in deleteHandler for:", t, err)
  2108  			return
  2109  		}
  2110  	}
  2111  
  2112  	err = hook.BeforeAdminDelete(res, req)
  2113  	if err != nil {
  2114  		log.Println("Error running BeforeAdminDelete method in deleteHandler for:", t, err)
  2115  		return
  2116  	}
  2117  
  2118  	err = hook.BeforeDelete(res, req)
  2119  	if err != nil {
  2120  		log.Println("Error running BeforeDelete method in deleteHandler for:", t, err)
  2121  		return
  2122  	}
  2123  
  2124  	err = db.DeleteContent(t + ":" + id)
  2125  	if err != nil {
  2126  		log.Println(err)
  2127  		res.WriteHeader(http.StatusInternalServerError)
  2128  		return
  2129  	}
  2130  
  2131  	err = hook.AfterDelete(res, req)
  2132  	if err != nil {
  2133  		log.Println("Error running AfterDelete method in deleteHandler for:", t, err)
  2134  		return
  2135  	}
  2136  
  2137  	err = hook.AfterAdminDelete(res, req)
  2138  	if err != nil {
  2139  		log.Println("Error running AfterDelete method in deleteHandler for:", t, err)
  2140  		return
  2141  	}
  2142  
  2143  	if reject == "true" {
  2144  		err = hook.AfterReject(res, req)
  2145  		if err != nil {
  2146  			log.Println("Error running AfterReject method in deleteHandler for:", t, err)
  2147  			return
  2148  		}
  2149  	}
  2150  
  2151  	redir := strings.TrimSuffix(req.URL.Scheme+req.URL.Host+req.URL.Path, "/edit/delete")
  2152  	redir = redir + "/contents?type=" + ct
  2153  	http.Redirect(res, req, redir, http.StatusFound)
  2154  }
  2155  
  2156  func deleteUploadHandler(res http.ResponseWriter, req *http.Request) {
  2157  	if req.Method != http.MethodPost {
  2158  		res.WriteHeader(http.StatusMethodNotAllowed)
  2159  		return
  2160  	}
  2161  
  2162  	err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
  2163  	if err != nil {
  2164  		log.Println(err)
  2165  		res.WriteHeader(http.StatusInternalServerError)
  2166  		errView, err := Error500()
  2167  		if err != nil {
  2168  			return
  2169  		}
  2170  
  2171  		res.Write(errView)
  2172  		return
  2173  	}
  2174  
  2175  	id := req.FormValue("id")
  2176  	t := "__uploads"
  2177  
  2178  	if id == "" || t == "" {
  2179  		res.WriteHeader(http.StatusBadRequest)
  2180  		return
  2181  	}
  2182  
  2183  	post := interface{}(&item.FileUpload{})
  2184  	hook, ok := post.(item.Hookable)
  2185  	if !ok {
  2186  		log.Println("Type", t, "does not implement item.Hookable or embed item.Item.")
  2187  		res.WriteHeader(http.StatusBadRequest)
  2188  		errView, err := Error400()
  2189  		if err != nil {
  2190  			return
  2191  		}
  2192  
  2193  		res.Write(errView)
  2194  		return
  2195  	}
  2196  
  2197  	err = hook.BeforeDelete(res, req)
  2198  	if err != nil {
  2199  		log.Println("Error running BeforeDelete method in deleteHandler for:", t, err)
  2200  		return
  2201  	}
  2202  
  2203  	dbTarget := t + ":" + id
  2204  
  2205  	// delete from file system, if good, we continue to delete
  2206  	// from database, if bad error 500
  2207  	err = deleteUploadFromDisk(dbTarget)
  2208  	if err != nil {
  2209  		log.Println(err)
  2210  		res.WriteHeader(http.StatusInternalServerError)
  2211  		return
  2212  	}
  2213  
  2214  	err = db.DeleteUpload(dbTarget)
  2215  	if err != nil {
  2216  		log.Println(err)
  2217  		res.WriteHeader(http.StatusInternalServerError)
  2218  		return
  2219  	}
  2220  
  2221  	err = hook.AfterDelete(res, req)
  2222  	if err != nil {
  2223  		log.Println("Error running AfterDelete method in deleteHandler for:", t, err)
  2224  		return
  2225  	}
  2226  
  2227  	redir := "/admin/uploads"
  2228  	http.Redirect(res, req, redir, http.StatusFound)
  2229  }
  2230  
  2231  func editUploadHandler(res http.ResponseWriter, req *http.Request) {
  2232  	switch req.Method {
  2233  	case http.MethodGet:
  2234  		q := req.URL.Query()
  2235  		i := q.Get("id")
  2236  		t := "__uploads"
  2237  
  2238  		post := &item.FileUpload{}
  2239  
  2240  		if i != "" {
  2241  			data, err := db.Upload(t + ":" + i)
  2242  			if err != nil {
  2243  				log.Println(err)
  2244  				res.WriteHeader(http.StatusInternalServerError)
  2245  				errView, err := Error500()
  2246  				if err != nil {
  2247  					return
  2248  				}
  2249  
  2250  				res.Write(errView)
  2251  				return
  2252  			}
  2253  
  2254  			if len(data) < 1 || data == nil {
  2255  				res.WriteHeader(http.StatusNotFound)
  2256  				errView, err := Error404()
  2257  				if err != nil {
  2258  					return
  2259  				}
  2260  
  2261  				res.Write(errView)
  2262  				return
  2263  			}
  2264  
  2265  			err = json.Unmarshal(data, post)
  2266  			if err != nil {
  2267  				log.Println(err)
  2268  				res.WriteHeader(http.StatusInternalServerError)
  2269  				errView, err := Error500()
  2270  				if err != nil {
  2271  					return
  2272  				}
  2273  
  2274  				res.Write(errView)
  2275  				return
  2276  			}
  2277  		} else {
  2278  			it, ok := interface{}(post).(item.Identifiable)
  2279  			if !ok {
  2280  				log.Println("Content type", t, "doesn't implement item.Identifiable")
  2281  				return
  2282  			}
  2283  
  2284  			it.SetItemID(-1)
  2285  		}
  2286  
  2287  		m, err := manager.Manage(interface{}(post).(editor.Editable), t)
  2288  		if err != nil {
  2289  			log.Println(err)
  2290  			res.WriteHeader(http.StatusInternalServerError)
  2291  			errView, err := Error500()
  2292  			if err != nil {
  2293  				return
  2294  			}
  2295  
  2296  			res.Write(errView)
  2297  			return
  2298  		}
  2299  
  2300  		adminView, err := Admin(m)
  2301  		if err != nil {
  2302  			log.Println(err)
  2303  			res.WriteHeader(http.StatusInternalServerError)
  2304  			return
  2305  		}
  2306  
  2307  		res.Header().Set("Content-Type", "text/html")
  2308  		res.Write(adminView)
  2309  
  2310  	case http.MethodPost:
  2311  		err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
  2312  		if err != nil {
  2313  			log.Println(err)
  2314  			res.WriteHeader(http.StatusInternalServerError)
  2315  			errView, err := Error500()
  2316  			if err != nil {
  2317  				return
  2318  			}
  2319  
  2320  			res.Write(errView)
  2321  			return
  2322  		}
  2323  
  2324  		t := req.FormValue("type")
  2325  		pt := "__uploads"
  2326  		ts := req.FormValue("timestamp")
  2327  		up := req.FormValue("updated")
  2328  
  2329  		// create a timestamp if one was not set
  2330  		if ts == "" {
  2331  			ts = fmt.Sprintf("%d", int64(time.Nanosecond)*time.Now().UTC().UnixNano()/int64(time.Millisecond))
  2332  			req.PostForm.Set("timestamp", ts)
  2333  		}
  2334  
  2335  		if up == "" {
  2336  			req.PostForm.Set("updated", ts)
  2337  		}
  2338  
  2339  		post := interface{}(&item.FileUpload{})
  2340  		hook, ok := post.(item.Hookable)
  2341  		if !ok {
  2342  			log.Println("Type", pt, "does not implement item.Hookable or embed item.Item.")
  2343  			res.WriteHeader(http.StatusBadRequest)
  2344  			errView, err := Error400()
  2345  			if err != nil {
  2346  				return
  2347  			}
  2348  
  2349  			res.Write(errView)
  2350  			return
  2351  		}
  2352  
  2353  		err = hook.BeforeSave(res, req)
  2354  		if err != nil {
  2355  			log.Println("Error running BeforeSave method in editHandler for:", t, err)
  2356  			return
  2357  		}
  2358  
  2359  		// StoreFiles has the SetUpload call (which is equivalent of SetContent in other handlers)
  2360  		urlPaths, err := upload.StoreFiles(req)
  2361  		if err != nil {
  2362  			log.Println(err)
  2363  			res.WriteHeader(http.StatusInternalServerError)
  2364  			errView, err := Error500()
  2365  			if err != nil {
  2366  				return
  2367  			}
  2368  
  2369  			res.Write(errView)
  2370  			return
  2371  		}
  2372  
  2373  		for name, urlPath := range urlPaths {
  2374  			req.PostForm.Set(name, urlPath)
  2375  		}
  2376  
  2377  		// check for any multi-value fields (ex. checkbox fields)
  2378  		// and correctly format for db storage. Essentially, we need
  2379  		// fieldX.0: value1, fieldX.1: value2 => fieldX: []string{value1, value2}
  2380  		fieldOrderValue := make(map[string]map[string][]string)
  2381  		ordVal := make(map[string][]string)
  2382  		for k, v := range req.PostForm {
  2383  			if strings.Contains(k, ".") {
  2384  				fo := strings.Split(k, ".")
  2385  
  2386  				// put the order and the field value into map
  2387  				field := string(fo[0])
  2388  				order := string(fo[1])
  2389  				fieldOrderValue[field] = ordVal
  2390  
  2391  				// orderValue is 0:[?type=Thing&id=1]
  2392  				orderValue := fieldOrderValue[field]
  2393  				orderValue[order] = v
  2394  				fieldOrderValue[field] = orderValue
  2395  
  2396  				// discard the post form value with name.N
  2397  				req.PostForm.Del(k)
  2398  			}
  2399  
  2400  		}
  2401  
  2402  		// add/set the key & value to the post form in order
  2403  		for f, ov := range fieldOrderValue {
  2404  			for i := 0; i < len(ov); i++ {
  2405  				position := fmt.Sprintf("%d", i)
  2406  				fieldValue := ov[position]
  2407  
  2408  				if req.PostForm.Get(f) == "" {
  2409  					for i, fv := range fieldValue {
  2410  						if i == 0 {
  2411  							req.PostForm.Set(f, fv)
  2412  						} else {
  2413  							req.PostForm.Add(f, fv)
  2414  						}
  2415  					}
  2416  				} else {
  2417  					for _, fv := range fieldValue {
  2418  						req.PostForm.Add(f, fv)
  2419  					}
  2420  				}
  2421  			}
  2422  		}
  2423  
  2424  		err = hook.AfterSave(res, req)
  2425  		if err != nil {
  2426  			log.Println("Error running AfterSave method in editHandler for:", t, err)
  2427  			return
  2428  		}
  2429  
  2430  		scheme := req.URL.Scheme
  2431  		host := req.URL.Host
  2432  		redir := scheme + host + "/admin/uploads"
  2433  		http.Redirect(res, req, redir, http.StatusFound)
  2434  
  2435  	case http.MethodPut:
  2436  		urlPaths, err := upload.StoreFiles(req)
  2437  		if err != nil {
  2438  			log.Println("Couldn't store file uploads.", err)
  2439  			res.WriteHeader(http.StatusInternalServerError)
  2440  			return
  2441  		}
  2442  
  2443  		res.Header().Set("Content-Type", "application/json")
  2444  		res.Write([]byte(`{"data": [{"url": "` + urlPaths["file"] + `"}]}`))
  2445  	default:
  2446  		res.WriteHeader(http.StatusMethodNotAllowed)
  2447  		return
  2448  	}
  2449  }
  2450  
  2451  /*
  2452  func editUploadHandler(res http.ResponseWriter, req *http.Request) {
  2453  	if req.Method != http.MethodPost {
  2454  		res.WriteHeader(http.StatusMethodNotAllowed)
  2455  		return
  2456  	}
  2457  
  2458  	urlPaths, err := upload.StoreFiles(req)
  2459  	if err != nil {
  2460  		log.Println("Couldn't store file uploads.", err)
  2461  		res.WriteHeader(http.StatusInternalServerError)
  2462  		return
  2463  	}
  2464  
  2465  	res.Header().Set("Content-Type", "application/json")
  2466  	res.Write([]byte(`{"data": [{"url": "` + urlPaths["file"] + `"}]}`))
  2467  }
  2468  */
  2469  
  2470  func searchHandler(res http.ResponseWriter, req *http.Request) {
  2471  	q := req.URL.Query()
  2472  	t := q.Get("type")
  2473  	search := q.Get("q")
  2474  	status := q.Get("status")
  2475  	var specifier string
  2476  
  2477  	if t == "" || search == "" {
  2478  		http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound)
  2479  		return
  2480  	}
  2481  
  2482  	if status == "pending" {
  2483  		specifier = "__" + status
  2484  	}
  2485  
  2486  	posts := db.ContentAll(t + specifier)
  2487  	b := &bytes.Buffer{}
  2488  	pt, ok := item.Types[t]
  2489  	if !ok {
  2490  		res.WriteHeader(http.StatusBadRequest)
  2491  		return
  2492  	}
  2493  
  2494  	post := pt()
  2495  
  2496  	p := post.(editor.Editable)
  2497  
  2498  	html := `<div class="col s9 card">		
  2499  					<div class="card-content">
  2500  					<div class="row">
  2501  					<div class="card-title col s7">` + t + ` Results</div>	
  2502  					<form class="col s4" action="/admin/contents/search" method="get">
  2503  						<div class="input-field post-search inline">
  2504  							<label class="active">Search:</label>
  2505  							<i class="right material-icons search-icon">search</i>
  2506  							<input class="search" name="q" type="text" placeholder="Within all ` + t + ` fields" class="search"/>
  2507  							<input type="hidden" name="type" value="` + t + `" />
  2508  							<input type="hidden" name="status" value="` + status + `" />
  2509  						</div>
  2510                      </form>	
  2511  					</div>
  2512  					<ul class="posts row">`
  2513  
  2514  	for i := range posts {
  2515  		// skip posts that don't have any matching search criteria
  2516  		match := strings.ToLower(search)
  2517  		all := strings.ToLower(string(posts[i]))
  2518  		if !strings.Contains(all, match) {
  2519  			continue
  2520  		}
  2521  
  2522  		err := json.Unmarshal(posts[i], &p)
  2523  		if err != nil {
  2524  			log.Println("Error unmarshal search result json into", t, err, posts[i])
  2525  
  2526  			post := `<li class="col s12">Error decoding data. Possible file corruption.</li>`
  2527  			_, err = b.Write([]byte(post))
  2528  			if err != nil {
  2529  				log.Println(err)
  2530  
  2531  				res.WriteHeader(http.StatusInternalServerError)
  2532  				errView, err := Error500()
  2533  				if err != nil {
  2534  					log.Println(err)
  2535  				}
  2536  
  2537  				res.Write(errView)
  2538  				return
  2539  			}
  2540  			continue
  2541  		}
  2542  
  2543  		post := adminPostListItem(p, t, status)
  2544  		_, err = b.Write([]byte(post))
  2545  		if err != nil {
  2546  			log.Println(err)
  2547  
  2548  			res.WriteHeader(http.StatusInternalServerError)
  2549  			errView, err := Error500()
  2550  			if err != nil {
  2551  				log.Println(err)
  2552  			}
  2553  
  2554  			res.Write(errView)
  2555  			return
  2556  		}
  2557  	}
  2558  
  2559  	_, err := b.WriteString(`</ul></div></div>`)
  2560  	if err != nil {
  2561  		log.Println(err)
  2562  
  2563  		res.WriteHeader(http.StatusInternalServerError)
  2564  		errView, err := Error500()
  2565  		if err != nil {
  2566  			log.Println(err)
  2567  		}
  2568  
  2569  		res.Write(errView)
  2570  		return
  2571  	}
  2572  
  2573  	script := `
  2574  	<script>
  2575  		$(function() {
  2576  			var del = $('.quick-delete-post.__ponzu span');
  2577  			del.on('click', function(e) {
  2578  				if (confirm("[Ponzu] Please confirm:\n\nAre you sure you want to delete this post?\nThis cannot be undone.")) {
  2579  					$(e.target).parent().submit();
  2580  				}
  2581  			});
  2582  		});
  2583  
  2584  		// disable link from being clicked if parent is 'disabled'
  2585  		$(function() {
  2586  			$('ul.pagination li.disabled a').on('click', function(e) {
  2587  				e.preventDefault();
  2588  			});
  2589  		});
  2590  	</script>
  2591  	`
  2592  
  2593  	btn := `<div class="col s3">
  2594  		<a href="/admin/edit?type=` + t + `" class="btn new-post waves-effect waves-light">
  2595  			New ` + t + `
  2596  		</a>`
  2597  
  2598  	html += b.String() + script + btn + `</div></div>`
  2599  
  2600  	adminView, err := Admin([]byte(html))
  2601  	if err != nil {
  2602  		log.Println(err)
  2603  		res.WriteHeader(http.StatusInternalServerError)
  2604  		return
  2605  	}
  2606  
  2607  	res.Header().Set("Content-Type", "text/html")
  2608  	res.Write(adminView)
  2609  }
  2610  
  2611  func uploadSearchHandler(res http.ResponseWriter, req *http.Request) {
  2612  	q := req.URL.Query()
  2613  	t := "__uploads"
  2614  	search := q.Get("q")
  2615  	status := q.Get("status")
  2616  
  2617  	if t == "" || search == "" {
  2618  		http.Redirect(res, req, req.URL.Scheme+req.URL.Host+"/admin", http.StatusFound)
  2619  		return
  2620  	}
  2621  
  2622  	posts := db.UploadAll()
  2623  	b := &bytes.Buffer{}
  2624  	p := interface{}(&item.FileUpload{}).(editor.Editable)
  2625  
  2626  	html := `<div class="col s9 card">		
  2627  					<div class="card-content">
  2628  					<div class="row">
  2629  					<div class="card-title col s7">Uploads Results</div>	
  2630  					<form class="col s4" action="/admin/uploads/search" method="get">
  2631  						<div class="input-field post-search inline">
  2632  							<label class="active">Search:</label>
  2633  							<i class="right material-icons search-icon">search</i>
  2634  							<input class="search" name="q" type="text" placeholder="Within all Upload fields" class="search"/>
  2635  							<input type="hidden" name="type" value="` + t + `" />
  2636  						</div>
  2637                      </form>	
  2638  					</div>
  2639  					<ul class="posts row">`
  2640  
  2641  	for i := range posts {
  2642  		// skip posts that don't have any matching search criteria
  2643  		match := strings.ToLower(search)
  2644  		all := strings.ToLower(string(posts[i]))
  2645  		if !strings.Contains(all, match) {
  2646  			continue
  2647  		}
  2648  
  2649  		err := json.Unmarshal(posts[i], &p)
  2650  		if err != nil {
  2651  			log.Println("Error unmarshal search result json into", t, err, posts[i])
  2652  
  2653  			post := `<li class="col s12">Error decoding data. Possible file corruption.</li>`
  2654  			_, err = b.Write([]byte(post))
  2655  			if err != nil {
  2656  				log.Println(err)
  2657  
  2658  				res.WriteHeader(http.StatusInternalServerError)
  2659  				errView, err := Error500()
  2660  				if err != nil {
  2661  					log.Println(err)
  2662  				}
  2663  
  2664  				res.Write(errView)
  2665  				return
  2666  			}
  2667  			continue
  2668  		}
  2669  
  2670  		post := adminPostListItem(p, t, status)
  2671  		_, err = b.Write([]byte(post))
  2672  		if err != nil {
  2673  			log.Println(err)
  2674  
  2675  			res.WriteHeader(http.StatusInternalServerError)
  2676  			errView, err := Error500()
  2677  			if err != nil {
  2678  				log.Println(err)
  2679  			}
  2680  
  2681  			res.Write(errView)
  2682  			return
  2683  		}
  2684  	}
  2685  
  2686  	_, err := b.WriteString(`</ul></div></div>`)
  2687  	if err != nil {
  2688  		log.Println(err)
  2689  
  2690  		res.WriteHeader(http.StatusInternalServerError)
  2691  		errView, err := Error500()
  2692  		if err != nil {
  2693  			log.Println(err)
  2694  		}
  2695  
  2696  		res.Write(errView)
  2697  		return
  2698  	}
  2699  
  2700  	btn := `<div class="col s3"><a href="/admin/edit/upload" class="btn new-post waves-effect waves-light">New Upload</a></div></div>`
  2701  	html = html + b.String() + btn
  2702  
  2703  	adminView, err := Admin([]byte(html))
  2704  	if err != nil {
  2705  		log.Println(err)
  2706  		res.WriteHeader(http.StatusInternalServerError)
  2707  		return
  2708  	}
  2709  
  2710  	res.Header().Set("Content-Type", "text/html")
  2711  	res.Write(adminView)
  2712  }
  2713  
  2714  func addonsHandler(res http.ResponseWriter, req *http.Request) {
  2715  	switch req.Method {
  2716  	case http.MethodGet:
  2717  		all := db.AddonAll()
  2718  		list := &bytes.Buffer{}
  2719  
  2720  		for i := range all {
  2721  			v := adminAddonListItem(all[i])
  2722  			_, err := list.Write(v)
  2723  			if err != nil {
  2724  				log.Println("Error writing bytes to addon list view:", err)
  2725  				res.WriteHeader(http.StatusInternalServerError)
  2726  				errView, err := Error500()
  2727  				if err != nil {
  2728  					log.Println(err)
  2729  					return
  2730  				}
  2731  
  2732  				res.Write(errView)
  2733  				return
  2734  			}
  2735  		}
  2736  
  2737  		html := &bytes.Buffer{}
  2738  		open := `<div class="col s9 card">		
  2739  				<div class="card-content">
  2740  				<div class="row">
  2741  				<div class="card-title col s7">Addons</div>	
  2742  				</div>
  2743  				<ul class="posts row">`
  2744  
  2745  		_, err := html.WriteString(open)
  2746  		if err != nil {
  2747  			log.Println("Error writing open html to addon html view:", err)
  2748  			res.WriteHeader(http.StatusInternalServerError)
  2749  			errView, err := Error500()
  2750  			if err != nil {
  2751  				log.Println(err)
  2752  				return
  2753  			}
  2754  
  2755  			res.Write(errView)
  2756  			return
  2757  		}
  2758  
  2759  		_, err = html.Write(list.Bytes())
  2760  		if err != nil {
  2761  			log.Println("Error writing list bytes to addon html view:", err)
  2762  			res.WriteHeader(http.StatusInternalServerError)
  2763  			errView, err := Error500()
  2764  			if err != nil {
  2765  				log.Println(err)
  2766  				return
  2767  			}
  2768  
  2769  			res.Write(errView)
  2770  			return
  2771  		}
  2772  
  2773  		_, err = html.WriteString(`</ul></div></div>`)
  2774  		if err != nil {
  2775  			log.Println("Error writing close html to addon html view:", err)
  2776  			res.WriteHeader(http.StatusInternalServerError)
  2777  			errView, err := Error500()
  2778  			if err != nil {
  2779  				log.Println(err)
  2780  				return
  2781  			}
  2782  
  2783  			res.Write(errView)
  2784  			return
  2785  		}
  2786  
  2787  		if html.Len() == 0 {
  2788  			_, err := html.WriteString(`<p>No addons available.</p>`)
  2789  			if err != nil {
  2790  				log.Println("Error writing default addon html to admin view:", err)
  2791  				res.WriteHeader(http.StatusInternalServerError)
  2792  				errView, err := Error500()
  2793  				if err != nil {
  2794  					log.Println(err)
  2795  					return
  2796  				}
  2797  
  2798  				res.Write(errView)
  2799  				return
  2800  			}
  2801  		}
  2802  
  2803  		view, err := Admin(html.Bytes())
  2804  		if err != nil {
  2805  			log.Println("Error writing addon html to admin view:", err)
  2806  			res.WriteHeader(http.StatusInternalServerError)
  2807  			errView, err := Error500()
  2808  			if err != nil {
  2809  				log.Println(err)
  2810  				return
  2811  			}
  2812  
  2813  			res.Write(errView)
  2814  			return
  2815  		}
  2816  
  2817  		res.Write(view)
  2818  
  2819  	case http.MethodPost:
  2820  		err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
  2821  		if err != nil {
  2822  			log.Println(err)
  2823  			res.WriteHeader(http.StatusInternalServerError)
  2824  			errView, err := Error500()
  2825  			if err != nil {
  2826  				return
  2827  			}
  2828  
  2829  			res.Write(errView)
  2830  			return
  2831  		}
  2832  
  2833  		id := req.PostFormValue("id")
  2834  		action := strings.ToLower(req.PostFormValue("action"))
  2835  
  2836  		at, ok := addon.Types[id]
  2837  		if !ok {
  2838  			log.Println("Error: no addon type found for:", id)
  2839  			log.Println(err)
  2840  			res.WriteHeader(http.StatusNotFound)
  2841  			errView, err := Error404()
  2842  			if err != nil {
  2843  				return
  2844  			}
  2845  
  2846  			res.Write(errView)
  2847  			return
  2848  		}
  2849  
  2850  		b, err := db.Addon(id)
  2851  		if err == db.ErrNoAddonExists {
  2852  			log.Println(err)
  2853  			res.WriteHeader(http.StatusNotFound)
  2854  			errView, err := Error404()
  2855  			if err != nil {
  2856  				return
  2857  			}
  2858  
  2859  			res.Write(errView)
  2860  			return
  2861  		}
  2862  		if err != nil {
  2863  			log.Println(err)
  2864  			res.WriteHeader(http.StatusInternalServerError)
  2865  			errView, err := Error500()
  2866  			if err != nil {
  2867  				return
  2868  			}
  2869  
  2870  			res.Write(errView)
  2871  			return
  2872  		}
  2873  
  2874  		adn := at()
  2875  		err = json.Unmarshal(b, adn)
  2876  		if err != nil {
  2877  			log.Println(err)
  2878  			res.WriteHeader(http.StatusInternalServerError)
  2879  			errView, err := Error500()
  2880  			if err != nil {
  2881  				return
  2882  			}
  2883  
  2884  			res.Write(errView)
  2885  			return
  2886  		}
  2887  
  2888  		h, ok := adn.(item.Hookable)
  2889  		if !ok {
  2890  			log.Println("Addon", adn, "does not implement the item.Hookable interface or embed item.Item")
  2891  			return
  2892  		}
  2893  
  2894  		switch action {
  2895  		case "enable":
  2896  			err := h.BeforeEnable(res, req)
  2897  			if err != nil {
  2898  				log.Println(err)
  2899  				res.WriteHeader(http.StatusInternalServerError)
  2900  				errView, err := Error500()
  2901  				if err != nil {
  2902  					return
  2903  				}
  2904  
  2905  				res.Write(errView)
  2906  				return
  2907  			}
  2908  
  2909  			err = addon.Enable(id)
  2910  			if err != nil {
  2911  				log.Println(err)
  2912  				res.WriteHeader(http.StatusInternalServerError)
  2913  				errView, err := Error500()
  2914  				if err != nil {
  2915  					return
  2916  				}
  2917  
  2918  				res.Write(errView)
  2919  				return
  2920  			}
  2921  
  2922  			err = h.AfterEnable(res, req)
  2923  			if err != nil {
  2924  				log.Println(err)
  2925  				res.WriteHeader(http.StatusInternalServerError)
  2926  				errView, err := Error500()
  2927  				if err != nil {
  2928  					return
  2929  				}
  2930  
  2931  				res.Write(errView)
  2932  				return
  2933  			}
  2934  
  2935  		case "disable":
  2936  			err := h.BeforeDisable(res, req)
  2937  			if err != nil {
  2938  				log.Println(err)
  2939  				res.WriteHeader(http.StatusInternalServerError)
  2940  				errView, err := Error500()
  2941  				if err != nil {
  2942  					return
  2943  				}
  2944  
  2945  				res.Write(errView)
  2946  				return
  2947  			}
  2948  
  2949  			err = addon.Disable(id)
  2950  			if err != nil {
  2951  				log.Println(err)
  2952  				res.WriteHeader(http.StatusInternalServerError)
  2953  				errView, err := Error500()
  2954  				if err != nil {
  2955  					return
  2956  				}
  2957  
  2958  				res.Write(errView)
  2959  				return
  2960  			}
  2961  
  2962  			err = h.AfterDisable(res, req)
  2963  			if err != nil {
  2964  				log.Println(err)
  2965  				res.WriteHeader(http.StatusInternalServerError)
  2966  				errView, err := Error500()
  2967  				if err != nil {
  2968  					return
  2969  				}
  2970  
  2971  				res.Write(errView)
  2972  				return
  2973  			}
  2974  		default:
  2975  			res.WriteHeader(http.StatusBadRequest)
  2976  			errView, err := Error400()
  2977  			if err != nil {
  2978  				return
  2979  			}
  2980  
  2981  			res.Write(errView)
  2982  			return
  2983  		}
  2984  
  2985  		http.Redirect(res, req, req.URL.String(), http.StatusFound)
  2986  
  2987  	default:
  2988  		res.WriteHeader(http.StatusBadRequest)
  2989  		errView, err := Error400()
  2990  		if err != nil {
  2991  			log.Println(err)
  2992  			return
  2993  		}
  2994  
  2995  		res.Write(errView)
  2996  		return
  2997  	}
  2998  }
  2999  
  3000  func addonHandler(res http.ResponseWriter, req *http.Request) {
  3001  	switch req.Method {
  3002  	case http.MethodGet:
  3003  		id := req.FormValue("id")
  3004  
  3005  		data, err := db.Addon(id)
  3006  		if err != nil {
  3007  			log.Println(err)
  3008  			res.WriteHeader(http.StatusInternalServerError)
  3009  			errView, err := Error500()
  3010  			if err != nil {
  3011  				return
  3012  			}
  3013  
  3014  			res.Write(errView)
  3015  			return
  3016  		}
  3017  
  3018  		_, ok := addon.Types[id]
  3019  		if !ok {
  3020  			log.Println("Addon: ", id, "is not found in addon.Types map")
  3021  			res.WriteHeader(http.StatusNotFound)
  3022  			errView, err := Error404()
  3023  			if err != nil {
  3024  				return
  3025  			}
  3026  
  3027  			res.Write(errView)
  3028  			return
  3029  		}
  3030  
  3031  		m, err := addon.Manage(data, id)
  3032  		if err != nil {
  3033  			log.Println(err)
  3034  			res.WriteHeader(http.StatusInternalServerError)
  3035  			errView, err := Error500()
  3036  			if err != nil {
  3037  				return
  3038  			}
  3039  
  3040  			res.Write(errView)
  3041  			return
  3042  		}
  3043  
  3044  		addonView, err := Admin(m)
  3045  		if err != nil {
  3046  			log.Println(err)
  3047  			res.WriteHeader(http.StatusInternalServerError)
  3048  			return
  3049  		}
  3050  
  3051  		res.Header().Set("Content-Type", "text/html")
  3052  		res.Write(addonView)
  3053  
  3054  	case http.MethodPost:
  3055  		// save req.Form
  3056  		err := req.ParseMultipartForm(1024 * 1024 * 4) // maxMemory 4MB
  3057  		if err != nil {
  3058  			log.Println(err)
  3059  			res.WriteHeader(http.StatusInternalServerError)
  3060  			errView, err := Error500()
  3061  			if err != nil {
  3062  				return
  3063  			}
  3064  
  3065  			res.Write(errView)
  3066  			return
  3067  		}
  3068  
  3069  		name := req.FormValue("addon_name")
  3070  		id := req.FormValue("addon_reverse_dns")
  3071  
  3072  		at, ok := addon.Types[id]
  3073  		if !ok {
  3074  			log.Println("Error: addon", name, "has no record in addon.Types map at", id)
  3075  			res.WriteHeader(http.StatusBadRequest)
  3076  			errView, err := Error400()
  3077  			if err != nil {
  3078  				return
  3079  			}
  3080  
  3081  			res.Write(errView)
  3082  			return
  3083  		}
  3084  
  3085  		// if Hookable, call BeforeSave prior to saving
  3086  		h, ok := at().(item.Hookable)
  3087  		if ok {
  3088  			err := h.BeforeSave(res, req)
  3089  			if err != nil {
  3090  				log.Println("Error running BeforeSave method in addonHandler for:", id, err)
  3091  				return
  3092  			}
  3093  		}
  3094  
  3095  		err = db.SetAddon(req.Form, at())
  3096  		if err != nil {
  3097  			log.Println("Error saving addon:", name, err)
  3098  			res.WriteHeader(http.StatusInternalServerError)
  3099  			errView, err := Error500()
  3100  			if err != nil {
  3101  				return
  3102  			}
  3103  
  3104  			res.Write(errView)
  3105  			return
  3106  		}
  3107  
  3108  		http.Redirect(res, req, "/admin/addon?id="+id, http.StatusFound)
  3109  
  3110  	default:
  3111  		res.WriteHeader(http.StatusBadRequest)
  3112  		errView, err := Error405()
  3113  		if err != nil {
  3114  			log.Println(err)
  3115  			return
  3116  		}
  3117  
  3118  		res.Write(errView)
  3119  		return
  3120  	}
  3121  }
  3122  
  3123  func adminAddonListItem(data []byte) []byte {
  3124  	id := gjson.GetBytes(data, "addon_reverse_dns").String()
  3125  	status := gjson.GetBytes(data, "addon_status").String()
  3126  	name := gjson.GetBytes(data, "addon_name").String()
  3127  	author := gjson.GetBytes(data, "addon_author").String()
  3128  	authorURL := gjson.GetBytes(data, "addon_author_url").String()
  3129  	version := gjson.GetBytes(data, "addon_version").String()
  3130  
  3131  	var action string
  3132  	var buttonClass string
  3133  	if status != addon.StatusEnabled {
  3134  		action = "Enable"
  3135  		buttonClass = "green"
  3136  	} else {
  3137  		action = "Disable"
  3138  		buttonClass = "red"
  3139  	}
  3140  
  3141  	a := `
  3142  			<li class="col s12">
  3143  				<div class="row">
  3144  					<div class="col s9">
  3145  						<a class="addon-name" href="/admin/addon?id=` + id + `" alt="Configure '` + name + `'">` + name + `</a>
  3146  						<span class="addon-meta addon-author">by: <a href="` + authorURL + `">` + author + `</a></span>
  3147  						<span class="addon-meta addon-version">version: ` + version + `</span>
  3148  					</div>
  3149  
  3150  					<div class="col s3">					
  3151  						<form enctype="multipart/form-data" class="quick-` + strings.ToLower(action) + `-addon __ponzu right" action="/admin/addons" method="post">
  3152  							<button class="btn waves-effect waves-effect-light ` + buttonClass + `">` + action + `</button>
  3153  							<input type="hidden" name="id" value="` + id + `" />
  3154  							<input type="hidden" name="action" value="` + action + `" />
  3155  						</form>
  3156  					</div>
  3157  				</div>
  3158  			</li>`
  3159  
  3160  	return []byte(a)
  3161  }