blob: 10390556d670e055ba33b5cfd4887356ed3467f0 [file] [log] [blame]
package api
import (
"context"
"encoding/json"
"net/http"
"time"
"github.com/go-chi/chi/v5"
"go.skia.org/infra/go/alogin"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/perf/go/config"
"go.skia.org/infra/perf/go/favorites"
)
const (
defaultDatabaseTimeout = time.Minute
)
// favoritesApi provides a struct for handling Favorites feature.
type favoritesApi struct {
loginProvider alogin.Login
favStore favorites.Store
}
// NewFavoritesApi returns a new instance of favoritesApi.
func NewFavoritesApi(loginProvider alogin.Login, favoritesStore favorites.Store) favoritesApi {
return favoritesApi{
loginProvider: loginProvider,
favStore: favoritesStore,
}
}
// RegisterHandlers registers the api handlers for their respective routes.
func (f favoritesApi) RegisterHandlers(router *chi.Mux) {
router.Get("/_/favorites", f.favoritesHandler)
router.Post("/_/favorites/new", f.newFavoriteHandler)
router.Post("/_/favorites/delete", f.deleteFavoriteHandler)
router.Post("/_/favorites/edit", f.updateFavoriteHandler)
}
// favoritesHandler returns the favorites config for the instance. If a user is
// logged in this also returns the favorites specific to the logged in user.
func (f favoritesApi) favoritesHandler(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), defaultDatabaseTimeout)
defer cancel()
w.Header().Set("Content-Type", "application/json")
fav := config.Favorites{
Sections: []config.FavoritesSectionConfig{},
}
if config.Config.Favorites.Sections != nil {
fav.Sections = config.Config.Favorites.Sections
}
loggedInEmail := f.loginProvider.LoggedInAs(r)
if loggedInEmail != "" {
favsFromDb, err := f.favStore.List(ctx, loggedInEmail.String())
if err != nil {
httputils.ReportError(w, err, "Failed to list favorite.", http.StatusInternalServerError)
return
}
favoriteList := []config.FavoritesSectionLinkConfig{}
for _, favorite := range favsFromDb {
favoriteList = append(favoriteList, config.FavoritesSectionLinkConfig{
Id: favorite.ID,
Text: favorite.Name,
Href: favorite.Url,
Description: favorite.Description,
})
}
if len(favsFromDb) > 0 {
fav.Sections = append(fav.Sections, config.FavoritesSectionConfig{
Name: "My Favorites",
Links: favoriteList,
})
}
}
if err := json.NewEncoder(w).Encode(fav); err != nil {
sklog.Errorf("Error writing the Favorites json to response: %s", err)
}
}
// CreateFavRequest is the request to create a new Favorite
type CreateFavRequest struct {
Name string `json:"name"`
Description string `json:"description"`
Url string `json:"url"`
}
// newFavoriteHandler creates a new favorite in the db
func (f *favoritesApi) newFavoriteHandler(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), defaultDatabaseTimeout)
defer cancel()
w.Header().Set("Content-Type", "application/json")
loggedInEmail := f.loginProvider.LoggedInAs(r)
if loggedInEmail == "" {
httputils.ReportError(w, skerr.Fmt("Favorite Error:"), "Login Required", http.StatusUnauthorized)
return
}
var createReq CreateFavRequest
if err := json.NewDecoder(r.Body).Decode(&createReq); err != nil {
httputils.ReportError(w, err, "Failed to decode JSON.", http.StatusInternalServerError)
return
}
saveReq := favorites.SaveRequest{
UserId: loggedInEmail.String(),
Name: createReq.Name,
Description: createReq.Description,
Url: createReq.Url,
}
err := f.favStore.Create(ctx, &saveReq)
if err != nil {
httputils.ReportError(w, err, "Failed to create favorite.", http.StatusInternalServerError)
}
}
// DeleteFavRequest is a request to delete an existing Favorite
type DeleteFavRequest struct {
Id string `json:"id"`
}
// deleteFavoriteHandler deletes a favorite per id in the db
func (f *favoritesApi) deleteFavoriteHandler(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), defaultDatabaseTimeout)
defer cancel()
w.Header().Set("Content-Type", "application/json")
loggedInEmail := f.loginProvider.LoggedInAs(r)
if loggedInEmail == "" {
httputils.ReportError(w, skerr.Fmt("Favorite Error:"), "Login Required", http.StatusUnauthorized)
return
}
var deleteReq DeleteFavRequest
if err := json.NewDecoder(r.Body).Decode(&deleteReq); err != nil {
httputils.ReportError(w, err, "Failed to decode JSON.", http.StatusInternalServerError)
return
}
err := f.favStore.Delete(ctx, loggedInEmail.String(), deleteReq.Id)
if err != nil {
httputils.ReportError(w, skerr.Fmt("Favorite Error:"), "Failed to delete favorite.", http.StatusInternalServerError)
}
}
// UpdateFavRequest is a request to update an existing Favorite
type UpdateFavRequest struct {
Id string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Url string `json:"url"`
}
// updateFavoriteHandler updates a new favorite per id in the db
func (f *favoritesApi) updateFavoriteHandler(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), defaultDatabaseTimeout)
defer cancel()
w.Header().Set("Content-Type", "application/json")
loggedInEmail := f.loginProvider.LoggedInAs(r)
if loggedInEmail == "" {
httputils.ReportError(w, skerr.Fmt("Favorite Error:"), "Login Required", http.StatusUnauthorized)
return
}
var updateReq UpdateFavRequest
if err := json.NewDecoder(r.Body).Decode(&updateReq); err != nil {
httputils.ReportError(w, err, "Failed to decode JSON.", http.StatusInternalServerError)
return
}
fav, err := f.favStore.Get(ctx, updateReq.Id)
if err != nil {
httputils.ReportError(w, skerr.Fmt("Favorite Error:"), "Invalid id", http.StatusNotFound)
return
}
if fav.UserId != loggedInEmail.String() {
httputils.ReportError(w, skerr.Fmt("Favorite Error:"), "Unauthorized action", http.StatusForbidden)
return
}
saveReq := favorites.SaveRequest{
UserId: loggedInEmail.String(),
Name: updateReq.Name,
Description: updateReq.Description,
Url: updateReq.Url,
}
err = f.favStore.Update(ctx, &saveReq, fav.ID)
if err != nil {
httputils.ReportError(w, skerr.Fmt("Favorite Error:"), "Failed to update favorite.", http.StatusInternalServerError)
}
}