Compare commits

...

6 Commits

Author SHA1 Message Date
Alessandro Pinna 9804b5b34e
Merge 3cc0b10097 into e697e9163e 2024-07-24 23:14:20 +08:00
Simone Gotti e697e9163e
Merge pull request #526 from sgotti/release_v0.10.0
Release v0.10.0
2024-06-24 10:44:52 +02:00
Simone Gotti 86579b03cf Release v0.10.0 2024-06-19 10:49:40 +02:00
alessandro.pinna 3cc0b10097 gateway: add user project favorites api
add api to create, get, delete user project favorites.
2024-03-11 11:46:34 +01:00
alessandro.pinna 6c9bac15ad configstore: add user project favorites api
add api to create, get, delete user project favorites.
2024-03-11 11:24:55 +01:00
alessandro.pinna b78c9fbb1b configstore: store user project favorite
add UserProjectFavorite type
2024-03-11 11:22:17 +01:00
30 changed files with 2529 additions and 3 deletions

View File

@ -115,7 +115,7 @@ local task_build_push_images(name, target, push) =
|||,
},
]) + [
{ type: 'run', command: '/kaniko/executor --context=dir:///kaniko/agola --build-arg AGOLAWEB_IMAGE=sorintlab/agola-web:v0.9.0 --target %s %s' % [target, options] },
{ type: 'run', command: '/kaniko/executor --context=dir:///kaniko/agola --build-arg AGOLAWEB_IMAGE=sorintlab/agola-web:v0.10.0 --target %s %s' % [target, options] },
],
depends: ['checkout code and save to workspace', 'integration tests', 'test docker driver'],
};

View File

@ -1,5 +1,30 @@
## Changelog
### v0.10.0
- gateway: unify/improve handling of authenticated user (@sgotti) [#525](https://github.com/agola-io/agola/pull/525)
- gateway: move remaining get of current user from api to action (@sgotti) [#524](https://github.com/agola-io/agola/pull/524)
- gateway: fix commit status and run webhook deliveries api (@alessandro-sorint) [#521](https://github.com/agola-io/agola/pull/521)
- tests: fix wrong ProjectCommitStatusRedelivery test (@alessandro-sorint) [#522](https://github.com/agola-io/agola/pull/522)
- cmd/export: close file descriptor (@testwill) [#516](https://github.com/agola-io/agola/pull/516)
- objectstorage: multiple updates and changes (@sgotti) [#519](https://github.com/agola-io/agola/pull/519)
- runservice: fix objectsCleaner lock key usage (@sgotti) [#520](https://github.com/agola-io/agola/pull/520)
- util: use generic Ptr function (@sgotti) [#518](https://github.com/agola-io/agola/pull/518)
- *: update dependencies (@sgotti) [#517](https://github.com/agola-io/agola/pull/517)
- api: add some missing http content types headers. (@sgotti) [#514](https://github.com/agola-io/agola/pull/514)
- config: add docker registries auth nil check (@alessandro-sorint) [#513](https://github.com/agola-io/agola/pull/513)
- configstore: move logic from db to action handler (@sgotti) [#511](https://github.com/agola-io/agola/pull/511)
- *: update dependencies (@sgotti) [#512](https://github.com/agola-io/agola/pull/512)
- *: add detailed errors to api responses (@sgotti) [#507](https://github.com/agola-io/agola/pull/507)
- *: use do method in http handlers (@sgotti) [#508](https://github.com/agola-io/agola/pull/508)
- gateway: add action.APIErrorFromRemoteError (@sgotti) [#506](https://github.com/agola-io/agola/pull/506)
- errors: split wrapped error from APIError message (@sgotti) [#505](https://github.com/agola-io/agola/pull/505)
- configstore: move last bits of logic from api to action (@sgotti) [#504](https://github.com/agola-io/agola/pull/504)
- configstore: fix wrong error handling (@sgotti) [#503](https://github.com/agola-io/agola/pull/503)
- validation: add min/max length check to name validation (@sgotti) [#499](https://github.com/agola-io/agola/pull/499)
- *: update to go 1.22 (@sgotti) [#498](https://github.com/agola-io/agola/pull/498)
- sqlg updates (@sgotti) [#495](https://github.com/agola-io/agola/pull/495)
### v0.9.0
- agolademo: use docker compose (@sgotti) [#494](https://github.com/agola-io/agola/pull/494)

View File

@ -385,6 +385,10 @@ func (h *ActionHandler) DeleteProject(ctx context.Context, projectRef string) er
return util.NewAPIError(util.ErrNotExist, util.WithAPIErrorMsg("project %q doesn't exist", projectRef), serrors.ProjectDoesNotExist())
}
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)

View File

@ -289,6 +289,10 @@ func (h *ActionHandler) DeleteUser(ctx context.Context, userRef string) error {
return errors.WithStack(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)
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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()
@ -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")

View File

@ -152,6 +152,21 @@ func getVariables(ctx context.Context, cs *Configstore) ([]*types.Variable, erro
return variables, errors.WithStack(err)
}
func getUserProjectFavorites(ctx context.Context, cs *Configstore) ([]*types.UserProjectFavorite, error) {
var userProjectFavorites []*types.UserProjectFavorite
err := cs.d.Do(ctx, func(tx *sql.Tx) error {
var err error
userProjectFavorites, err = cs.d.GetUserProjectFavorites(tx)
if err != nil {
return errors.WithStack(err)
}
return nil
})
return userProjectFavorites, errors.WithStack(err)
}
func cmpDiffObject(x, y interface{}) cmp.Comparison {
// Since postgres has microsecond time precision while go has nanosecond time precision we should check times with a microsecond margin
return cmp.DeepEqual(x, y, cmpopts.IgnoreFields(sqlg.ObjectMeta{}, "TxID"), cmpopts.EquateApproxTime(1*time.Microsecond))
@ -1955,3 +1970,241 @@ func TestOrgInvitation(t *testing.T) {
})
}
}
func TestUserProjectFavorite(t *testing.T) {
t.Parallel()
log := testutil.NewLogger(t)
setupUsers := func(t *testing.T, ctx context.Context, cs *Configstore, limit int) {
for i := 1; i <= limit; i++ {
_, err := cs.ah.CreateUser(ctx, &action.CreateUserRequest{UserName: fmt.Sprintf("user%d", i)})
testutil.NilError(t, err)
}
}
setupOrg := func(t *testing.T, ctx context.Context, cs *Configstore, creatorUserID string) {
_, err := cs.ah.CreateOrg(ctx, &action.CreateOrgRequest{Name: "org01", Visibility: types.VisibilityPublic, CreatorUserID: creatorUserID})
testutil.NilError(t, err)
}
setupProjects := func(t *testing.T, ctx context.Context, cs *Configstore, limit int) {
for i := 1; i <= limit; i++ {
_, err := cs.ah.CreateProject(ctx, &action.CreateUpdateProjectRequest{Name: fmt.Sprintf("project%d", i), Parent: types.Parent{Kind: types.ObjectKindProjectGroup, ID: path.Join("org", "org01")}, Visibility: types.VisibilityPublic, RemoteRepositoryConfigType: types.RemoteRepositoryConfigTypeManual})
testutil.NilError(t, err)
}
}
tests := []struct {
name string
f func(ctx context.Context, t *testing.T, cs *Configstore)
}{
{
name: "test create user project favorites",
f: func(ctx context.Context, t *testing.T, cs *Configstore) {
setupUsers(t, ctx, cs, 4)
users, err := getUsers(ctx, cs)
testutil.NilError(t, err)
userOwner := users[0]
setupOrg(t, ctx, cs, userOwner.ID)
setupProjects(t, ctx, cs, 4)
projects, err := getProjects(ctx, cs)
testutil.NilError(t, err)
var expectedUserProjectFavorites []*types.UserProjectFavorite
for i := 0; i < len(users); i++ {
userProjectFavorite, err := cs.ah.CreateUserProjectFavorite(ctx, &action.CreateUserProjectFavoriteRequest{UserRef: fmt.Sprintf("user%d", i+1), ProjectRef: projects[i].ID})
testutil.NilError(t, err)
expectedUserProjectFavorites = append(expectedUserProjectFavorites, userProjectFavorite)
}
userProjectFavorites, err := getUserProjectFavorites(ctx, cs)
testutil.NilError(t, err)
assert.Assert(t, cmpDiffObject(expectedUserProjectFavorites, userProjectFavorites))
},
},
{
name: "test user project favorite creation with already existing user project favorite",
f: func(ctx context.Context, t *testing.T, cs *Configstore) {
setupUsers(t, ctx, cs, 1)
users, err := getUsers(ctx, cs)
testutil.NilError(t, err)
user := users[0]
setupOrg(t, ctx, cs, user.ID)
setupProjects(t, ctx, cs, 1)
projects, err := getProjects(ctx, cs)
testutil.NilError(t, err)
_, err = cs.ah.CreateUserProjectFavorite(ctx, &action.CreateUserProjectFavoriteRequest{UserRef: "user1", ProjectRef: projects[0].ID})
testutil.NilError(t, err)
expectedErr := util.NewAPIError(util.ErrBadRequest, errors.Errorf("user project favorite with user ref %q, project ref %q already exists", "user1", projects[0].ID))
_, err = cs.ah.CreateUserProjectFavorite(ctx, &action.CreateUserProjectFavoriteRequest{UserRef: "user1", ProjectRef: projects[0].ID})
assert.Error(t, err, expectedErr.Error())
},
},
{
name: "test user project favorite creation with not existing user",
f: func(ctx context.Context, t *testing.T, cs *Configstore) {
setupUsers(t, ctx, cs, 1)
users, err := getUsers(ctx, cs)
testutil.NilError(t, err)
userOwner := users[0]
setupOrg(t, ctx, cs, userOwner.ID)
setupProjects(t, ctx, cs, 1)
projects, err := getProjects(ctx, cs)
testutil.NilError(t, err)
expectedErr := util.NewAPIError(util.ErrBadRequest, errors.Errorf("user with ref %q doesn't exist", "usertest"))
_, err = cs.ah.CreateUserProjectFavorite(ctx, &action.CreateUserProjectFavoriteRequest{UserRef: "usertest", ProjectRef: projects[0].ID})
assert.Error(t, err, expectedErr.Error())
},
},
{
name: "test user project favorite creation with not existing project",
f: func(ctx context.Context, t *testing.T, cs *Configstore) {
setupUsers(t, ctx, cs, 1)
users, err := getUsers(ctx, cs)
testutil.NilError(t, err)
userOwner := users[0]
setupOrg(t, ctx, cs, userOwner.ID)
setupProjects(t, ctx, cs, 1)
expectedErr := util.NewAPIError(util.ErrBadRequest, errors.Errorf("project with ref %q doesn't exist", "projecttest"))
_, err = cs.ah.CreateUserProjectFavorite(ctx, &action.CreateUserProjectFavoriteRequest{UserRef: users[0].ID, ProjectRef: "projecttest"})
assert.Error(t, err, expectedErr.Error())
},
},
{
name: "test user project favorite deletion",
f: func(ctx context.Context, t *testing.T, cs *Configstore) {
setupUsers(t, ctx, cs, 1)
users, err := getUsers(ctx, cs)
testutil.NilError(t, err)
userOwner := users[0]
setupOrg(t, ctx, cs, userOwner.ID)
setupProjects(t, ctx, cs, 1)
projects, err := getProjects(ctx, cs)
testutil.NilError(t, err)
_, err = cs.ah.CreateUserProjectFavorite(ctx, &action.CreateUserProjectFavoriteRequest{UserRef: "user1", ProjectRef: projects[0].ID})
testutil.NilError(t, err)
err = cs.ah.DeleteUserProjectFavorite(ctx, "user1", projects[0].ID)
testutil.NilError(t, err)
aresp, err := cs.ah.GetUserProjectFavorites(ctx, &action.GetUserProjectFavoritesRequest{UserRef: "user1"})
testutil.NilError(t, err)
assert.Assert(t, cmp.Len(aresp.UserProjectFavorites, 0))
},
},
{
name: "test user project favorite deletion with not existing user project favorite",
f: func(ctx context.Context, t *testing.T, cs *Configstore) {
setupUsers(t, ctx, cs, 1)
users, err := getUsers(ctx, cs)
testutil.NilError(t, err)
userOwner := users[0]
setupOrg(t, ctx, cs, userOwner.ID)
setupProjects(t, ctx, cs, 1)
projects, err := getProjects(ctx, cs)
testutil.NilError(t, err)
expectedErr := util.NewAPIError(util.ErrBadRequest, errors.Errorf("user project favorite for user %q, project %q doesn't exist", "user1", projects[0].ID))
err = cs.ah.DeleteUserProjectFavorite(ctx, "user1", projects[0].ID)
assert.Error(t, err, expectedErr.Error())
},
},
{
name: "test user deletion with existing user project favorite",
f: func(ctx context.Context, t *testing.T, cs *Configstore) {
setupUsers(t, ctx, cs, 1)
users, err := getUsers(ctx, cs)
testutil.NilError(t, err)
userOwner := users[0]
setupOrg(t, ctx, cs, userOwner.ID)
setupProjects(t, ctx, cs, 1)
projects, err := getProjects(ctx, cs)
testutil.NilError(t, err)
_, err = cs.ah.CreateUserProjectFavorite(ctx, &action.CreateUserProjectFavoriteRequest{UserRef: "user1", ProjectRef: projects[0].ID})
testutil.NilError(t, err)
err = cs.ah.DeleteUser(ctx, "user1")
testutil.NilError(t, err)
userProjectFavorites, err := getUserProjectFavorites(ctx, cs)
testutil.NilError(t, err)
assert.Assert(t, cmp.Len(userProjectFavorites, 0))
},
},
{
name: "test user project deletion with existing project favorite",
f: func(ctx context.Context, t *testing.T, cs *Configstore) {
setupUsers(t, ctx, cs, 1)
users, err := getUsers(ctx, cs)
testutil.NilError(t, err)
userOwner := users[0]
setupOrg(t, ctx, cs, userOwner.ID)
setupProjects(t, ctx, cs, 1)
projects, err := getProjects(ctx, cs)
testutil.NilError(t, err)
_, err = cs.ah.CreateUserProjectFavorite(ctx, &action.CreateUserProjectFavoriteRequest{UserRef: "user1", ProjectRef: projects[0].ID})
testutil.NilError(t, err)
err = cs.ah.DeleteProject(ctx, projects[0].ID)
testutil.NilError(t, err)
userProjectFavorites, err := getUserProjectFavorites(ctx, cs)
testutil.NilError(t, err)
assert.Assert(t, cmp.Len(userProjectFavorites, 0))
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
ctx := context.Background()
cs := setupConfigstore(ctx, t, log, dir)
t.Logf("starting cs")
go func() { _ = cs.Run(ctx) }()
tt.f(ctx, t, cs)
})
}
}

View File

@ -849,3 +849,72 @@ func (d *DB) DeleteOrgInvitationsByUserID(tx *sql.Tx, userID string) error {
return nil
}
func (d *DB) GetUserProjectFavorites(tx *sql.Tx) ([]*types.UserProjectFavorite, error) {
q := userProjectFavoriteSelect()
userProjectFavorites, _, err := d.fetchUserProjectFavorites(tx, q)
return userProjectFavorites, errors.WithStack(err)
}
func (d *DB) GetUserProjectFavorite(tx *sql.Tx, userID string, projectID string) (*types.UserProjectFavorite, error) {
q := userProjectFavoriteSelect()
q.Where(q.E("user_id", userID))
q.Where(q.E("project_id", projectID))
userProjectFavorites, _, err := d.fetchUserProjectFavorites(tx, q)
if err != nil {
return nil, errors.WithStack(err)
}
out, err := mustSingleRow(userProjectFavorites)
return out, errors.WithStack(err)
}
func (d *DB) GetUserProjectFavoritesByUserID(tx *sql.Tx, userID string, startUserProjectFavoriteID string, limit int, sortDirection types.SortDirection) ([]*types.UserProjectFavorite, error) {
q := userProjectFavoriteSelect()
q.Where(q.E("user_id", userID))
q.OrderBy("id")
switch sortDirection {
case types.SortDirectionAsc:
q = q.Asc()
case types.SortDirectionDesc:
q = q.Desc()
}
if startUserProjectFavoriteID != "" {
switch sortDirection {
case types.SortDirectionAsc:
q = q.Where(q.G("id", startUserProjectFavoriteID))
case types.SortDirectionDesc:
q = q.Where(q.L("id", startUserProjectFavoriteID))
}
}
if limit > 0 {
q = q.Limit(limit)
}
userProjectFavorites, _, err := d.fetchUserProjectFavorites(tx, q)
return userProjectFavorites, errors.WithStack(err)
}
func (d *DB) DeleteUserProjectFavoritesByProjectID(tx *sql.Tx, projectID string) error {
q := sq.NewDeleteBuilder()
q.DeleteFrom("userprojectfavorite").Where(q.E("project_id", projectID))
if _, err := d.exec(tx, q); err != nil {
return errors.Wrap(err, "failed to delete userprojectfavorite")
}
return nil
}
func (d *DB) DeleteUserProjectFavoritesByUserID(tx *sql.Tx, userID string) error {
q := sq.NewDeleteBuilder()
q.DeleteFrom("userprojectfavorite").Where(q.E("user_id", userID))
if _, err := d.exec(tx, q); err != nil {
return errors.Wrap(err, "failed to delete userprojectfavorite")
}
return nil
}

View File

@ -16,6 +16,7 @@ var DDLPostgres = []string{
"create table if not exists secret (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, name varchar NOT NULL, parent_kind varchar NOT NULL, parent_id varchar NOT NULL, type varchar NOT NULL, data jsonb NOT NULL, secret_provider_id varchar NOT NULL, path varchar NOT NULL, PRIMARY KEY (id))",
"create table if not exists variable (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, name varchar NOT NULL, parent_kind varchar NOT NULL, parent_id varchar NOT NULL, variable_values jsonb NOT NULL, PRIMARY KEY (id))",
"create table if not exists orginvitation (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, user_id varchar NOT NULL, organization_id varchar NOT NULL, role varchar NOT NULL, PRIMARY KEY (id), foreign key (user_id) references user_t(id), foreign key (organization_id) references organization(id))",
"create table if not exists userprojectfavorite (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, user_id varchar NOT NULL, project_id varchar NOT NULL, PRIMARY KEY (id), foreign key (user_id) references user_t(id), foreign key (project_id) references project(id))",
// indexes
}
@ -31,6 +32,7 @@ var DDLSqlite3 = []string{
"create table if not exists secret (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, name varchar NOT NULL, parent_kind varchar NOT NULL, parent_id varchar NOT NULL, type varchar NOT NULL, data text NOT NULL, secret_provider_id varchar NOT NULL, path varchar NOT NULL, PRIMARY KEY (id))",
"create table if not exists variable (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, name varchar NOT NULL, parent_kind varchar NOT NULL, parent_id varchar NOT NULL, variable_values text NOT NULL, PRIMARY KEY (id))",
"create table if not exists orginvitation (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, user_id varchar NOT NULL, organization_id varchar NOT NULL, role varchar NOT NULL, PRIMARY KEY (id), foreign key (user_id) references user_t(id), foreign key (organization_id) references organization(id))",
"create table if not exists userprojectfavorite (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, user_id varchar NOT NULL, project_id varchar NOT NULL, PRIMARY KEY (id), foreign key (user_id) references user_t(id), foreign key (project_id) references project(id))",
// indexes
}

View File

@ -1479,6 +1479,139 @@ func (d *DB) insertRawOrgInvitation(tx *sql.Tx, v *types.OrgInvitation) error {
return nil
}
var (
userProjectFavoriteSelectColumns = func(additionalCols ...string) []string {
columns := []string{"userprojectfavorite.id", "userprojectfavorite.revision", "userprojectfavorite.creation_time", "userprojectfavorite.update_time", "userprojectfavorite.user_id", "userprojectfavorite.project_id"}
columns = append(columns, additionalCols...)
return columns
}
userProjectFavoriteSelect = func(additionalCols ...string) *sq.SelectBuilder {
return sq.NewSelectBuilder().Select(userProjectFavoriteSelectColumns(additionalCols...)...).From("userprojectfavorite")
}
)
func (d *DB) InsertOrUpdateUserProjectFavorite(tx *sql.Tx, v *types.UserProjectFavorite) error {
var err error
if v.Revision == 0 {
err = d.InsertUserProjectFavorite(tx, v)
} else {
err = d.UpdateUserProjectFavorite(tx, v)
}
return errors.WithStack(err)
}
func (d *DB) InsertUserProjectFavorite(tx *sql.Tx, v *types.UserProjectFavorite) error {
if v.Revision != 0 {
return errors.Errorf("expected revision 0 got %d", v.Revision)
}
if v.TxID != tx.ID() {
return errors.Errorf("object was not created by this transaction")
}
v.Revision = 1
now := time.Now()
v.CreationTime = now
v.UpdateTime = now
var err error
switch d.DBType() {
case sql.Postgres:
err = d.insertRawUserProjectFavoritePostgres(tx, v);
case sql.Sqlite3:
err = d.insertUserProjectFavoriteSqlite3(tx, v);
}
if err != nil {
v.Revision = 0
return errors.Wrap(err, "failed to insert userprojectfavorite")
}
return nil
}
func (d *DB) UpdateUserProjectFavorite(tx *sql.Tx, v *types.UserProjectFavorite) error {
if v.Revision < 1 {
return errors.Errorf("expected revision > 0 got %d", v.Revision)
}
if v.TxID != tx.ID() {
return errors.Errorf("object was not fetched by this transaction")
}
curRevision := v.Revision
v.Revision++
v.UpdateTime = time.Now()
var res stdsql.Result
var err error
switch d.DBType() {
case sql.Postgres:
res, err = d.updateUserProjectFavoritePostgres(tx, curRevision, v);
case sql.Sqlite3:
res, err = d.updateUserProjectFavoriteSqlite3(tx, curRevision, v);
}
if err != nil {
v.Revision = curRevision
return errors.Wrap(err, "failed to update userprojectfavorite")
}
rows, err := res.RowsAffected()
if err != nil {
v.Revision = curRevision
return errors.Wrap(err, "failed to update userprojectfavorite")
}
if rows != 1 {
v.Revision = curRevision
return sqlg.ErrConcurrent
}
return nil
}
func (d *DB) deleteUserProjectFavorite(tx *sql.Tx, userProjectFavoriteID string) error {
q := sq.NewDeleteBuilder()
q.DeleteFrom("userprojectfavorite").Where(q.E("id", userProjectFavoriteID))
if _, err := d.exec(tx, q); err != nil {
return errors.Wrap(err, "failed to delete userProjectFavorite")
}
return nil
}
func (d *DB) DeleteUserProjectFavorite(tx *sql.Tx, id string) error {
return d.deleteUserProjectFavorite(tx, id)
}
// insertRawUserProjectFavorite should be used only for import.
// * It won't update object times.
// * It will insert values for sequences.
func (d *DB) insertRawUserProjectFavorite(tx *sql.Tx, v *types.UserProjectFavorite) error {
v.Revision = 1
var err error
switch d.DBType() {
case sql.Postgres:
err = d.insertRawUserProjectFavoritePostgres(tx, v);
case sql.Sqlite3:
err = d.insertRawUserProjectFavoriteSqlite3(tx, v);
}
if err != nil {
v.Revision = 0
return errors.Wrap(err, "failed to insert userprojectfavorite")
}
return nil
}
func (d *DB) UnmarshalExportObject(data []byte) (sqlg.Object, error) {
type exportObjectExportMeta struct {
ExportMeta sqlg.ExportMeta `json:"exportMeta"`
@ -1514,6 +1647,8 @@ func (d *DB) UnmarshalExportObject(data []byte) (sqlg.Object, error) {
obj = &types.Variable{}
case "OrgInvitation":
obj = &types.OrgInvitation{}
case "UserProjectFavorite":
obj = &types.UserProjectFavorite{}
default:
panic(errors.Errorf("unknown object kind %q, data: %s", om.ExportMeta.Kind, data))
@ -1550,6 +1685,8 @@ func (d *DB) InsertRawObject(tx *sql.Tx, obj sqlg.Object) error {
return d.insertRawVariable(tx, o)
case *types.OrgInvitation:
return d.insertRawOrgInvitation(tx, o)
case *types.UserProjectFavorite:
return d.insertRawUserProjectFavorite(tx, o)
default:
panic(errors.Errorf("unknown object type %T", obj))
@ -1580,6 +1717,8 @@ func (d *DB) SelectObject(kind string) *sq.SelectBuilder {
return variableSelect()
case "OrgInvitation":
return orgInvitationSelect()
case "UserProjectFavorite":
return userProjectFavoriteSelect()
default:
panic(errors.Errorf("unknown object kind %q", kind))
@ -1719,6 +1858,18 @@ func (d *DB) FetchObjects(tx *sql.Tx, kind string, q sq.Builder) ([]sqlg.Object,
objs[i] = fobj
}
return objs, nil
case "UserProjectFavorite":
fobjs, _, err := d.fetchUserProjectFavorites(tx, q)
if err != nil {
return nil, errors.WithStack(err)
}
objs := make([]sqlg.Object, len(fobjs))
for i, fobj := range fobjs {
objs[i] = fobj
}
return objs, nil
default:
@ -1859,6 +2010,18 @@ func (d *DB) ObjectToExportJSON(obj sqlg.Object, e *json.Encoder) error {
return errors.WithStack(err)
}
return nil
case *types.UserProjectFavorite:
type exportObject struct {
ExportMeta sqlg.ExportMeta `json:"exportMeta"`
*types.UserProjectFavorite
}
if err := e.Encode(&exportObject{ExportMeta: sqlg.ExportMeta{ Kind: "UserProjectFavorite" }, UserProjectFavorite: o}); err != nil {
return errors.WithStack(err)
}
return nil
default:

View File

@ -543,3 +543,49 @@ func (d *DB) insertRawOrgInvitationPostgres(tx *sql.Tx, orginvitation *types.Org
return nil
}
var (
userProjectFavoriteInsertPostgres = func(inID string, inRevision uint64, inCreationTime time.Time, inUpdateTime time.Time, inUserID string, inProjectID string) *sq.InsertBuilder {
ib:= sq.NewInsertBuilder()
return ib.InsertInto("userprojectfavorite").Cols("id", "revision", "creation_time", "update_time", "user_id", "project_id").Values(inID, inRevision, inCreationTime, inUpdateTime, inUserID, inProjectID)
}
userProjectFavoriteUpdatePostgres = func(curRevision uint64, inID string, inRevision uint64, inCreationTime time.Time, inUpdateTime time.Time, inUserID string, inProjectID string) *sq.UpdateBuilder {
ub:= sq.NewUpdateBuilder()
return ub.Update("userprojectfavorite").Set(ub.Assign("id", inID), ub.Assign("revision", inRevision), ub.Assign("creation_time", inCreationTime), ub.Assign("update_time", inUpdateTime), ub.Assign("user_id", inUserID), ub.Assign("project_id", inProjectID)).Where(ub.E("id", inID), ub.E("revision", curRevision))
}
userProjectFavoriteInsertRawPostgres = func(inID string, inRevision uint64, inCreationTime time.Time, inUpdateTime time.Time, inUserID string, inProjectID string) *sq.InsertBuilder {
ib:= sq.NewInsertBuilder()
return ib.InsertInto("userprojectfavorite").Cols("id", "revision", "creation_time", "update_time", "user_id", "project_id").SQL("OVERRIDING SYSTEM VALUE").Values(inID, inRevision, inCreationTime, inUpdateTime, inUserID, inProjectID)
}
)
func (d *DB) insertUserProjectFavoritePostgres(tx *sql.Tx, userprojectfavorite *types.UserProjectFavorite) error {
q := userProjectFavoriteInsertPostgres(userprojectfavorite.ID, userprojectfavorite.Revision, userprojectfavorite.CreationTime, userprojectfavorite.UpdateTime, userprojectfavorite.UserID, userprojectfavorite.ProjectID)
if _, err := d.exec(tx, q); err != nil {
return errors.Wrap(err, "failed to insert userProjectFavorite")
}
return nil
}
func (d *DB) updateUserProjectFavoritePostgres(tx *sql.Tx, curRevision uint64, userprojectfavorite *types.UserProjectFavorite) (stdsql.Result, error) {
q := userProjectFavoriteUpdatePostgres(curRevision, userprojectfavorite.ID, userprojectfavorite.Revision, userprojectfavorite.CreationTime, userprojectfavorite.UpdateTime, userprojectfavorite.UserID, userprojectfavorite.ProjectID)
res, err := d.exec(tx, q)
if err != nil {
return nil, errors.Wrap(err, "failed to update userProjectFavorite")
}
return res, nil
}
func (d *DB) insertRawUserProjectFavoritePostgres(tx *sql.Tx, userprojectfavorite *types.UserProjectFavorite) error {
q := userProjectFavoriteInsertRawPostgres(userprojectfavorite.ID, userprojectfavorite.Revision, userprojectfavorite.CreationTime, userprojectfavorite.UpdateTime, userprojectfavorite.UserID, userprojectfavorite.ProjectID)
if _, err := d.exec(tx, q); err != nil {
return errors.Wrap(err, "failed to insert userProjectFavorite")
}
return nil
}

View File

@ -543,3 +543,49 @@ func (d *DB) insertRawOrgInvitationSqlite3(tx *sql.Tx, orginvitation *types.OrgI
return nil
}
var (
userProjectFavoriteInsertSqlite3 = func(inID string, inRevision uint64, inCreationTime time.Time, inUpdateTime time.Time, inUserID string, inProjectID string) *sq.InsertBuilder {
ib:= sq.NewInsertBuilder()
return ib.InsertInto("userprojectfavorite").Cols("id", "revision", "creation_time", "update_time", "user_id", "project_id").Values(inID, inRevision, inCreationTime, inUpdateTime, inUserID, inProjectID)
}
userProjectFavoriteUpdateSqlite3 = func(curRevision uint64, inID string, inRevision uint64, inCreationTime time.Time, inUpdateTime time.Time, inUserID string, inProjectID string) *sq.UpdateBuilder {
ub:= sq.NewUpdateBuilder()
return ub.Update("userprojectfavorite").Set(ub.Assign("id", inID), ub.Assign("revision", inRevision), ub.Assign("creation_time", inCreationTime), ub.Assign("update_time", inUpdateTime), ub.Assign("user_id", inUserID), ub.Assign("project_id", inProjectID)).Where(ub.E("id", inID), ub.E("revision", curRevision))
}
userProjectFavoriteInsertRawSqlite3 = func(inID string, inRevision uint64, inCreationTime time.Time, inUpdateTime time.Time, inUserID string, inProjectID string) *sq.InsertBuilder {
ib:= sq.NewInsertBuilder()
return ib.InsertInto("userprojectfavorite").Cols("id", "revision", "creation_time", "update_time", "user_id", "project_id").SQL("").Values(inID, inRevision, inCreationTime, inUpdateTime, inUserID, inProjectID)
}
)
func (d *DB) insertUserProjectFavoriteSqlite3(tx *sql.Tx, userprojectfavorite *types.UserProjectFavorite) error {
q := userProjectFavoriteInsertSqlite3(userprojectfavorite.ID, userprojectfavorite.Revision, userprojectfavorite.CreationTime, userprojectfavorite.UpdateTime, userprojectfavorite.UserID, userprojectfavorite.ProjectID)
if _, err := d.exec(tx, q); err != nil {
return errors.Wrap(err, "failed to insert userProjectFavorite")
}
return nil
}
func (d *DB) updateUserProjectFavoriteSqlite3(tx *sql.Tx, curRevision uint64, userprojectfavorite *types.UserProjectFavorite) (stdsql.Result, error) {
q := userProjectFavoriteUpdateSqlite3(curRevision, userprojectfavorite.ID, userprojectfavorite.Revision, userprojectfavorite.CreationTime, userprojectfavorite.UpdateTime, userprojectfavorite.UserID, userprojectfavorite.ProjectID)
res, err := d.exec(tx, q)
if err != nil {
return nil, errors.Wrap(err, "failed to update userProjectFavorite")
}
return res, nil
}
func (d *DB) insertRawUserProjectFavoriteSqlite3(tx *sql.Tx, userprojectfavorite *types.UserProjectFavorite) error {
q := userProjectFavoriteInsertRawSqlite3(userprojectfavorite.ID, userprojectfavorite.Revision, userprojectfavorite.CreationTime, userprojectfavorite.UpdateTime, userprojectfavorite.UserID, userprojectfavorite.ProjectID)
if _, err := d.exec(tx, q); err != nil {
return errors.Wrap(err, "failed to insert userProjectFavorite")
}
return nil
}

View File

@ -1260,3 +1260,107 @@ func (d *DB) OrgInvitationFromArray(a []any, txID string) (*types.OrgInvitation,
return v, v.ID, nil
}
func (d *DB) fetchUserProjectFavorites(tx *sql.Tx, q sq.Builder) ([]*types.UserProjectFavorite, []string, error) {
rows, err := d.query(tx, q)
if err != nil {
return nil, nil, errors.WithStack(err)
}
defer rows.Close()
return d.scanUserProjectFavorites(rows, tx.ID(), 0)
}
func (d *DB) fetchUserProjectFavoritesSkipLastFields(tx *sql.Tx, q sq.Builder, skipFieldsCount uint) ([]*types.UserProjectFavorite, []string, error) {
rows, err := d.query(tx, q)
if err != nil {
return nil, nil, errors.WithStack(err)
}
defer rows.Close()
return d.scanUserProjectFavorites(rows, tx.ID(), skipFieldsCount)
}
func (d *DB) scanUserProjectFavorite(rows *stdsql.Rows, skipFieldsCount uint) (*types.UserProjectFavorite, string, error) {
v := &types.UserProjectFavorite{}
var vi any = v
if x, ok := vi.(sqlg.Initer); ok {
x.Init()
}
fields := []any{&v.ID, &v.Revision, &v.CreationTime, &v.UpdateTime, &v.UserID, &v.ProjectID}
for i := uint(0); i < skipFieldsCount; i++ {
fields = append(fields, new(any))
}
if err := rows.Scan(fields...); err != nil {
return nil, "", errors.Wrap(err, "failed to scan row")
}
if x, ok := vi.(sqlg.PreJSONSetupper); ok {
if err := x.PreJSON(); err != nil {
return nil, "", errors.Wrap(err, "prejson error")
}
}
return v, v.ID, nil
}
func (d *DB) scanUserProjectFavorites(rows *stdsql.Rows, txID string, skipFieldsCount uint) ([]*types.UserProjectFavorite, []string, error) {
vs := []*types.UserProjectFavorite{}
ids := []string{}
for rows.Next() {
v, id, err := d.scanUserProjectFavorite(rows, skipFieldsCount)
if err != nil {
rows.Close()
return nil, nil, errors.WithStack(err)
}
v.TxID = txID
vs = append(vs, v)
ids = append(ids, id)
}
if err := rows.Err(); err != nil {
return nil, nil, errors.WithStack(err)
}
return vs, ids, nil
}
func (d *DB) UserProjectFavoriteArray() []any {
a := []any{}
a = append(a, new(string))
a = append(a, new(uint64))
a = append(a, new(time.Time))
a = append(a, new(time.Time))
a = append(a, new(string))
a = append(a, new(string))
return a
}
func (d *DB) UserProjectFavoriteFromArray(a []any, txID string) (*types.UserProjectFavorite, string, error) {
v := &types.UserProjectFavorite{}
var vi any = v
if x, ok := vi.(sqlg.Initer); ok {
x.Init()
}
v.ID = *a[0].(*string)
v.Revision = *a[1].(*uint64)
v.CreationTime = *a[2].(*time.Time)
v.UpdateTime = *a[3].(*time.Time)
v.UserID = *a[4].(*string)
v.ProjectID = *a[5].(*string)
if x, ok := vi.(sqlg.PreJSONSetupper); ok {
if err := x.PreJSON(); err != nil {
return nil, "", errors.Wrap(err, "prejson error")
}
}
v.TxID = txID
return v, v.ID, nil
}

View File

@ -11,7 +11,7 @@ import (
"github.com/sorintlab/errors"
)
func (d *DB) Version() uint { return 3 }
func (d *DB) Version() uint { return 4 }
func (d *DB) DDL() []string {
switch d.DBType() {

View File

@ -14,6 +14,7 @@ func (d *DB) MigrateFuncs() map[uint]sqlg.MigrateFunc {
return map[uint]sqlg.MigrateFunc{
2: d.migrateV2,
3: d.migrateV3,
4: d.migrateV4,
}
}
@ -143,3 +144,29 @@ func (d *DB) migrateV3(tx *sql.Tx) error {
return nil
}
func (d *DB) migrateV4(tx *sql.Tx) error {
var ddlPostgres = []string{
"create table if not exists userprojectfavorite (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, user_id varchar NOT NULL, project_id varchar NOT NULL, PRIMARY KEY (id), foreign key (user_id) references user_t(id), foreign key (project_id) references project(id))",
}
var ddlSqlite3 = []string{
"create table if not exists userprojectfavorite (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, user_id varchar NOT NULL, project_id varchar NOT NULL, PRIMARY KEY (id), foreign key (user_id) references user_t(id), foreign key (project_id) references project(id))",
}
var stmts []string
switch d.sdb.Type() {
case sql.Postgres:
stmts = ddlPostgres
case sql.Sqlite3:
stmts = ddlSqlite3
}
for _, stmt := range stmts {
if _, err := tx.Exec(stmt); err != nil {
return errors.WithStack(err)
}
}
return nil
}

View File

@ -5,7 +5,7 @@ import (
)
const (
Version = uint(3)
Version = uint(4)
)
const TypesImport = "agola.io/agola/services/configstore/types"
@ -138,4 +138,14 @@ var ObjectsInfo = []sqlg.ObjectInfo{
"foreign key (organization_id) references organization(id)",
},
},
{Name: "UserProjectFavorite", Table: "userprojectfavorite",
Fields: []sqlg.ObjectField{
{Name: "UserID", Type: "string"},
{Name: "ProjectID", Type: "string"},
},
Constraints: []string{
"foreign key (user_id) references user_t(id)",
"foreign key (project_id) references project(id)",
},
},
}

View File

@ -0,0 +1,675 @@
{
"ddl": {
"postgres": [
"create table if not exists remotesource (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, name varchar NOT NULL, apiurl varchar NOT NULL, skip_verify boolean NOT NULL, type varchar NOT NULL, auth_type varchar NOT NULL, oauth2_client_id varchar NOT NULL, oauth2_client_secret varchar NOT NULL, ssh_host_key varchar NOT NULL, skip_ssh_host_key_check boolean NOT NULL, registration_enabled boolean NOT NULL, login_enabled boolean NOT NULL, PRIMARY KEY (id))",
"create table if not exists user_t (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, name varchar NOT NULL, secret varchar NOT NULL, admin boolean NOT NULL, PRIMARY KEY (id))",
"create table if not exists usertoken (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, user_id varchar NOT NULL, name varchar NOT NULL, value varchar NOT NULL, PRIMARY KEY (id), foreign key (user_id) references user_t(id))",
"create table if not exists linkedaccount (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, user_id varchar NOT NULL, remote_user_id varchar NOT NULL, remote_user_name varchar NOT NULL, remote_user_avatar_url varchar NOT NULL, remote_source_id varchar NOT NULL, user_access_token varchar NOT NULL, oauth2_access_token varchar NOT NULL, oauth2_refresh_token varchar NOT NULL, oauth2_access_token_expires_at timestamptz NOT NULL, PRIMARY KEY (id), foreign key (user_id) references user_t(id), foreign key (remote_source_id) references remotesource(id))",
"create table if not exists organization (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, name varchar NOT NULL, visibility varchar NOT NULL, creator_user_id varchar NOT NULL, PRIMARY KEY (id))",
"create table if not exists orgmember (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, organization_id varchar NOT NULL, user_id varchar NOT NULL, member_role varchar NOT NULL, PRIMARY KEY (id), foreign key (organization_id) references organization(id), foreign key (user_id) references user_t(id))",
"create table if not exists projectgroup (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, name varchar NOT NULL, parent_kind varchar NOT NULL, parent_id varchar NOT NULL, visibility varchar NOT NULL, PRIMARY KEY (id))",
"create table if not exists project (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, name varchar NOT NULL, parent_kind varchar NOT NULL, parent_id varchar NOT NULL, secret varchar NOT NULL, visibility varchar NOT NULL, remote_repository_config_type varchar NOT NULL, remote_source_id varchar NOT NULL, linked_account_id varchar NOT NULL, repository_id varchar NOT NULL, repository_path varchar NOT NULL, ssh_private_key varchar NOT NULL, skip_ssh_host_key_check boolean NOT NULL, webhook_secret varchar NOT NULL, pass_vars_to_forked_pr boolean NOT NULL, default_branch varchar NOT NULL, members_can_perform_run_actions boolean NOT NULL, PRIMARY KEY (id))",
"create table if not exists secret (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, name varchar NOT NULL, parent_kind varchar NOT NULL, parent_id varchar NOT NULL, type varchar NOT NULL, data jsonb NOT NULL, secret_provider_id varchar NOT NULL, path varchar NOT NULL, PRIMARY KEY (id))",
"create table if not exists variable (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, name varchar NOT NULL, parent_kind varchar NOT NULL, parent_id varchar NOT NULL, variable_values jsonb NOT NULL, PRIMARY KEY (id))",
"create table if not exists orginvitation (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, user_id varchar NOT NULL, organization_id varchar NOT NULL, role varchar NOT NULL, PRIMARY KEY (id), foreign key (user_id) references user_t(id), foreign key (organization_id) references organization(id))",
"create table if not exists userprojectfavorite (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamptz NOT NULL, update_time timestamptz NOT NULL, user_id varchar NOT NULL, project_id varchar NOT NULL, PRIMARY KEY (id), foreign key (user_id) references user_t(id), foreign key (project_id) references project(id))"
],
"sqlite3": [
"create table if not exists remotesource (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, name varchar NOT NULL, apiurl varchar NOT NULL, skip_verify integer NOT NULL, type varchar NOT NULL, auth_type varchar NOT NULL, oauth2_client_id varchar NOT NULL, oauth2_client_secret varchar NOT NULL, ssh_host_key varchar NOT NULL, skip_ssh_host_key_check integer NOT NULL, registration_enabled integer NOT NULL, login_enabled integer NOT NULL, PRIMARY KEY (id))",
"create table if not exists user_t (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, name varchar NOT NULL, secret varchar NOT NULL, admin integer NOT NULL, PRIMARY KEY (id))",
"create table if not exists usertoken (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, user_id varchar NOT NULL, name varchar NOT NULL, value varchar NOT NULL, PRIMARY KEY (id), foreign key (user_id) references user_t(id))",
"create table if not exists linkedaccount (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, user_id varchar NOT NULL, remote_user_id varchar NOT NULL, remote_user_name varchar NOT NULL, remote_user_avatar_url varchar NOT NULL, remote_source_id varchar NOT NULL, user_access_token varchar NOT NULL, oauth2_access_token varchar NOT NULL, oauth2_refresh_token varchar NOT NULL, oauth2_access_token_expires_at timestamp NOT NULL, PRIMARY KEY (id), foreign key (user_id) references user_t(id), foreign key (remote_source_id) references remotesource(id))",
"create table if not exists organization (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, name varchar NOT NULL, visibility varchar NOT NULL, creator_user_id varchar NOT NULL, PRIMARY KEY (id))",
"create table if not exists orgmember (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, organization_id varchar NOT NULL, user_id varchar NOT NULL, member_role varchar NOT NULL, PRIMARY KEY (id), foreign key (organization_id) references organization(id), foreign key (user_id) references user_t(id))",
"create table if not exists projectgroup (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, name varchar NOT NULL, parent_kind varchar NOT NULL, parent_id varchar NOT NULL, visibility varchar NOT NULL, PRIMARY KEY (id))",
"create table if not exists project (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, name varchar NOT NULL, parent_kind varchar NOT NULL, parent_id varchar NOT NULL, secret varchar NOT NULL, visibility varchar NOT NULL, remote_repository_config_type varchar NOT NULL, remote_source_id varchar NOT NULL, linked_account_id varchar NOT NULL, repository_id varchar NOT NULL, repository_path varchar NOT NULL, ssh_private_key varchar NOT NULL, skip_ssh_host_key_check integer NOT NULL, webhook_secret varchar NOT NULL, pass_vars_to_forked_pr integer NOT NULL, default_branch varchar NOT NULL, members_can_perform_run_actions integer NOT NULL, PRIMARY KEY (id))",
"create table if not exists secret (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, name varchar NOT NULL, parent_kind varchar NOT NULL, parent_id varchar NOT NULL, type varchar NOT NULL, data text NOT NULL, secret_provider_id varchar NOT NULL, path varchar NOT NULL, PRIMARY KEY (id))",
"create table if not exists variable (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, name varchar NOT NULL, parent_kind varchar NOT NULL, parent_id varchar NOT NULL, variable_values text NOT NULL, PRIMARY KEY (id))",
"create table if not exists orginvitation (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, user_id varchar NOT NULL, organization_id varchar NOT NULL, role varchar NOT NULL, PRIMARY KEY (id), foreign key (user_id) references user_t(id), foreign key (organization_id) references organization(id))",
"create table if not exists userprojectfavorite (id varchar NOT NULL, revision bigint NOT NULL, creation_time timestamp NOT NULL, update_time timestamp NOT NULL, user_id varchar NOT NULL, project_id varchar NOT NULL, PRIMARY KEY (id), foreign key (user_id) references user_t(id), foreign key (project_id) references project(id))"
]
},
"sequences": [],
"tables": [
{
"name": "remotesource",
"columns": [
{
"name": "id",
"type": "string",
"nullable": false
},
{
"name": "revision",
"type": "uint64",
"nullable": false
},
{
"name": "creation_time",
"type": "time.Time",
"nullable": false
},
{
"name": "update_time",
"type": "time.Time",
"nullable": false
},
{
"name": "name",
"type": "string",
"nullable": false
},
{
"name": "apiurl",
"type": "string",
"nullable": false
},
{
"name": "skip_verify",
"type": "bool",
"nullable": false
},
{
"name": "type",
"type": "string",
"nullable": false
},
{
"name": "auth_type",
"type": "string",
"nullable": false
},
{
"name": "oauth2_client_id",
"type": "string",
"nullable": false
},
{
"name": "oauth2_client_secret",
"type": "string",
"nullable": false
},
{
"name": "ssh_host_key",
"type": "string",
"nullable": false
},
{
"name": "skip_ssh_host_key_check",
"type": "bool",
"nullable": false
},
{
"name": "registration_enabled",
"type": "bool",
"nullable": false
},
{
"name": "login_enabled",
"type": "bool",
"nullable": false
}
]
},
{
"name": "user_t",
"columns": [
{
"name": "id",
"type": "string",
"nullable": false
},
{
"name": "revision",
"type": "uint64",
"nullable": false
},
{
"name": "creation_time",
"type": "time.Time",
"nullable": false
},
{
"name": "update_time",
"type": "time.Time",
"nullable": false
},
{
"name": "name",
"type": "string",
"nullable": false
},
{
"name": "secret",
"type": "string",
"nullable": false
},
{
"name": "admin",
"type": "bool",
"nullable": false
}
]
},
{
"name": "usertoken",
"columns": [
{
"name": "id",
"type": "string",
"nullable": false
},
{
"name": "revision",
"type": "uint64",
"nullable": false
},
{
"name": "creation_time",
"type": "time.Time",
"nullable": false
},
{
"name": "update_time",
"type": "time.Time",
"nullable": false
},
{
"name": "user_id",
"type": "string",
"nullable": false
},
{
"name": "name",
"type": "string",
"nullable": false
},
{
"name": "value",
"type": "string",
"nullable": false
}
]
},
{
"name": "linkedaccount",
"columns": [
{
"name": "id",
"type": "string",
"nullable": false
},
{
"name": "revision",
"type": "uint64",
"nullable": false
},
{
"name": "creation_time",
"type": "time.Time",
"nullable": false
},
{
"name": "update_time",
"type": "time.Time",
"nullable": false
},
{
"name": "user_id",
"type": "string",
"nullable": false
},
{
"name": "remote_user_id",
"type": "string",
"nullable": false
},
{
"name": "remote_user_name",
"type": "string",
"nullable": false
},
{
"name": "remote_user_avatar_url",
"type": "string",
"nullable": false
},
{
"name": "remote_source_id",
"type": "string",
"nullable": false
},
{
"name": "user_access_token",
"type": "string",
"nullable": false
},
{
"name": "oauth2_access_token",
"type": "string",
"nullable": false
},
{
"name": "oauth2_refresh_token",
"type": "string",
"nullable": false
},
{
"name": "oauth2_access_token_expires_at",
"type": "time.Time",
"nullable": false
}
]
},
{
"name": "organization",
"columns": [
{
"name": "id",
"type": "string",
"nullable": false
},
{
"name": "revision",
"type": "uint64",
"nullable": false
},
{
"name": "creation_time",
"type": "time.Time",
"nullable": false
},
{
"name": "update_time",
"type": "time.Time",
"nullable": false
},
{
"name": "name",
"type": "string",
"nullable": false
},
{
"name": "visibility",
"type": "string",
"nullable": false
},
{
"name": "creator_user_id",
"type": "string",
"nullable": false
}
]
},
{
"name": "orgmember",
"columns": [
{
"name": "id",
"type": "string",
"nullable": false
},
{
"name": "revision",
"type": "uint64",
"nullable": false
},
{
"name": "creation_time",
"type": "time.Time",
"nullable": false
},
{
"name": "update_time",
"type": "time.Time",
"nullable": false
},
{
"name": "organization_id",
"type": "string",
"nullable": false
},
{
"name": "user_id",
"type": "string",
"nullable": false
},
{
"name": "member_role",
"type": "string",
"nullable": false
}
]
},
{
"name": "projectgroup",
"columns": [
{
"name": "id",
"type": "string",
"nullable": false
},
{
"name": "revision",
"type": "uint64",
"nullable": false
},
{
"name": "creation_time",
"type": "time.Time",
"nullable": false
},
{
"name": "update_time",
"type": "time.Time",
"nullable": false
},
{
"name": "name",
"type": "string",
"nullable": false
},
{
"name": "parent_kind",
"type": "string",
"nullable": false
},
{
"name": "parent_id",
"type": "string",
"nullable": false
},
{
"name": "visibility",
"type": "string",
"nullable": false
}
]
},
{
"name": "project",
"columns": [
{
"name": "id",
"type": "string",
"nullable": false
},
{
"name": "revision",
"type": "uint64",
"nullable": false
},
{
"name": "creation_time",
"type": "time.Time",
"nullable": false
},
{
"name": "update_time",
"type": "time.Time",
"nullable": false
},
{
"name": "name",
"type": "string",
"nullable": false
},
{
"name": "parent_kind",
"type": "string",
"nullable": false
},
{
"name": "parent_id",
"type": "string",
"nullable": false
},
{
"name": "secret",
"type": "string",
"nullable": false
},
{
"name": "visibility",
"type": "string",
"nullable": false
},
{
"name": "remote_repository_config_type",
"type": "string",
"nullable": false
},
{
"name": "remote_source_id",
"type": "string",
"nullable": false
},
{
"name": "linked_account_id",
"type": "string",
"nullable": false
},
{
"name": "repository_id",
"type": "string",
"nullable": false
},
{
"name": "repository_path",
"type": "string",
"nullable": false
},
{
"name": "ssh_private_key",
"type": "string",
"nullable": false
},
{
"name": "skip_ssh_host_key_check",
"type": "bool",
"nullable": false
},
{
"name": "webhook_secret",
"type": "string",
"nullable": false
},
{
"name": "pass_vars_to_forked_pr",
"type": "bool",
"nullable": false
},
{
"name": "default_branch",
"type": "string",
"nullable": false
},
{
"name": "members_can_perform_run_actions",
"type": "bool",
"nullable": false
}
]
},
{
"name": "secret",
"columns": [
{
"name": "id",
"type": "string",
"nullable": false
},
{
"name": "revision",
"type": "uint64",
"nullable": false
},
{
"name": "creation_time",
"type": "time.Time",
"nullable": false
},
{
"name": "update_time",
"type": "time.Time",
"nullable": false
},
{
"name": "name",
"type": "string",
"nullable": false
},
{
"name": "parent_kind",
"type": "string",
"nullable": false
},
{
"name": "parent_id",
"type": "string",
"nullable": false
},
{
"name": "type",
"type": "string",
"nullable": false
},
{
"name": "data",
"type": "json",
"nullable": false
},
{
"name": "secret_provider_id",
"type": "string",
"nullable": false
},
{
"name": "path",
"type": "string",
"nullable": false
}
]
},
{
"name": "variable",
"columns": [
{
"name": "id",
"type": "string",
"nullable": false
},
{
"name": "revision",
"type": "uint64",
"nullable": false
},
{
"name": "creation_time",
"type": "time.Time",
"nullable": false
},
{
"name": "update_time",
"type": "time.Time",
"nullable": false
},
{
"name": "name",
"type": "string",
"nullable": false
},
{
"name": "parent_kind",
"type": "string",
"nullable": false
},
{
"name": "parent_id",
"type": "string",
"nullable": false
},
{
"name": "variable_values",
"type": "json",
"nullable": false
}
]
},
{
"name": "orginvitation",
"columns": [
{
"name": "id",
"type": "string",
"nullable": false
},
{
"name": "revision",
"type": "uint64",
"nullable": false
},
{
"name": "creation_time",
"type": "time.Time",
"nullable": false
},
{
"name": "update_time",
"type": "time.Time",
"nullable": false
},
{
"name": "user_id",
"type": "string",
"nullable": false
},
{
"name": "organization_id",
"type": "string",
"nullable": false
},
{
"name": "role",
"type": "string",
"nullable": false
}
]
},
{
"name": "userprojectfavorite",
"columns": [
{
"name": "id",
"type": "string",
"nullable": false
},
{
"name": "revision",
"type": "uint64",
"nullable": false
},
{
"name": "creation_time",
"type": "time.Time",
"nullable": false
},
{
"name": "update_time",
"type": "time.Time",
"nullable": false
},
{
"name": "user_id",
"type": "string",
"nullable": false
},
{
"name": "project_id",
"type": "string",
"nullable": false
}
]
}
]
}

View File

@ -57,3 +57,7 @@
{"exportMeta":{"kind":"OrgInvitation"},"id":"ccfa97b7-f673-4437-9d5f-8fd11ec05c6f","creationTime":"2023-04-07T12:12:19.048529Z","updateTime":"2023-04-07T12:12:19.048529Z","organization_id":"15bfe438-9844-4024-b493-d137468bf6e9","user_id":"06c3b92a-f544-4eab-a254-a9d0465e16fc","role":"owner"}
{"exportMeta":{"kind":"LinkedAccount"},"id":"4037d8a4-78a2-41dc-8108-faa7f514b5e2","creationTime":"2023-04-07T12:12:19.048529Z","updateTime":"2023-04-07T12:12:19.048529Z","user_id":"06c3b92a-f544-4eab-a254-a9d0465e16fc","remote_user_id":"12345","remote_username":"remoteuser01","remote_source_id":"41e2edca-ed29-4bab-a552-e4720cc2aca9","oauth2_access_token":"accesstoken","oauth_2_access_token_expires_at":"0001-01-01T00:00:00Z"}
{"exportMeta":{"kind":"UserProjectFavorite"},"id":"6db79d0c-6f75-4212-ac28-9b96ac04db34","creationTime":"2023-04-07T12:12:19.048529Z","updateTime":"2023-04-07T12:12:19.048529Z","user_id":"06c3b92a-f544-4eab-a254-a9d0465e16fc","project_id":"a15977f1-2f25-4fb9-a94c-bdfe11cc7292"}
{"exportMeta":{"kind":"UserProjectFavorite"},"id":"72c862b4-4e0c-4877-a326-ff6b60815d32","creationTime":"2023-04-07T12:12:19.048529Z","updateTime":"2023-04-07T12:12:19.048529Z","user_id":"172f750c-0800-4fd1-9eaa-415935cfb7b0","project_id":"ac31830e-af56-4825-882e-a5dedf30ef96"}

View File

@ -0,0 +1,59 @@
{"table":"remotesource","values":{"id":"41e2edca-ed29-4bab-a552-e4720cc2aca9","creation_time":"2023-04-03T12:23:46.281047451Z","update_time":"2023-04-03T12:23:46.281047451Z","name":"rs01","apiurl":"http://example.com","type":"gitea","auth_type":"password"}}
{"table":"user_t","values":{"id":"06c3b92a-f544-4eab-a254-a9d0465e16fc","creation_time":"2023-04-03T12:23:46.281976152Z","update_time":"2023-04-03T12:23:46.281976152Z","name":"user4","secret":"91b63c16455434c6a902625f5729361dd6dbf3a4"}}
{"table":"user_t","values":{"id":"172f750c-0800-4fd1-9eaa-415935cfb7b0","creation_time":"2023-04-03T12:23:46.282401495Z","update_time":"2023-04-03T12:23:46.282401495Z","name":"user8","secret":"0184c3cae3ca9b2ab59cb40aa263d135c9f6c381"}}
{"table":"user_t","values":{"id":"240ba203-3e26-4451-9018-05c8fee5efc8","creation_time":"2023-04-03T12:23:46.282513244Z","update_time":"2023-04-03T12:23:46.282513244Z","name":"user9","secret":"800a7d79a041c55fa2e456b9d5ddb719fb4d49fa"}}
{"table":"user_t","values":{"id":"2a9afa25-f428-4fb7-8fa8-2b530b590ea9","creation_time":"2023-04-03T12:23:46.281399389Z","update_time":"2023-04-03T12:23:46.281399389Z","name":"user0","secret":"f6b12b3faad2e8a8894a45f1a49cea2a87560161"}}
{"table":"user_t","values":{"id":"31eb74d4-7bfd-4e28-8de2-a7b75d86b62d","creation_time":"2023-04-03T12:23:51.284329084Z","update_time":"2023-04-03T12:23:51.284329084Z","name":"user13","secret":"ecb7e25dd599cd263bac126999445c45015f1e79"}}
{"table":"user_t","values":{"id":"3664b856-f50f-4f66-bb0b-50446e5b6b7d","creation_time":"2023-04-03T12:23:51.285245283Z","update_time":"2023-04-03T12:23:51.285245283Z","name":"user01","secret":"5bb749a35684a7644d3b406672ea4890bee00a4b"}}
{"table":"user_t","values":{"id":"3d81312a-4f1c-4795-ab92-55305c6bab72","creation_time":"2023-04-03T12:23:46.281862238Z","update_time":"2023-04-03T12:23:46.281862238Z","name":"user3","secret":"56c45aee5776be4727df920bcb874380f7589282"}}
{"table":"user_t","values":{"id":"4b111e2e-aae2-4e74-88ae-0f0bd1b75798","creation_time":"2023-04-03T12:23:51.284008924Z","update_time":"2023-04-03T12:23:51.284008924Z","name":"user11","secret":"ddee8466e21e58b9a96e6e8c659d0fd35532cc8f"}}
{"table":"user_t","values":{"id":"5ad2244f-72b8-4b99-90cb-42e0f4906a82","creation_time":"2023-04-03T12:23:46.28206576Z","update_time":"2023-04-03T12:23:46.28206576Z","name":"user5","secret":"3c8671f4206cc744b28380648450c2d074dd114d"}}
{"table":"user_t","values":{"id":"6201f121-51b6-4631-bea5-da993c60627e","creation_time":"2023-04-03T12:23:51.28454406Z","update_time":"2023-04-03T12:23:51.28454406Z","name":"user15","secret":"97f1a1c719513072a2872e361a8dbcab4884e322"}}
{"table":"user_t","values":{"id":"6220c7c7-b668-46df-bf18-004640a52a71","creation_time":"2023-04-03T12:23:46.282245536Z","update_time":"2023-04-03T12:23:46.282245536Z","name":"user7","secret":"d4f16a8e328b1eae5dafd8a278bf5b14ef1ac308"}}
{"table":"user_t","values":{"id":"6a980aa7-7c5c-4274-85d6-06024ddc1bf0","creation_time":"2023-04-03T12:23:51.284652666Z","update_time":"2023-04-03T12:23:51.284652666Z","name":"user16","secret":"1706eb1507c631dbc08c072766e45a61b7d99d6f"}}
{"table":"user_t","values":{"id":"6c1bb669-f289-4406-b821-d2a908075c27","creation_time":"2023-04-03T12:23:46.281620372Z","update_time":"2023-04-03T12:23:46.281620372Z","name":"user1","secret":"9376cd24de3e8acf83cb53cff281c7ff57e7faf7"}}
{"table":"user_t","values":{"id":"7a19dfb9-023d-4fcb-8661-062c8a35e64e","creation_time":"2023-04-03T12:23:51.28444188Z","update_time":"2023-04-03T12:23:51.28444188Z","name":"user14","secret":"6c63f262db71c6c92c3ffe8a6c371da4d327741b"}}
{"table":"user_t","values":{"id":"9b259867-2676-432e-bdc1-d46314069767","creation_time":"2023-04-03T12:23:51.285007258Z","update_time":"2023-04-03T12:23:51.285007258Z","name":"user19","secret":"fa313dc618aea249cf34611526c46777a4926d22"}}
{"table":"user_t","values":{"id":"a1d93c42-566a-4f85-b3e9-7808d9c03a8c","creation_time":"2023-04-03T12:23:46.28215928Z","update_time":"2023-04-03T12:23:46.28215928Z","name":"user6","secret":"be3506a311f1b2ff45505b71352bb0ea3652ca83"}}
{"table":"user_t","values":{"id":"a1ddc940-0024-4fc6-aa7a-7039dd0219cb","creation_time":"2023-04-03T12:23:51.283685621Z","update_time":"2023-04-03T12:23:51.283685621Z","name":"user10","secret":"a8dfab34e973c9948cc55795eb6f615736e1a724"}}
{"table":"user_t","values":{"id":"a5a2935e-6a33-4cb9-99a4-b2924f42eefb","creation_time":"2023-04-03T12:23:46.281783595Z","update_time":"2023-04-03T12:23:46.281783595Z","name":"user2","secret":"851acfde65da1fc57b7d52befb26b2d646525571"}}
{"table":"user_t","values":{"id":"a6235238-e63e-4e0d-840c-8428a282c5db","creation_time":"2023-04-03T12:23:51.284905567Z","update_time":"2023-04-03T12:23:51.284905567Z","name":"user18","secret":"e912a8a18940147cf435a417f0cff073e1b9f907"}}
{"table":"user_t","values":{"id":"b6f7617a-a5d1-4a63-ad71-b980e82d3a0c","creation_time":"2023-04-03T12:23:51.284182623Z","update_time":"2023-04-03T12:23:51.284182623Z","name":"user12","secret":"75471711fa7214896fe8d3e69ca7f02ac539227a"}}
{"table":"user_t","values":{"id":"c9f68e97-15fb-4453-9673-8d1e4ba247b9","creation_time":"2023-04-03T12:23:51.284787253Z","update_time":"2023-04-03T12:23:51.284787253Z","name":"user17","secret":"e8336a917cd4353e9f5bab6e94e770e653d567fb"}}
{"table":"organization","values":{"id":"15bfe438-9844-4024-b493-d137468bf6e9","creation_time":"2023-04-03T12:23:51.285377984Z","update_time":"2023-04-03T12:23:51.285377984Z","name":"org01","visibility":"public"}}
{"table":"projectgroup","values":{"id":"0316f6cb-1215-4003-823f-4c33abf4f128","creation_time":"2023-04-03T12:23:51.285269658Z","update_time":"2023-04-03T12:23:51.285269658Z","parent_kind":"user","parent_id":"3664b856-f50f-4f66-bb0b-50446e5b6b7d","visibility":"public"}}
{"table":"projectgroup","values":{"id":"0988a136-74ac-4da9-be5f-67c7fac4013b","creation_time":"2023-04-03T12:23:51.284207906Z","update_time":"2023-04-03T12:23:51.284207906Z","parent_kind":"user","parent_id":"b6f7617a-a5d1-4a63-ad71-b980e82d3a0c","visibility":"public"}}
{"table":"projectgroup","values":{"id":"0cc9b923-ba9d-40d0-abca-0eb381eae08d","creation_time":"2023-04-03T12:23:51.28467285Z","update_time":"2023-04-03T12:23:51.28467285Z","parent_kind":"user","parent_id":"6a980aa7-7c5c-4274-85d6-06024ddc1bf0","visibility":"public"}}
{"table":"projectgroup","values":{"id":"0d3c9bc4-ea1d-4750-9c0a-be6e5a2521b7","creation_time":"2023-04-03T12:23:46.282530356Z","update_time":"2023-04-03T12:23:46.282530356Z","parent_kind":"user","parent_id":"240ba203-3e26-4451-9018-05c8fee5efc8","visibility":"public"}}
{"table":"projectgroup","values":{"id":"0d6efcb7-0ef4-4b3a-8815-72e3706bf7e5","creation_time":"2023-04-03T12:23:51.286201083Z","update_time":"2023-04-03T12:23:51.286201083Z","name":"projectgroup01","parent_kind":"projectgroup","parent_id":"c6a49dfa-dbfb-43e6-af72-d7d594ed6734","visibility":"public"}}
{"table":"projectgroup","values":{"id":"0f26f9cd-31ca-4301-b346-72b7901ecea6","creation_time":"2023-04-03T12:23:46.282420213Z","update_time":"2023-04-03T12:23:46.282420213Z","parent_kind":"user","parent_id":"172f750c-0800-4fd1-9eaa-415935cfb7b0","visibility":"public"}}
{"table":"projectgroup","values":{"id":"12ecac96-fd68-46e4-a458-e3c1acf3ae04","creation_time":"2023-04-03T12:23:46.28208378Z","update_time":"2023-04-03T12:23:46.28208378Z","parent_kind":"user","parent_id":"5ad2244f-72b8-4b99-90cb-42e0f4906a82","visibility":"public"}}
{"table":"projectgroup","values":{"id":"37795e36-163e-4368-9681-fc8b8d8caa3e","creation_time":"2023-04-03T12:23:51.285027862Z","update_time":"2023-04-03T12:23:51.285027862Z","parent_kind":"user","parent_id":"9b259867-2676-432e-bdc1-d46314069767","visibility":"public"}}
{"table":"projectgroup","values":{"id":"421cec99-5434-46da-9421-43bf1ad3e24d","creation_time":"2023-04-03T12:23:51.28403714Z","update_time":"2023-04-03T12:23:51.28403714Z","parent_kind":"user","parent_id":"4b111e2e-aae2-4e74-88ae-0f0bd1b75798","visibility":"public"}}
{"table":"projectgroup","values":{"id":"42f8fb71-56a1-4584-94d9-074a4730f295","creation_time":"2023-04-03T12:23:51.284560264Z","update_time":"2023-04-03T12:23:51.284560264Z","parent_kind":"user","parent_id":"6201f121-51b6-4631-bea5-da993c60627e","visibility":"public"}}
{"table":"projectgroup","values":{"id":"4f2568d5-7d78-4268-81a7-f49edef85fad","creation_time":"2023-04-03T12:23:51.285854313Z","update_time":"2023-04-03T12:23:51.285854313Z","name":"projectgroup01","parent_kind":"projectgroup","parent_id":"0316f6cb-1215-4003-823f-4c33abf4f128","visibility":"public"}}
{"table":"projectgroup","values":{"id":"54dac4ed-a596-447b-bd85-5c987d3878b6","creation_time":"2023-04-03T12:23:46.281893179Z","update_time":"2023-04-03T12:23:46.281893179Z","parent_kind":"user","parent_id":"3d81312a-4f1c-4795-ab92-55305c6bab72","visibility":"public"}}
{"table":"projectgroup","values":{"id":"6c4a38dd-13ef-4810-915b-f7584f5cc320","creation_time":"2023-04-03T12:23:46.28143899Z","update_time":"2023-04-03T12:23:46.28143899Z","parent_kind":"user","parent_id":"2a9afa25-f428-4fb7-8fa8-2b530b590ea9","visibility":"public"}}
{"table":"projectgroup","values":{"id":"6d91e71e-0dfd-4f87-a2aa-86d3abd84034","creation_time":"2023-04-03T12:23:51.284805971Z","update_time":"2023-04-03T12:23:51.284805971Z","parent_kind":"user","parent_id":"c9f68e97-15fb-4453-9673-8d1e4ba247b9","visibility":"public"}}
{"table":"projectgroup","values":{"id":"8b8f07d1-1078-4e3c-af4a-36f6cab55ab3","creation_time":"2023-04-03T12:23:46.281996826Z","update_time":"2023-04-03T12:23:46.281996826Z","parent_kind":"user","parent_id":"06c3b92a-f544-4eab-a254-a9d0465e16fc","visibility":"public"}}
{"table":"projectgroup","values":{"id":"8ce0fdc5-0356-4565-b721-9022c47999c0","creation_time":"2023-04-03T12:23:46.281662278Z","update_time":"2023-04-03T12:23:46.281662278Z","parent_kind":"user","parent_id":"6c1bb669-f289-4406-b821-d2a908075c27","visibility":"public"}}
{"table":"projectgroup","values":{"id":"911a177f-1f3e-4277-b2c4-3269906135cc","creation_time":"2023-04-03T12:23:51.284356322Z","update_time":"2023-04-03T12:23:51.284356322Z","parent_kind":"user","parent_id":"31eb74d4-7bfd-4e28-8de2-a7b75d86b62d","visibility":"public"}}
{"table":"projectgroup","values":{"id":"92689b70-bbf4-43f5-b481-e60a955fe934","creation_time":"2023-04-03T12:23:46.282262648Z","update_time":"2023-04-03T12:23:46.282262648Z","parent_kind":"user","parent_id":"6220c7c7-b668-46df-bf18-004640a52a71","visibility":"public"}}
{"table":"projectgroup","values":{"id":"a4a944f8-f43b-4ab9-a3c3-83d1e5d97eca","creation_time":"2023-04-03T12:23:51.284923237Z","update_time":"2023-04-03T12:23:51.284923237Z","parent_kind":"user","parent_id":"a6235238-e63e-4e0d-840c-8428a282c5db","visibility":"public"}}
{"table":"projectgroup","values":{"id":"c6a49dfa-dbfb-43e6-af72-d7d594ed6734","creation_time":"2023-04-03T12:23:51.285403617Z","update_time":"2023-04-03T12:23:51.285403617Z","parent_kind":"org","parent_id":"15bfe438-9844-4024-b493-d137468bf6e9","visibility":"public"}}
{"table":"projectgroup","values":{"id":"e3ce2f10-4766-49a4-ace4-9867014eb2f2","creation_time":"2023-04-03T12:23:46.282174436Z","update_time":"2023-04-03T12:23:46.282174436Z","parent_kind":"user","parent_id":"a1d93c42-566a-4f85-b3e9-7808d9c03a8c","visibility":"public"}}
{"table":"projectgroup","values":{"id":"e76c2e8d-b33c-49ab-8c7b-efe401693f6e","creation_time":"2023-04-03T12:23:51.283740308Z","update_time":"2023-04-03T12:23:51.283740308Z","parent_kind":"user","parent_id":"a1ddc940-0024-4fc6-aa7a-7039dd0219cb","visibility":"public"}}
{"table":"projectgroup","values":{"id":"f0c12a1c-ffca-446d-b35f-4e1c650bf3e5","creation_time":"2023-04-03T12:23:51.284460109Z","update_time":"2023-04-03T12:23:51.284460109Z","parent_kind":"user","parent_id":"7a19dfb9-023d-4fcb-8661-062c8a35e64e","visibility":"public"}}
{"table":"projectgroup","values":{"id":"f7b239bf-2a75-464e-8a47-340299bbbbc2","creation_time":"2023-04-03T12:23:46.28179924Z","update_time":"2023-04-03T12:23:46.28179924Z","parent_kind":"user","parent_id":"a5a2935e-6a33-4cb9-99a4-b2924f42eefb","visibility":"public"}}
{"table":"project","values":{"id":"a15977f1-2f25-4fb9-a94c-bdfe11cc7292","creation_time":"2023-04-03T12:23:51.285619501Z","update_time":"2023-04-03T12:23:51.285619501Z","name":"project01","parent_kind":"projectgroup","parent_id":"0316f6cb-1215-4003-823f-4c33abf4f128","secret":"1de077c9d0a18ea0543aa58c7bc44646c4a62349","visibility":"public","remote_repository_config_type":"manual","webhook_secret":"df258d355846073b83754824c5b4142155b5ef28","members_can_perform_run_actions":false}}
{"table":"project","values":{"id":"ac31830e-af56-4825-882e-a5dedf30ef96","creation_time":"2023-04-03T12:23:51.286053365Z","update_time":"2023-04-03T12:23:51.286053365Z","name":"project01","parent_kind":"projectgroup","parent_id":"4f2568d5-7d78-4268-81a7-f49edef85fad","secret":"338046e8570ba381cd54ef3089f484bc28c52fed","visibility":"public","remote_repository_config_type":"manual","webhook_secret":"d364a30958a3319ea21cc153ed529d1a77cd6411","members_can_perform_run_actions":false}}
{"table":"secret","values":{"id":"7489c8d6-a91e-4f7e-97f0-add1d81671a3","creation_time":"2023-04-03T12:23:51.286411031Z","update_time":"2023-04-03T12:23:51.286411031Z","name":"secret01","parent_kind":"project","parent_id":"ac31830e-af56-4825-882e-a5dedf30ef96","type":"internal","data":{"secret01":"secretvar01"}}}
{"table":"variable","values":{"id":"8faedc8f-9b3c-4403-9b5c-f20193a33817","creation_time":"2023-04-03T12:23:51.287368857Z","update_time":"2023-04-03T12:23:51.287368857Z","name":"variable01","parent_kind":"projectgroup","parent_id":"4f2568d5-7d78-4268-81a7-f49edef85fad","variable_values":[{"secret_name":"secret01","secret_var":"secretvar01"}]}}
{"table":"usertoken","values":{"id":"380b36a3-c860-4540-89b1-99a0708eac58","creation_time":"2023-04-07T12:12:19.048529Z","update_time":"2023-04-07T12:12:19.048529Z","name":"default","value":"tokenvalue","user_id":"06c3b92a-f544-4eab-a254-a9d0465e16fc"}}
{"table":"orgmember","values":{"id":"8749225d-5356-4c15-a14a-986a21e06498","creation_time":"2023-04-07T12:12:19.048529Z","update_time":"2023-04-07T12:12:19.048529Z","organization_id":"15bfe438-9844-4024-b493-d137468bf6e9","user_id":"06c3b92a-f544-4eab-a254-a9d0465e16fc","member_role":"owner"}}
{"table":"orginvitation","values":{"id":"ccfa97b7-f673-4437-9d5f-8fd11ec05c6f","creation_time":"2023-04-07T12:12:19.048529Z","update_time":"2023-04-07T12:12:19.048529Z","organization_id":"15bfe438-9844-4024-b493-d137468bf6e9","user_id":"06c3b92a-f544-4eab-a254-a9d0465e16fc","role":"owner"}}
{"table":"linkedaccount","values":{"id":"4037d8a4-78a2-41dc-8108-faa7f514b5e2","creation_time":"2023-04-07T12:12:19.048529Z","update_time":"2023-04-07T12:12:19.048529Z","user_id":"06c3b92a-f544-4eab-a254-a9d0465e16fc","remote_user_id":"12345","remote_user_name":"remoteuser01","remote_source_id":"41e2edca-ed29-4bab-a552-e4720cc2aca9","oauth2_access_token":"accesstoken","oauth2_access_token_expires_at":"0001-01-01T00:00:00Z"}}

View File

@ -36,6 +36,7 @@ var importFixtures = testutil.DataFixtures{
1: "dbv1.jsonc",
2: "dbv2.jsonc",
3: "dbv3.jsonc",
4: "dbv4.jsonc",
}
func TestCreate(t *testing.T) {

View File

@ -0,0 +1,119 @@
// Copyright 2019 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 action
import (
"context"
"github.com/sorintlab/errors"
"agola.io/agola/internal/services/gateway/common"
"agola.io/agola/internal/util"
csapitypes "agola.io/agola/services/configstore/api/types"
"agola.io/agola/services/configstore/client"
cstypes "agola.io/agola/services/configstore/types"
)
type CreateUserProjectFavoriteRequest struct {
ProjectRef string
}
func (h *ActionHandler) CreateUserProjectFavorite(ctx context.Context, req *CreateUserProjectFavoriteRequest) (*cstypes.UserProjectFavorite, error) {
if !common.IsUserLogged(ctx) {
return nil, errors.Errorf("user not logged in")
}
userID := common.CurrentUserID(ctx)
creq := &csapitypes.CreateUserProjectFavoriteRequest{
UserRef: userID,
ProjectRef: req.ProjectRef,
}
userProjectFavorite, _, err := h.configstoreClient.CreateUserProjectFavorite(ctx, creq)
if err != nil {
return nil, util.NewAPIError(util.KindFromRemoteError(err), errors.Wrapf(err, "failed to create user project favorite"))
}
return userProjectFavorite, nil
}
func (h *ActionHandler) DeleteUserProjectFavorite(ctx context.Context, projectRef string) error {
if !common.IsUserLogged(ctx) {
return errors.Errorf("user not logged in")
}
userID := common.CurrentUserID(ctx)
if _, err := h.configstoreClient.DeleteUserProjectFavorite(ctx, userID, projectRef); err != nil {
return util.NewAPIError(util.KindFromRemoteError(err), errors.Wrapf(err, "failed to delete user project favorite"))
}
return nil
}
type GetUserProjectFavoritesRequest struct {
Cursor string
Limit int
SortDirection SortDirection
}
type GetUserProjectFavoritesResponse struct {
UserProjectFavorites []*cstypes.UserProjectFavorite
Cursor string
}
func (h *ActionHandler) GetUserProjectFavorites(ctx context.Context, req *GetUserProjectFavoritesRequest) (*GetUserProjectFavoritesResponse, error) {
if !common.IsUserLogged(ctx) {
return nil, errors.Errorf("user not logged in")
}
userID := common.CurrentUserID(ctx)
inCursor := &StartCursor{}
sortDirection := req.SortDirection
if req.Cursor != "" {
if err := UnmarshalCursor(req.Cursor, inCursor); err != nil {
return nil, errors.WithStack(err)
}
sortDirection = inCursor.SortDirection
}
if sortDirection == "" {
sortDirection = SortDirectionAsc
}
userProjectFavorites, resp, err := h.configstoreClient.GetUserProjectFavorites(ctx, userID, &client.GetUserProjectFavoritesOptions{ListOptions: &client.ListOptions{Limit: req.Limit, SortDirection: cstypes.SortDirection(sortDirection)}, StartUserProjectFavoriteID: inCursor.Start})
if err != nil {
return nil, util.NewAPIError(util.KindFromRemoteError(err), err)
}
var outCursor string
if resp.HasMore && len(userProjectFavorites) > 0 {
lastuserProjectFavoriteID := userProjectFavorites[len(userProjectFavorites)-1].ID
outCursor, err = MarshalCursor(&StartCursor{
Start: lastuserProjectFavoriteID,
SortDirection: sortDirection,
})
if err != nil {
return nil, errors.WithStack(err)
}
}
res := &GetUserProjectFavoritesResponse{
UserProjectFavorites: userProjectFavorites,
Cursor: outCursor,
}
return res, nil
}

View File

@ -0,0 +1,136 @@
// 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"
"github.com/gorilla/mux"
"github.com/rs/zerolog"
"github.com/sorintlab/errors"
"agola.io/agola/internal/services/gateway/action"
util "agola.io/agola/internal/util"
cstypes "agola.io/agola/services/configstore/types"
gwapitypes "agola.io/agola/services/gateway/api/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)
projectRef := vars["projectref"]
creq := &action.CreateUserProjectFavoriteRequest{
ProjectRef: projectRef,
}
userProjectFavorite, err := h.ah.CreateUserProjectFavorite(ctx, creq)
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)
projectRef := vars["projectref"]
err := h.ah.DeleteUserProjectFavorite(ctx, projectRef)
if util.HTTPError(w, err) {
h.log.Err(err).Send()
return
}
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) ([]*gwapitypes.UserProjectFavoriteResponse, error) {
ctx := r.Context()
ropts, err := parseRequestOptions(r)
if err != nil {
return nil, errors.WithStack(err)
}
ares, err := h.ah.GetUserProjectFavorites(ctx, &action.GetUserProjectFavoritesRequest{Cursor: ropts.Cursor, Limit: ropts.Limit, SortDirection: action.SortDirection(ropts.SortDirection)})
if err != nil {
return nil, errors.WithStack(err)
}
userProjectFavorites := make([]*gwapitypes.UserProjectFavoriteResponse, len(ares.UserProjectFavorites))
for i, p := range ares.UserProjectFavorites {
userProjectFavorites[i] = createUserProjectFavoriteResponse(p)
}
addCursorHeader(w, ares.Cursor)
return userProjectFavorites, nil
}
func createUserProjectFavoriteResponse(o *cstypes.UserProjectFavorite) *gwapitypes.UserProjectFavoriteResponse {
org := &gwapitypes.UserProjectFavoriteResponse{
ID: o.ID,
ProjectID: o.ProjectID,
}
return org
}

View File

@ -265,6 +265,10 @@ func (g *Gateway) Run(ctx context.Context) error {
userRunLogsHandler := api.NewLogsHandler(g.log, g.ah, scommon.GroupTypeUser)
userRunLogsDeleteHandler := api.NewLogsDeleteHandler(g.log, g.ah, scommon.GroupTypeUser)
createUserProjectFavoriteHandler := api.NewCreateUserProjectFavoriteHandler(g.log, g.ah)
deleteUserProjectFavoriteHandler := api.NewDeleteUserProjectFavoriteHandler(g.log, g.ah)
userProjectFavoritesHandler := api.NewUserProjectFavoritesHandler(g.log, g.ah)
userRemoteReposHandler := api.NewUserRemoteReposHandler(g.log, g.ah, g.configstoreClient)
badgeHandler := api.NewBadgeHandler(g.log, g.ah)
@ -354,6 +358,9 @@ func (g *Gateway) Run(ctx context.Context) error {
apirouter.Handle("/user/orgs", authForcedHandler(userOrgsHandler)).Methods("GET")
apirouter.Handle("/user/org_invitations", authForcedHandler(userOrgInvitationsHandler)).Methods("GET")
apirouter.Handle("/user/org_invitations/{orgref}/actions", authForcedHandler(userOrgInvitationActionHandler)).Methods("PUT")
apirouter.Handle("/user/projects/{projectref}/projectfavorites", authForcedHandler(createUserProjectFavoriteHandler)).Methods("POST")
apirouter.Handle("/user/projects/{projectref}/projectfavorites", authForcedHandler(deleteUserProjectFavoriteHandler)).Methods("DELETE")
apirouter.Handle("/user/projectfavorites", authForcedHandler(userProjectFavoritesHandler)).Methods("GET")
apirouter.Handle("/users/{userref}/runs", authForcedHandler(userRunsHandler)).Methods("GET")
apirouter.Handle("/users/{userref}/runs/{runnumber}", authOptionalHandler(userRunHandler)).Methods("GET")

View File

@ -0,0 +1,20 @@
// 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 types
type CreateUserProjectFavoriteRequest struct {
UserRef string
ProjectRef string
}

View File

@ -778,3 +778,42 @@ func (c *Client) Import(ctx context.Context, r io.Reader) (*Response, error) {
resp, err := c.GetResponse(ctx, "POST", "/import", nil, -1, common.JSONContent, r)
return resp, errors.WithStack(err)
}
func (c *Client) CreateUserProjectFavorite(ctx context.Context, req *csapitypes.CreateUserProjectFavoriteRequest) (*cstypes.UserProjectFavorite, *Response, error) {
reqj, err := json.Marshal(req)
if err != nil {
return nil, nil, errors.WithStack(err)
}
userProjectFavorite := new(cstypes.UserProjectFavorite)
resp, err := c.GetParsedResponse(ctx, "POST", fmt.Sprintf("/users/%s/projects/%s/projectfavorites", req.UserRef, req.ProjectRef), nil, common.JSONContent, bytes.NewReader(reqj), userProjectFavorite)
return userProjectFavorite, resp, errors.WithStack(err)
}
func (c *Client) DeleteUserProjectFavorite(ctx context.Context, userRef string, projectRef string) (*Response, error) {
resp, err := c.GetResponse(ctx, "DELETE", fmt.Sprintf("/users/%s/projects/%s/projectfavorites", userRef, projectRef), nil, -1, common.JSONContent, nil)
return resp, errors.WithStack(err)
}
type GetUserProjectFavoritesOptions struct {
*ListOptions
StartUserProjectFavoriteID string
}
func (o *GetUserProjectFavoritesOptions) Add(q url.Values) {
o.ListOptions.Add(q)
if o.StartUserProjectFavoriteID != "" {
q.Add("startuserprojectfavoriteid", o.StartUserProjectFavoriteID)
}
}
func (c *Client) GetUserProjectFavorites(ctx context.Context, userRef string, opts *GetUserProjectFavoritesOptions) ([]*cstypes.UserProjectFavorite, *Response, error) {
q := url.Values{}
opts.Add(q)
userProjectFavorites := []*cstypes.UserProjectFavorite{}
resp, err := c.GetParsedResponse(ctx, "GET", fmt.Sprintf("/users/%s/projectfavorites", userRef), q, common.JSONContent, nil, &userProjectFavorites)
return userProjectFavorites, resp, errors.WithStack(err)
}

View File

@ -0,0 +1,34 @@
// 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 types
import (
"agola.io/agola/internal/sqlg"
"agola.io/agola/internal/sqlg/sql"
)
type UserProjectFavorite struct {
sqlg.ObjectMeta
UserID string `json:"user_id,omitempty"`
ProjectID string `json:"project_id,omitempty"`
}
func NewUserProjectFavorite(tx *sql.Tx) *UserProjectFavorite {
return &UserProjectFavorite{
ObjectMeta: sqlg.NewObjectMeta(tx),
}
}

View File

@ -0,0 +1,10 @@
package types
type UserProjectFavoriteResponse struct {
ID string `json:"id"`
ProjectID string `json:"project_id"`
}
type CreateUserProjectFavoriteRequest struct {
ProjectRef string
}

View File

@ -871,3 +871,27 @@ func (c *Client) GetProjectCommitStatusDeliveries(ctx context.Context, projectRe
func (c *Client) ProjectCommitStatusRedelivery(ctx context.Context, projectRef string, commitStatusDeliveryID string) (*Response, error) {
return c.getResponse(ctx, "PUT", fmt.Sprintf("/projects/%s/commitstatusdeliveries/%s/redelivery", url.PathEscape(projectRef), commitStatusDeliveryID), nil, jsonContent, nil)
}
func (c *Client) GetUserProjectFavorites(ctx context.Context, opts *ListOptions) ([]*gwapitypes.UserProjectFavoriteResponse, *Response, error) {
q := url.Values{}
opts.Add(q)
projectFavorites := []*gwapitypes.UserProjectFavoriteResponse{}
resp, err := c.getParsedResponse(ctx, "GET", "/user/projectfavorites", q, jsonContent, nil, &projectFavorites)
return projectFavorites, resp, errors.WithStack(err)
}
func (c *Client) CreateUserProjectFavorite(ctx context.Context, req *gwapitypes.CreateUserProjectFavoriteRequest) (*gwapitypes.UserProjectFavoriteResponse, *Response, error) {
reqj, err := json.Marshal(req)
if err != nil {
return nil, nil, errors.WithStack(err)
}
projectFavorite := new(gwapitypes.UserProjectFavoriteResponse)
resp, err := c.getParsedResponse(ctx, "POST", fmt.Sprintf("/user/projects/%s/projectfavorites", req.ProjectRef), nil, jsonContent, bytes.NewReader(reqj), projectFavorite)
return projectFavorite, resp, errors.WithStack(err)
}
func (c *Client) DeleteUserProjectFavorite(ctx context.Context, projectRef string) (*Response, error) {
return c.getResponse(ctx, "DELETE", fmt.Sprintf("/user/projects/%s/projectfavorites", projectRef), nil, jsonContent, nil)
}

View File

@ -9,6 +9,7 @@ import (
"path"
"path/filepath"
"slices"
"sort"
"strconv"
"testing"
"time"
@ -3585,3 +3586,327 @@ func testGetGroupRuns(t *testing.T, userRun bool) {
})
}
}
type userProjectFavoritesByID []*gwapitypes.UserProjectFavoriteResponse
func (p userProjectFavoritesByID) Len() int { return len(p) }
func (p userProjectFavoritesByID) Less(i, j int) bool {
return p[i].ID < p[j].ID
}
func (p userProjectFavoritesByID) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func TestUserProjectFavorite(t *testing.T) {
t.Parallel()
type testUserProjectFavoriteConfig struct {
sc *setupContext
tokenUser01 string
tokenUser02 string
gwAdminClient *gwclient.Client
gwClientUser01 *gwclient.Client
gwClientUser02 *gwclient.Client
projects []*gwapitypes.ProjectResponse
}
tests := []struct {
name string
f func(ctx context.Context, t *testing.T, tc *testUserProjectFavoriteConfig)
}{
{
name: "create user project favorite",
f: func(ctx context.Context, t *testing.T, tc *testUserProjectFavoriteConfig) {
var expectedUser01ProjectFavorites []*gwapitypes.UserProjectFavoriteResponse
var expectedUser02ProjectFavorites []*gwapitypes.UserProjectFavoriteResponse
for i := 0; i < 2; i++ {
projectFavorite, _, err := tc.gwClientUser01.CreateUserProjectFavorite(ctx, &gwapitypes.CreateUserProjectFavoriteRequest{ProjectRef: tc.projects[i].ID})
testutil.NilError(t, err)
expectedUser01ProjectFavorites = append(expectedUser01ProjectFavorites, projectFavorite)
}
sort.Sort(userProjectFavoritesByID(expectedUser01ProjectFavorites))
for i := 2; i < 4; i++ {
userProjectFavorite, _, err := tc.gwClientUser02.CreateUserProjectFavorite(ctx, &gwapitypes.CreateUserProjectFavoriteRequest{ProjectRef: tc.projects[i].ID})
testutil.NilError(t, err)
expectedUser02ProjectFavorites = append(expectedUser02ProjectFavorites, userProjectFavorite)
}
sort.Sort(userProjectFavoritesByID(expectedUser02ProjectFavorites))
userProjectFavorites, _, err := tc.gwClientUser01.GetUserProjectFavorites(ctx, nil)
testutil.NilError(t, err)
assert.DeepEqual(t, userProjectFavorites, expectedUser01ProjectFavorites)
userProjectFavorites, _, err = tc.gwClientUser02.GetUserProjectFavorites(ctx, nil)
testutil.NilError(t, err)
assert.DeepEqual(t, userProjectFavorites, expectedUser02ProjectFavorites)
},
},
{
name: "test user project favorite creation with already existing project favorite",
f: func(ctx context.Context, t *testing.T, tc *testUserProjectFavoriteConfig) {
userProjectFavorite, _, err := tc.gwClientUser01.CreateUserProjectFavorite(ctx, &gwapitypes.CreateUserProjectFavoriteRequest{ProjectRef: tc.projects[0].ID})
testutil.NilError(t, err)
_, _, err = tc.gwClientUser01.CreateUserProjectFavorite(ctx, &gwapitypes.CreateUserProjectFavoriteRequest{ProjectRef: tc.projects[0].ID})
expectedErr := remoteErrorBadRequest
assert.Error(t, err, expectedErr)
userProjectFavorites, _, err := tc.gwClientUser01.GetUserProjectFavorites(ctx, nil)
testutil.NilError(t, err)
assert.Assert(t, cmp.Equal(len(userProjectFavorites), 1))
assert.DeepEqual(t, userProjectFavorites[0], userProjectFavorite)
},
},
{
name: "test user project favorite creation with not existing project",
f: func(ctx context.Context, t *testing.T, tc *testUserProjectFavoriteConfig) {
_, _, err := tc.gwClientUser01.CreateUserProjectFavorite(ctx, &gwapitypes.CreateUserProjectFavoriteRequest{ProjectRef: "projecttest"})
expectedErr := remoteErrorBadRequest
assert.Error(t, err, expectedErr)
userProjectFavorites, _, err := tc.gwClientUser01.GetUserProjectFavorites(ctx, nil)
testutil.NilError(t, err)
assert.Assert(t, cmp.Equal(len(userProjectFavorites), 0))
},
},
{
name: "test user project favorite deletion",
f: func(ctx context.Context, t *testing.T, tc *testUserProjectFavoriteConfig) {
_, _, err := tc.gwClientUser01.CreateUserProjectFavorite(ctx, &gwapitypes.CreateUserProjectFavoriteRequest{ProjectRef: tc.projects[0].ID})
testutil.NilError(t, err)
_, err = tc.gwClientUser01.DeleteUserProjectFavorite(ctx, tc.projects[0].ID)
testutil.NilError(t, err)
userProjectFavorites, _, err := tc.gwClientUser01.GetUserProjectFavorites(ctx, nil)
testutil.NilError(t, err)
assert.Assert(t, cmp.Equal(len(userProjectFavorites), 0))
},
},
{
name: "test user project favorite deletion with not existing project favorite",
f: func(ctx context.Context, t *testing.T, tc *testUserProjectFavoriteConfig) {
_, err := tc.gwClientUser01.DeleteUserProjectFavorite(ctx, tc.projects[0].ID)
expectedErr := remoteErrorBadRequest
assert.Error(t, err, expectedErr)
},
},
{
name: "test user project deletion with existing project favorite",
f: func(ctx context.Context, t *testing.T, tc *testUserProjectFavoriteConfig) {
_, _, err := tc.gwClientUser01.CreateUserProjectFavorite(ctx, &gwapitypes.CreateUserProjectFavoriteRequest{ProjectRef: tc.projects[0].ID})
testutil.NilError(t, err)
_, err = tc.gwClientUser01.DeleteProject(ctx, tc.projects[0].ID)
testutil.NilError(t, err)
userProjectFavorites, _, err := tc.gwClientUser01.GetUserProjectFavorites(ctx, nil)
testutil.NilError(t, err)
assert.Assert(t, cmp.Equal(len(userProjectFavorites), 0))
},
},
{
name: "test get user project preferites with admin user",
f: func(ctx context.Context, t *testing.T, tc *testUserProjectFavoriteConfig) {
_, _, err := tc.gwAdminClient.GetUserProjectFavorites(ctx, nil)
expectedErr := remoteErrorInternal
assert.Error(t, err, expectedErr)
},
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
dir := t.TempDir()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sc := setup(ctx, t, dir, withGitea(true))
defer sc.stop()
gwAdminClient := gwclient.NewClient(sc.config.Gateway.APIExposedURL, sc.config.Gateway.AdminToken)
giteaToken, tokenUser01 := createLinkedAccount(ctx, t, sc.gitea, sc.config)
gwClientUser01 := gwclient.NewClient(sc.config.Gateway.APIExposedURL, tokenUser01)
_, _, err := gwAdminClient.CreateUser(ctx, &gwapitypes.CreateUserRequest{UserName: agolaUser02})
testutil.NilError(t, err)
tokenUser02, _, err := gwAdminClient.CreateUserToken(ctx, agolaUser02, &gwapitypes.CreateUserTokenRequest{TokenName: "test"})
testutil.NilError(t, err)
gwClientUser02 := gwclient.NewClient(sc.config.Gateway.APIExposedURL, tokenUser02.Token)
_, _, err = gwClientUser01.CreateOrg(ctx, &gwapitypes.CreateOrgRequest{Name: agolaOrg01, Visibility: gwapitypes.VisibilityPublic})
testutil.NilError(t, err)
giteaAPIURL := fmt.Sprintf("http://%s:%s", sc.gitea.HTTPListenAddress, sc.gitea.HTTPPort)
giteaClient, err := gitea.NewClient(giteaAPIURL, gitea.SetToken(giteaToken))
testutil.NilError(t, err)
var projects []*gwapitypes.ProjectResponse
_, project := createProject(ctx, t, giteaClient, gwClientUser01, withVisibility(gwapitypes.VisibilityPrivate))
projects = append(projects, project)
for i := 2; i < 5; i++ {
project, _, err = gwClientUser01.CreateProject(ctx, &gwapitypes.CreateProjectRequest{
Name: fmt.Sprintf("project0%d", i),
ParentRef: path.Join("org", agolaOrg01),
RemoteSourceName: "gitea",
RepoPath: path.Join(giteaUser01, "repo01"),
Visibility: gwapitypes.VisibilityPublic,
})
testutil.NilError(t, err)
projects = append(projects, project)
}
tc := &testUserProjectFavoriteConfig{
sc: sc,
tokenUser01: tokenUser01,
tokenUser02: tokenUser02.Token,
gwClientUser01: gwClientUser01,
gwClientUser02: gwClientUser02,
gwAdminClient: gwAdminClient,
projects: projects,
}
tt.f(ctx, t, tc)
})
}
}
func TestGetUserProjectFavorites(t *testing.T) {
t.Parallel()
dir := t.TempDir()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sc := setup(ctx, t, dir, withGitea(true))
defer sc.stop()
giteaToken, tokenUser01 := createLinkedAccount(ctx, t, sc.gitea, sc.config)
gwClientUser01 := gwclient.NewClient(sc.config.Gateway.APIExposedURL, tokenUser01)
giteaAPIURL := fmt.Sprintf("http://%s:%s", sc.gitea.HTTPListenAddress, sc.gitea.HTTPPort)
giteaClient, err := gitea.NewClient(giteaAPIURL, gitea.SetToken(giteaToken))
testutil.NilError(t, err)
allUserProjectFavorites := []*gwapitypes.UserProjectFavoriteResponse{}
_, project := createProject(ctx, t, giteaClient, gwClientUser01, withVisibility(gwapitypes.VisibilityPrivate))
userProjectFavorite, _, err := gwClientUser01.CreateUserProjectFavorite(ctx, &gwapitypes.CreateUserProjectFavoriteRequest{ProjectRef: project.ID})
testutil.NilError(t, err)
allUserProjectFavorites = append(allUserProjectFavorites, userProjectFavorite)
for i := 2; i < 10; i++ {
project, _, err := gwClientUser01.CreateProject(ctx, &gwapitypes.CreateProjectRequest{
Name: fmt.Sprintf("project0%d", i),
ParentRef: path.Join("user", agolaUser01),
RemoteSourceName: "gitea",
RepoPath: path.Join(giteaUser01, "repo01"),
Visibility: gwapitypes.VisibilityPublic,
})
testutil.NilError(t, err)
userProjectFavorite, _, err := gwClientUser01.CreateUserProjectFavorite(ctx, &gwapitypes.CreateUserProjectFavoriteRequest{ProjectRef: project.ID})
testutil.NilError(t, err)
allUserProjectFavorites = append(allUserProjectFavorites, userProjectFavorite)
}
sort.Sort(userProjectFavoritesByID(allUserProjectFavorites))
tests := []struct {
name string
limit int
sortDirection gwapitypes.SortDirection
expectedCallsNumber int
}{
{
name: "get user project stars with limit = 0, no sortdirection",
expectedCallsNumber: 1,
},
{
name: "get user project stars with limit = 0",
sortDirection: gwapitypes.SortDirectionAsc,
expectedCallsNumber: 1,
},
{
name: "get user project stars with limit less than results length",
limit: 2,
sortDirection: gwapitypes.SortDirectionAsc,
expectedCallsNumber: 5,
},
{
name: "get user project stars with limit greater than results length",
limit: MaxLimit,
sortDirection: gwapitypes.SortDirectionAsc,
expectedCallsNumber: 1,
},
{
name: "get user project stars with limit = 0, sortDirection desc",
sortDirection: gwapitypes.SortDirectionDesc,
expectedCallsNumber: 1,
},
{
name: "get user project stars with limit less than results length, sortDirection desc",
limit: 2,
sortDirection: gwapitypes.SortDirectionDesc,
expectedCallsNumber: 5,
},
{
name: "get user project stars with limit greater than results length, sortDirection desc",
limit: MaxLimit,
sortDirection: gwapitypes.SortDirectionDesc,
expectedCallsNumber: 1,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
var expectedUserProjectFavorites []*gwapitypes.UserProjectFavoriteResponse
expectedUserProjectFavorites = append(expectedUserProjectFavorites, allUserProjectFavorites...)
// default sortdirection is asc
// reverse if sortDirection is desc
if tt.sortDirection == gwapitypes.SortDirectionDesc {
slices.Reverse(expectedUserProjectFavorites)
}
respAllUserProjectFavorites := []*gwapitypes.UserProjectFavoriteResponse{}
sortDirection := tt.sortDirection
callsNumber := 0
var cursor string
for {
respUserProjectFavorites, res, err := gwClientUser01.GetUserProjectFavorites(ctx, &gwclient.ListOptions{Cursor: cursor, Limit: tt.limit, SortDirection: sortDirection})
testutil.NilError(t, err)
callsNumber++
respAllUserProjectFavorites = append(respAllUserProjectFavorites, respUserProjectFavorites...)
if res.Cursor == "" {
break
}
cursor = res.Cursor
sortDirection = ""
}
assert.DeepEqual(t, expectedUserProjectFavorites, respAllUserProjectFavorites)
assert.Assert(t, cmp.Equal(callsNumber, tt.expectedCallsNumber))
})
}
}