func (sis *SiteinfoService) Register(srv *ab.Server) error { clientjs := make([]string, len(sis.BaseURLs)) for i, baseurl := range sis.BaseURLs { clientjs[i] = getClientJS(baseurl) } srv.Post("/api/siteinfo", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { d := siteInfoRequest{} ab.MustDecode(r, &d) db := ab.GetDB(r) info, err := sis.getFromDB(db, d.Url) ab.MaybeFail(http.StatusInternalServerError, err) if info.Empty() { info, err = sis.fetchAndSaveSiteinfo(db, d.Url, clientjs) if err != nil { if err == timeoutError { ab.Fail(http.StatusServiceUnavailable, err) } else { ab.Fail(http.StatusBadGateway, err) } } if info.Empty() { info, err = sis.getFromDB(db, d.Url) ab.MaybeFail(http.StatusInternalServerError, err) } } ab.Render(r).JSON(info) })) return nil }
func logService(ec *ab.EntityController, baseurl string) ab.Service { res := ab.EntityResource(ec, &Log{}, ab.EntityResourceConfig{ DisableList: true, DisableGet: true, DisablePost: true, DisablePut: true, DisableDelete: true, }) res.ExtraEndpoints = func(srv *ab.Server) error { walkthroughPlayed := prometheus.NewCounterFrom(stdprometheus.CounterOpts{ Namespace: "walkhub", Subsystem: "metrics", Name: "walkthrough_played", Help: "Number of walkthrough plays", }, []string{"uuid", "embedorigin"}) walkthroughVisited := prometheus.NewCounterFrom(stdprometheus.CounterOpts{ Namespace: "walkhub", Subsystem: "metrics", Name: "walkthrough_visited", Help: "Number of walkthrough visits", }, []string{"uuid", "embedorigin"}) srv.Post("/api/log/helpcenteropened", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { l := helpCenterOpenedLog{} ab.MustDecode(r, &l) db := ab.GetDB(r) userid := getLogUserID(r, ec) message := fmt.Sprintf("%s has opened the help center on %s", userid, l.URL) ab.MaybeFail(http.StatusInternalServerError, DBLog(db, ec, "helpcenteropened", message)) })) srv.Post("/api/log/walkthroughplayed", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { l := walkthroughPlayedLog{} ab.MustDecode(r, &l) db := ab.GetDB(r) userid := getLogUserID(r, ec) wt, err := LoadActualRevision(db, ec, l.UUID) ab.MaybeFail(http.StatusBadRequest, err) if wt == nil { ab.Fail(http.StatusNotFound, nil) } message := "" embedPart := "" if l.EmbedOrigin != "" { embedPart = "from the help center on " + l.EmbedOrigin + " " } wturl := baseurl + "walkthrough/" + wt.UUID if l.ErrorMessage == "" { message = fmt.Sprintf("%s has played the walkthrough %s<%s|%s>", userid, embedPart, wturl, wt.Name) } else { message = fmt.Sprintf("%s has failed to play the walkthrough %s<%s|%s> with the error message %s", userid, embedPart, wturl, wt.Name, l.ErrorMessage) } ab.MaybeFail(http.StatusInternalServerError, DBLog(db, ec, "walkthroughplayed", message)) walkthroughPlayed. With("uuid", l.UUID). With("embedorigin", l.EmbedOrigin). Add(1) })) srv.Post("/api/log/walkthroughpagevisited", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { l := walkthroughPageVisitedLog{} ab.MustDecode(r, &l) db := ab.GetDB(r) userid := getLogUserID(r, ec) wt, err := LoadActualRevision(db, ec, l.UUID) ab.MaybeFail(http.StatusBadRequest, err) if wt == nil { ab.Fail(http.StatusNotFound, nil) } embedPart := "" if l.EmbedOrigin != "" { embedPart = "embedded on " + l.EmbedOrigin + " " } wturl := baseurl + "walkthrough/" + wt.UUID message := fmt.Sprintf("%s has visited the walkthrough page %s<%s|%s>", userid, embedPart, wturl, wt.Name) ab.MaybeFail(http.StatusInternalServerError, DBLog(db, ec, "walkthroughvisited", message)) walkthroughVisited. With("uuid", l.UUID). With("embedorigin", l.EmbedOrigin). Add(1) })) return nil } return res }
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 }