Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gateway: add user project favorites api #492

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions internal/services/configstore/action/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,10 @@ func (h *ActionHandler) DeleteProject(ctx context.Context, projectRef string) er
return util.NewAPIError(util.ErrBadRequest, errors.Errorf("project %q doesn't exist", projectRef))
}

if err := h.d.DeleteUserProjectFavoritesByProjectID(tx, project.ID); err != nil {
return util.NewAPIError(util.KindFromRemoteError(err), err)
}

// TODO(sgotti) implement childs garbage collection
if err := h.d.DeleteProject(tx, project.ID); err != nil {
return errors.WithStack(err)
Expand Down
4 changes: 4 additions & 0 deletions internal/services/configstore/action/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,10 @@ func (h *ActionHandler) DeleteUser(ctx context.Context, userRef string) error {
return util.NewAPIError(util.KindFromRemoteError(err), err)
}

if err := h.d.DeleteUserProjectFavoritesByUserID(tx, user.ID); err != nil {
return util.NewAPIError(util.KindFromRemoteError(err), err)
}

if err := h.d.DeleteUser(tx, user.ID); err != nil {
return errors.WithStack(err)
}
Expand Down
161 changes: 161 additions & 0 deletions internal/services/configstore/action/userprojectfavorite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package action

import (
"context"

"github.com/sorintlab/errors"

"agola.io/agola/internal/sqlg/sql"
"agola.io/agola/internal/util"
"agola.io/agola/services/configstore/types"
)

type CreateUserProjectFavoriteRequest struct {
UserRef string
ProjectRef string
}

func (h *ActionHandler) CreateUserProjectFavorite(ctx context.Context, req *CreateUserProjectFavoriteRequest) (*types.UserProjectFavorite, error) {
var userProjectFavorite *types.UserProjectFavorite
err := h.d.Do(ctx, func(tx *sql.Tx) error {
var err error
user, err := h.d.GetUser(tx, req.UserRef)
if err != nil {
return errors.WithStack(err)
}
if user == nil {
return util.NewAPIError(util.ErrBadRequest, errors.Errorf("user with ref %q doesn't exist", req.UserRef))
}

project, err := h.d.GetProject(tx, req.ProjectRef)
if err != nil {
return errors.WithStack(err)
}
if project == nil {
return util.NewAPIError(util.ErrBadRequest, errors.Errorf("project with ref %q doesn't exist", req.ProjectRef))
}

// check duplicate user project favorite
userProjectFavorite, err = h.d.GetUserProjectFavorite(tx, user.ID, project.ID)
if err != nil {
return errors.WithStack(err)
}
if userProjectFavorite != nil {
return util.NewAPIError(util.ErrBadRequest, errors.Errorf("user project favorite with user ref %q, project ref %q already exists", req.UserRef, req.ProjectRef))
}

userProjectFavorite = types.NewUserProjectFavorite(tx)
userProjectFavorite.UserID = user.ID
userProjectFavorite.ProjectID = project.ID

if err := h.d.InsertUserProjectFavorite(tx, userProjectFavorite); err != nil {
return errors.WithStack(err)
}

return nil
})
if err != nil {
return nil, errors.WithStack(err)
}

return userProjectFavorite, errors.WithStack(err)
}

func (h *ActionHandler) DeleteUserProjectFavorite(ctx context.Context, userRef, projectRef string) error {
err := h.d.Do(ctx, func(tx *sql.Tx) error {
user, err := h.d.GetUser(tx, userRef)
if err != nil {
return errors.WithStack(err)
}
if user == nil {
return util.NewAPIError(util.ErrBadRequest, errors.Errorf("user with ref %q doesn't exist", userRef))
}

project, err := h.d.GetProject(tx, projectRef)
if err != nil {
return errors.WithStack(err)
}
if project == nil {
return util.NewAPIError(util.ErrBadRequest, errors.Errorf("project with ref %q doesn't exist", projectRef))
}

// check project favorite existance
userProjectFavorite, err := h.d.GetUserProjectFavorite(tx, user.ID, project.ID)
if err != nil {
return errors.WithStack(err)
}
if userProjectFavorite == nil {
return util.NewAPIError(util.ErrBadRequest, errors.Errorf("user project favorite for user %q, project %q doesn't exist", userRef, projectRef))
}

if err := h.d.DeleteUserProjectFavorite(tx, userProjectFavorite.ID); err != nil {
return errors.WithStack(err)
}

return nil
})
if err != nil {
return errors.WithStack(err)
}

return errors.WithStack(err)
}

type GetUserProjectFavoritesRequest struct {
UserRef string

StartUserProjectFavoriteID string

Limit int
SortDirection types.SortDirection
}

type GetUserProjectFavoritesResponse struct {
UserProjectFavorites []*types.UserProjectFavorite

HasMore bool
}

func (h *ActionHandler) GetUserProjectFavorites(ctx context.Context, req *GetUserProjectFavoritesRequest) (*GetUserProjectFavoritesResponse, error) {
limit := req.Limit
if limit > 0 {
limit += 1
}
if req.SortDirection == "" {
req.SortDirection = types.SortDirectionAsc
}

var userProjectFavorites []*types.UserProjectFavorite
err := h.d.Do(ctx, func(tx *sql.Tx) error {
user, err := h.d.GetUser(tx, req.UserRef)
if err != nil {
return errors.WithStack(err)
}
if user == nil {
return util.NewAPIError(util.ErrBadRequest, errors.Errorf("user with ref %q doesn't exist", req.UserRef))
}

userProjectFavorites, err = h.d.GetUserProjectFavoritesByUserID(tx, user.ID, req.StartUserProjectFavoriteID, limit, req.SortDirection)
if err != nil {
return errors.WithStack(err)
}

return nil
})
if err != nil {
return nil, errors.WithStack(err)
}

var hasMore bool
if req.Limit > 0 {
hasMore = len(userProjectFavorites) > req.Limit
if hasMore {
userProjectFavorites = userProjectFavorites[0:req.Limit]
}
}

return &GetUserProjectFavoritesResponse{
UserProjectFavorites: userProjectFavorites,
HasMore: hasMore,
}, nil
}
151 changes: 151 additions & 0 deletions internal/services/configstore/api/projectfavorite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// Copyright 2024 Sorint.lab
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied
// See the License for the specific language governing permissions and
// limitations under the License.

package api

import (
"net/http"
"net/url"

"github.com/gorilla/mux"
"github.com/rs/zerolog"
"github.com/sorintlab/errors"

"agola.io/agola/internal/services/configstore/action"
"agola.io/agola/internal/util"
"agola.io/agola/services/configstore/types"
)

type CreateUserProjectFavoriteHandler struct {
log zerolog.Logger
ah *action.ActionHandler
}

func NewCreateUserProjectFavoriteHandler(log zerolog.Logger, ah *action.ActionHandler) *CreateUserProjectFavoriteHandler {
return &CreateUserProjectFavoriteHandler{log: log, ah: ah}
}

func (h *CreateUserProjectFavoriteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)

userRef, err := url.PathUnescape(vars["userref"])
if err != nil {
util.HTTPError(w, util.NewAPIError(util.ErrBadRequest, err))
return
}

projectRef, err := url.PathUnescape(vars["projectref"])
if err != nil {
util.HTTPError(w, util.NewAPIError(util.ErrBadRequest, err))
return
}

areq := &action.CreateUserProjectFavoriteRequest{
UserRef: userRef,
ProjectRef: projectRef,
}

userProjectFavorite, err := h.ah.CreateUserProjectFavorite(ctx, areq)
if util.HTTPError(w, err) {
h.log.Err(err).Send()
return
}

if err := util.HTTPResponse(w, http.StatusCreated, userProjectFavorite); err != nil {
h.log.Err(err).Send()
}
}

type DeleteUserProjectFavoriteHandler struct {
log zerolog.Logger
ah *action.ActionHandler
}

func NewDeleteUserProjectFavoriteHandler(log zerolog.Logger, ah *action.ActionHandler) *DeleteUserProjectFavoriteHandler {
return &DeleteUserProjectFavoriteHandler{log: log, ah: ah}
}

func (h *DeleteUserProjectFavoriteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
vars := mux.Vars(r)

userRef, err := url.PathUnescape(vars["userref"])
if err != nil {
util.HTTPError(w, util.NewAPIError(util.ErrBadRequest, err))
return
}

projectRef, err := url.PathUnescape(vars["projectref"])
if err != nil {
util.HTTPError(w, util.NewAPIError(util.ErrBadRequest, err))
return
}

err = h.ah.DeleteUserProjectFavorite(ctx, userRef, projectRef)
if util.HTTPError(w, err) {
h.log.Err(err).Send()
}
if err := util.HTTPResponse(w, http.StatusNoContent, nil); err != nil {
h.log.Err(err).Send()
}
}

type UserProjectFavoritesHandler struct {
log zerolog.Logger
ah *action.ActionHandler
}

func NewUserProjectFavoritesHandler(log zerolog.Logger, ah *action.ActionHandler) *UserProjectFavoritesHandler {
return &UserProjectFavoritesHandler{log: log, ah: ah}
}

func (h *UserProjectFavoritesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
res, err := h.do(w, r)
if util.HTTPError(w, err) {
h.log.Err(err).Send()
return
}

if err := util.HTTPResponse(w, http.StatusOK, res); err != nil {
h.log.Err(err).Send()
}
}

func (h *UserProjectFavoritesHandler) do(w http.ResponseWriter, r *http.Request) ([]*types.UserProjectFavorite, error) {
ctx := r.Context()
vars := mux.Vars(r)
query := r.URL.Query()

ropts, err := parseRequestOptions(r)
if err != nil {
return nil, errors.WithStack(err)
}

userRef, err := url.PathUnescape(vars["userref"])
if err != nil {
return nil, util.NewAPIError(util.ErrBadRequest, err)
}

startUserProjectFavoriteID := query.Get("startuserprojectfavoriteid")

ares, err := h.ah.GetUserProjectFavorites(ctx, &action.GetUserProjectFavoritesRequest{UserRef: userRef, StartUserProjectFavoriteID: startUserProjectFavoriteID, Limit: ropts.Limit, SortDirection: ropts.SortDirection})
if err != nil {
return nil, errors.WithStack(err)
}

addHasMoreHeader(w, ares.HasMore)

return ares.UserProjectFavorites, nil
}
8 changes: 8 additions & 0 deletions internal/services/configstore/configstore.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,10 @@ func (s *Configstore) setupDefaultRouter() http.Handler {
deleteOrgInvitationHandler := api.NewDeleteOrgInvitationHandler(s.log, s.ah)
orgInvitationHandler := api.NewOrgInvitationHandler(s.log, s.ah)

userProjectFavorites := api.NewUserProjectFavoritesHandler(s.log, s.ah)
createUserProjectFavoriteHandler := api.NewCreateUserProjectFavoriteHandler(s.log, s.ah)
deleteUserProjectFavoriteHandler := api.NewDeleteUserProjectFavoriteHandler(s.log, s.ah)

authHandler := handlers.NewInternalAuthChecker(s.log, s.c.APIToken)

router := mux.NewRouter()
Expand Down Expand Up @@ -283,6 +287,10 @@ func (s *Configstore) setupDefaultRouter() http.Handler {

apirouter.Handle("/linkedaccounts", linkedAccountsHandler).Methods("GET")

apirouter.Handle("/users/{userref}/projectfavorites", userProjectFavorites).Methods("GET")
apirouter.Handle("/users/{userref}/projects/{projectref}/projectfavorites", createUserProjectFavoriteHandler).Methods("POST")
apirouter.Handle("/users/{userref}/projects/{projectref}/projectfavorites", deleteUserProjectFavoriteHandler).Methods("DELETE")

apirouter.Handle("/maintenance", maintenanceStatusHandler).Methods("GET")
apirouter.Handle("/maintenance", maintenanceModeHandler).Methods("PUT", "DELETE")

Expand Down
Loading