func DBLog(db ab.DB, ec *ab.EntityController, logtype, message string) error { l := &Log{ Type: logtype, Message: message, Created: time.Now(), } return ec.Insert(db, l) }
func screeningService(ec *ab.EntityController) ab.Service { res := ab.EntityResource(ec, &Screening{}, ab.EntityResourceConfig{ DisablePost: true, DisableList: true, DisableGet: true, DisablePut: true, DisableDelete: true, }) res.ExtraEndpoints = func(srv *ab.Server) error { srv.Post("/api/walkthrough/:id/screening", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { wid := ab.GetParams(r).ByName("id") data := []string{} ab.MustDecode(r, &data) db := ab.GetDB(r) uid := ab.GetSession(r)["uid"] userEntity, err := ec.Load(db, "user", uid) ab.MaybeFail(http.StatusInternalServerError, err) user := userEntity.(*User) wt, err := LoadActualRevision(db, ec, wid) ab.MaybeFail(http.StatusBadRequest, err) if wt.UID != uid && !user.Admin { ab.Fail(http.StatusForbidden, nil) } if len(data) == 0 || len(data) != len(wt.Steps)-1 { ab.Fail(http.StatusBadRequest, fmt.Errorf("got %d images, expected: %d", len(data), len(wt.Steps)-1)) } screening := &Screening{ WID: wid, UID: uid, Steps: uint(len(wt.Steps) - 1), Created: time.Now(), Published: true, } err = ec.Insert(db, screening) ab.MaybeFail(http.StatusInternalServerError, err) images := map[string][]byte{} for i, d := range data { if d == "" { continue } dataurl, err := dataurl.DecodeString(d) if err != nil { ab.LogTrace(r).Printf("data url error: %s", dataurl) ab.Fail(http.StatusBadRequest, err) } if dataurl.ContentType() != "image/png" { ab.Fail(http.StatusBadRequest, errors.New("not a png")) } fn := screening.ScreenshotPath(uint(i)) images[fn] = dataurl.Data } if len(images) == 0 { ab.Fail(http.StatusBadRequest, errors.New("no images sent")) } for name, content := range images { if err := ioutil.WriteFile(name, content, 0644); err != nil { ab.LogUser(r).Println(err) } } ab.Render(r). SetCode(http.StatusCreated). JSON(screening) }), userLoggedInMiddleware) lock := map[string]chan struct{}{} var mtx sync.Mutex srv.Get("/api/walkthrough/:id/screening", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { wid := ab.GetParams(r).ByName("id") db := ab.GetDB(r) screening, err := LoadActualScreeningForWalkthrough(db, ec, wid) ab.MaybeFail(http.StatusInternalServerError, err) if screening == nil { ab.Fail(http.StatusNotFound, nil) } fn := screening.GIFPath() reply := func() { filelist := make([]string, int(screening.Steps)) for i := uint(0); i < screening.Steps; i++ { filelist[i] = "/" + screening.ScreenshotPath(i) } ab.Render(r).AddOffer("image/gif", func(w http.ResponseWriter) { f, err := os.Open(fn) ab.MaybeFail(http.StatusInternalServerError, err) defer f.Close() io.Copy(w, f) }).JSON(filelist) } if _, err := os.Stat(fn); err == nil { reply() return } // Short-circuits the gif generation process. // If the client wants JSON, there is no need // to go through the GIF generation, which is // an expensive process. if r.Header.Get("Accept") == "application/json" { reply() return } mtx.Lock() l, ok := lock[fn] if ok { mtx.Unlock() select { case <-l: reply() case <-time.After(5 * time.Second): w.Header().Set("Retry-After", "30") ab.Render(r).SetCode(http.StatusServiceUnavailable) } return } l = make(chan struct{}) lock[fn] = l mtx.Unlock() err = screening.createGIF(false) defer func() { mtx.Lock() delete(lock, fn) mtx.Unlock() }() if err != nil { if _, ok := err.(*os.PathError); ok { ab.Fail(http.StatusNotFound, err) } else { ab.Fail(http.StatusInternalServerError, err) } } close(l) reply() })) return nil } return res }