2019-07-31 13:47:30 -08:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2019-08-05 03:25:24 -08:00
|
|
|
"fmt"
|
2019-07-31 13:47:30 -08:00
|
|
|
"net/http"
|
2019-08-05 03:25:24 -08:00
|
|
|
"strings"
|
2019-07-31 13:47:30 -08:00
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/checklist"
|
2019-08-05 03:25:24 -08:00
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/auth"
|
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/datatable"
|
2019-07-31 13:47:30 -08:00
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web"
|
2019-08-05 03:25:24 -08:00
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
|
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/weberror"
|
2019-08-17 11:03:48 +07:00
|
|
|
|
2019-08-05 03:25:24 -08:00
|
|
|
"github.com/gorilla/schema"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"gopkg.in/DataDog/dd-trace-go.v1/contrib/go-redis/redis"
|
2019-07-31 13:47:30 -08:00
|
|
|
)
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
// Checklists represents the Checklists API method handler set.
|
|
|
|
type Checklists struct {
|
|
|
|
ChecklistRepo *checklist.Repository
|
|
|
|
Redis *redis.Client
|
|
|
|
Renderer web.Renderer
|
2019-07-31 13:47:30 -08:00
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
func urlChecklistsIndex() string {
|
|
|
|
return fmt.Sprintf("/checklists")
|
2019-08-05 03:25:24 -08:00
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
func urlChecklistsCreate() string {
|
|
|
|
return fmt.Sprintf("/checklists/create")
|
2019-08-05 03:25:24 -08:00
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
func urlChecklistsView(checklistID string) string {
|
|
|
|
return fmt.Sprintf("/checklists/%s", checklistID)
|
2019-08-05 03:25:24 -08:00
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
func urlChecklistsUpdate(checklistID string) string {
|
|
|
|
return fmt.Sprintf("/checklists/%s/update", checklistID)
|
2019-08-05 03:25:24 -08:00
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
// Index handles listing all the checklists for the current account.
|
|
|
|
func (h *Checklists) Index(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
2019-08-05 03:25:24 -08:00
|
|
|
|
|
|
|
claims, err := auth.ClaimsFromContext(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
statusOpts := web.NewEnumResponse(ctx, nil, checklist.ChecklistStatus_ValuesInterface()...)
|
2019-08-05 03:25:24 -08:00
|
|
|
|
|
|
|
statusFilterItems := []datatable.FilterOptionItem{}
|
|
|
|
for _, opt := range statusOpts.Options {
|
|
|
|
statusFilterItems = append(statusFilterItems, datatable.FilterOptionItem{
|
|
|
|
Display: opt.Title,
|
|
|
|
Value: opt.Value,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fields := []datatable.DisplayField{
|
|
|
|
datatable.DisplayField{Field: "id", Title: "ID", Visible: false, Searchable: true, Orderable: true, Filterable: false},
|
2020-01-18 15:36:16 -09:00
|
|
|
datatable.DisplayField{Field: "name", Title: "Checklist", Visible: true, Searchable: true, Orderable: true, Filterable: true, FilterPlaceholder: "filter Name"},
|
2019-08-05 03:25:24 -08:00
|
|
|
datatable.DisplayField{Field: "status", Title: "Status", Visible: true, Searchable: true, Orderable: true, Filterable: true, FilterPlaceholder: "All Statuses", FilterItems: statusFilterItems},
|
|
|
|
datatable.DisplayField{Field: "updated_at", Title: "Last Updated", Visible: true, Searchable: true, Orderable: true, Filterable: false},
|
|
|
|
datatable.DisplayField{Field: "created_at", Title: "Created", Visible: true, Searchable: true, Orderable: true, Filterable: false},
|
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
mapFunc := func(q *checklist.Checklist, cols []datatable.DisplayField) (resp []datatable.ColumnValue, err error) {
|
2019-08-05 03:25:24 -08:00
|
|
|
for i := 0; i < len(cols); i++ {
|
|
|
|
col := cols[i]
|
|
|
|
var v datatable.ColumnValue
|
|
|
|
switch col.Field {
|
|
|
|
case "id":
|
2019-08-17 10:58:45 -08:00
|
|
|
v.Value = fmt.Sprintf("%s", q.ID)
|
2019-08-05 03:25:24 -08:00
|
|
|
case "name":
|
|
|
|
v.Value = q.Name
|
2020-01-18 15:36:16 -09:00
|
|
|
v.Formatted = fmt.Sprintf("<a href='%s'>%s</a>", urlChecklistsView(q.ID), v.Value)
|
2019-08-05 03:25:24 -08:00
|
|
|
case "status":
|
|
|
|
v.Value = q.Status.String()
|
|
|
|
|
|
|
|
var subStatusClass string
|
|
|
|
var subStatusIcon string
|
|
|
|
switch q.Status {
|
2020-01-18 15:36:16 -09:00
|
|
|
case checklist.ChecklistStatus_Active:
|
2019-08-05 03:25:24 -08:00
|
|
|
subStatusClass = "text-green"
|
|
|
|
subStatusIcon = "far fa-dot-circle"
|
2020-01-18 15:36:16 -09:00
|
|
|
case checklist.ChecklistStatus_Disabled:
|
2019-08-05 03:25:24 -08:00
|
|
|
subStatusClass = "text-orange"
|
|
|
|
subStatusIcon = "far fa-circle"
|
|
|
|
}
|
|
|
|
|
2019-08-05 18:47:07 -08:00
|
|
|
v.Formatted = fmt.Sprintf("<span class='cell-font-status %s'><i class='%s mr-1'></i>%s</span>", subStatusClass, subStatusIcon, web.EnumValueTitle(v.Value))
|
2019-08-05 03:25:24 -08:00
|
|
|
case "created_at":
|
|
|
|
dt := web.NewTimeResponse(ctx, q.CreatedAt)
|
|
|
|
v.Value = dt.Local
|
|
|
|
v.Formatted = fmt.Sprintf("<span class='cell-font-date'>%s</span>", v.Value)
|
|
|
|
case "updated_at":
|
|
|
|
dt := web.NewTimeResponse(ctx, q.UpdatedAt)
|
|
|
|
v.Value = dt.Local
|
|
|
|
v.Formatted = fmt.Sprintf("<span class='cell-font-date'>%s</span>", v.Value)
|
|
|
|
default:
|
|
|
|
return resp, errors.Errorf("Failed to map value for %s.", col.Field)
|
|
|
|
}
|
|
|
|
resp = append(resp, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
loadFunc := func(ctx context.Context, sorting string, fields []datatable.DisplayField) (resp [][]datatable.ColumnValue, err error) {
|
2020-01-18 15:36:16 -09:00
|
|
|
res, err := h.ChecklistRepo.Find(ctx, claims, checklist.ChecklistFindRequest{
|
2019-08-05 17:12:28 -08:00
|
|
|
Where: "account_id = ?",
|
2019-08-05 03:25:24 -08:00
|
|
|
Args: []interface{}{claims.Audience},
|
|
|
|
Order: strings.Split(sorting, ","),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, a := range res {
|
|
|
|
l, err := mapFunc(a, fields)
|
|
|
|
if err != nil {
|
2020-01-18 15:36:16 -09:00
|
|
|
return resp, errors.Wrapf(err, "Failed to map checklist for display.")
|
2019-08-05 03:25:24 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
resp = append(resp, l)
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
dt, err := datatable.New(ctx, w, r, h.Redis, fields, loadFunc)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if dt.HasCache() {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if ok, err := dt.Render(); ok {
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
data := map[string]interface{}{
|
2020-01-18 15:36:16 -09:00
|
|
|
"datatable": dt.Response(),
|
|
|
|
"urlChecklistsCreate": urlChecklistsCreate(),
|
2019-08-05 03:25:24 -08:00
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "checklists-index.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
|
2019-08-05 03:25:24 -08:00
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
// Create handles creating a new checklist for the account.
|
|
|
|
func (h *Checklists) Create(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
2019-08-05 03:25:24 -08:00
|
|
|
|
|
|
|
ctxValues, err := webcontext.ContextValues(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
claims, err := auth.ClaimsFromContext(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
2020-01-18 15:36:16 -09:00
|
|
|
req := new(checklist.ChecklistCreateRequest)
|
2019-08-05 03:25:24 -08:00
|
|
|
data := make(map[string]interface{})
|
|
|
|
f := func() (bool, error) {
|
|
|
|
if r.Method == http.MethodPost {
|
|
|
|
err := r.ParseForm()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
decoder := schema.NewDecoder()
|
|
|
|
decoder.IgnoreUnknownKeys(true)
|
|
|
|
|
|
|
|
if err := decoder.Decode(req, r.PostForm); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
req.AccountID = claims.Audience
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
usr, err := h.ChecklistRepo.Create(ctx, claims, *req, ctxValues.Now)
|
2019-08-05 03:25:24 -08:00
|
|
|
if err != nil {
|
|
|
|
switch errors.Cause(err) {
|
|
|
|
default:
|
|
|
|
if verr, ok := weberror.NewValidationError(ctx, err); ok {
|
|
|
|
data["validationErrors"] = verr.(*weberror.Error)
|
|
|
|
return false, nil
|
|
|
|
} else {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
// Display a success message to the checklist.
|
2019-08-05 03:25:24 -08:00
|
|
|
webcontext.SessionFlashSuccess(ctx,
|
2020-01-18 15:36:16 -09:00
|
|
|
"Checklist Created",
|
|
|
|
"Checklist successfully created.")
|
2019-08-05 03:25:24 -08:00
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
return true, web.Redirect(ctx, w, r, urlChecklistsView(usr.ID), http.StatusFound)
|
2019-08-05 03:25:24 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
end, err := f()
|
|
|
|
if err != nil {
|
|
|
|
return web.RenderError(ctx, w, r, err, h.Renderer, TmplLayoutBase, TmplContentErrorGeneric, web.MIMETextHTMLCharsetUTF8)
|
|
|
|
} else if end {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
data["form"] = req
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(checklist.ChecklistCreateRequest{})); ok {
|
2019-08-05 03:25:24 -08:00
|
|
|
data["validationDefaults"] = verr.(*weberror.Error)
|
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "checklists-create.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
|
2019-08-05 03:25:24 -08:00
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
// View handles displaying a checklist.
|
|
|
|
func (h *Checklists) View(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
2019-08-05 03:25:24 -08:00
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
checklistID := params["checklist_id"]
|
2019-08-05 03:25:24 -08:00
|
|
|
|
|
|
|
ctxValues, err := webcontext.ContextValues(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
claims, err := auth.ClaimsFromContext(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
data := make(map[string]interface{})
|
|
|
|
f := func() (bool, error) {
|
|
|
|
if r.Method == http.MethodPost {
|
|
|
|
err := r.ParseForm()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch r.PostForm.Get("action") {
|
|
|
|
case "archive":
|
2020-01-18 15:36:16 -09:00
|
|
|
err = h.ChecklistRepo.Archive(ctx, claims, checklist.ChecklistArchiveRequest{
|
|
|
|
ID: checklistID,
|
2019-08-05 03:25:24 -08:00
|
|
|
}, ctxValues.Now)
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
webcontext.SessionFlashSuccess(ctx,
|
2020-01-18 15:36:16 -09:00
|
|
|
"Checklist Archive",
|
|
|
|
"Checklist successfully archive.")
|
2019-08-05 03:25:24 -08:00
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
return true, web.Redirect(ctx, w, r, urlChecklistsIndex(), http.StatusFound)
|
2019-08-05 03:25:24 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
end, err := f()
|
|
|
|
if err != nil {
|
|
|
|
return web.RenderError(ctx, w, r, err, h.Renderer, TmplLayoutBase, TmplContentErrorGeneric, web.MIMETextHTMLCharsetUTF8)
|
|
|
|
} else if end {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
prj, err := h.ChecklistRepo.ReadByID(ctx, claims, checklistID)
|
2019-08-05 03:25:24 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-01-18 15:36:16 -09:00
|
|
|
data["checklist"] = prj.Response(ctx)
|
|
|
|
data["urlChecklistsView"] = urlChecklistsView(checklistID)
|
|
|
|
data["urlChecklistsUpdate"] = urlChecklistsUpdate(checklistID)
|
2019-08-05 03:25:24 -08:00
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "checklists-view.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
|
2019-08-05 03:25:24 -08:00
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
// Update handles updating a checklist for the account.
|
|
|
|
func (h *Checklists) Update(ctx context.Context, w http.ResponseWriter, r *http.Request, params map[string]string) error {
|
2019-08-05 03:25:24 -08:00
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
checklistID := params["checklist_id"]
|
2019-08-05 03:25:24 -08:00
|
|
|
|
|
|
|
ctxValues, err := webcontext.ContextValues(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
claims, err := auth.ClaimsFromContext(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
2020-01-18 15:36:16 -09:00
|
|
|
req := new(checklist.ChecklistUpdateRequest)
|
2019-08-05 03:25:24 -08:00
|
|
|
data := make(map[string]interface{})
|
|
|
|
f := func() (bool, error) {
|
|
|
|
if r.Method == http.MethodPost {
|
|
|
|
err := r.ParseForm()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
decoder := schema.NewDecoder()
|
|
|
|
decoder.IgnoreUnknownKeys(true)
|
|
|
|
|
|
|
|
if err := decoder.Decode(req, r.PostForm); err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
2020-01-18 15:36:16 -09:00
|
|
|
req.ID = checklistID
|
2019-08-05 03:25:24 -08:00
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
err = h.ChecklistRepo.Update(ctx, claims, *req, ctxValues.Now)
|
2019-08-05 03:25:24 -08:00
|
|
|
if err != nil {
|
|
|
|
switch errors.Cause(err) {
|
|
|
|
default:
|
|
|
|
if verr, ok := weberror.NewValidationError(ctx, err); ok {
|
|
|
|
data["validationErrors"] = verr.(*weberror.Error)
|
|
|
|
return false, nil
|
|
|
|
} else {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
// Display a success message to the checklist.
|
2019-08-05 03:25:24 -08:00
|
|
|
webcontext.SessionFlashSuccess(ctx,
|
2020-01-18 15:36:16 -09:00
|
|
|
"Checklist Updated",
|
|
|
|
"Checklist successfully updated.")
|
2019-08-05 03:25:24 -08:00
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
return true, web.Redirect(ctx, w, r, urlChecklistsView(req.ID), http.StatusFound)
|
2019-08-05 03:25:24 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
end, err := f()
|
|
|
|
if err != nil {
|
|
|
|
return web.RenderError(ctx, w, r, err, h.Renderer, TmplLayoutBase, TmplContentErrorGeneric, web.MIMETextHTMLCharsetUTF8)
|
|
|
|
} else if end {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
prj, err := h.ChecklistRepo.ReadByID(ctx, claims, checklistID)
|
2019-08-05 03:25:24 -08:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-01-18 15:36:16 -09:00
|
|
|
data["checklist"] = prj.Response(ctx)
|
2019-08-05 03:25:24 -08:00
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
data["urlChecklistsView"] = urlChecklistsView(checklistID)
|
2019-08-05 17:26:27 -08:00
|
|
|
|
2019-08-05 03:25:24 -08:00
|
|
|
if req.ID == "" {
|
|
|
|
req.Name = &prj.Name
|
|
|
|
req.Status = &prj.Status
|
|
|
|
}
|
|
|
|
data["form"] = req
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
if verr, ok := weberror.NewValidationError(ctx, webcontext.Validator().Struct(checklist.ChecklistUpdateRequest{})); ok {
|
2019-08-05 03:25:24 -08:00
|
|
|
data["validationDefaults"] = verr.(*weberror.Error)
|
|
|
|
}
|
|
|
|
|
2020-01-18 15:36:16 -09:00
|
|
|
return h.Renderer.Render(ctx, w, r, TmplLayoutBase, "checklists-update.gohtml", web.MIMETextHTMLCharsetUTF8, http.StatusOK, data)
|
2019-07-31 13:47:30 -08:00
|
|
|
}
|