Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
init commit
  • Loading branch information
ecrupper committed Nov 10, 2025
commit edfc34c9384f5c64bd0c6e4956464cdf8b176de7
78 changes: 78 additions & 0 deletions api/types/favorite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: Apache-2.0

package types

import (
"fmt"
)

// Favorite is the API representation of a user's favorite.
//
// swagger:model Favorite
type Favorite struct {
Position *int64 `json:"position,omitempty"`
Repo *string `json:"repo,omitempty"`
}

// GetPosition returns the Position field.
//
// When the provided Favorite type is nil, or the field within
// the type is nil, it returns the zero value for the field.
func (f *Favorite) GetPosition() int64 {
// return zero value if Favorite type or Position field is nil
if f == nil || f.Position == nil {
return 0
}

return *f.Position
}

// GetRepo returns the Repo field.
//
// When the provided Favorite type is nil, or the field within
// the type is nil, it returns the zero value for the field.
func (f *Favorite) GetRepo() string {
// return zero value if Favorite type or Repo field is nil
if f == nil || f.Repo == nil {
return ""
}

return *f.Repo
}

// SetPosition sets the Position field.
//
// When the provided Favorite type is nil, it
// will set nothing and immediately return.
func (f *Favorite) SetPosition(v int64) {
// return if Favorite type is nil
if f == nil {
return
}

f.Position = &v
}

// SetRepo sets the Repo field.
//
// When the provided Favorite type is nil, it
// will set nothing and immediately return.
func (f *Favorite) SetRepo(v string) {
// return if Favorite type is nil
if f == nil {
return
}

f.Repo = &v
}

// String implements the Stringer interface for the Favorite type.
func (f *Favorite) String() string {
return fmt.Sprintf(`{
Position: %d,
Repo: %s,
}`,
f.GetPosition(),
f.GetRepo(),
)
}
Empty file added api/user/add_favorite.go
Empty file.
54 changes: 54 additions & 0 deletions api/user/list_favorites.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// SPDX-License-Identifier: Apache-2.0

package user

import (
"fmt"
"net/http"

"github.com/gin-gonic/gin"

"github.com/go-vela/server/database"
"github.com/go-vela/server/router/middleware/user"
"github.com/go-vela/server/util"
)

// swagger:operation GET /api/v1/user/favorites users GetUserFavorites
//
// Get the current authenticated user's favorites
//
// ---
// produces:
// - application/json
// security:
// - ApiKeyAuth: []
// responses:
// '200':
// description: Successfully retrieved the current user's favorites
// schema:
// type: array
// items:
// "$ref": "#/definitions/Favorite"
// '401':
// description: Unauthorized
// schema:
// "$ref": "#/definitions/Error"

// GetUserFavorites represents the API handler to capture the
// currently authenticated user's favorites.
func GetUserFavorites(c *gin.Context) {
// capture middleware values
u := user.Retrieve(c)
ctx := c.Request.Context()

favorites, err := database.FromContext(c).ListUserFavorites(ctx, u)
if err != nil {
retErr := fmt.Errorf("unable to get favorites for user %s: %w", u.GetName(), err)

util.HandleError(ctx, http.StatusInternalServerError, retErr)

return
}

c.JSON(http.StatusOK, favorites)
}
61 changes: 61 additions & 0 deletions api/user/save_favorites.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: Apache-2.0

package user

import (
"net/http"

"github.com/gin-gonic/gin"

"github.com/go-vela/server/api/types"
"github.com/go-vela/server/database"
"github.com/go-vela/server/router/middleware/user"
"github.com/go-vela/server/util"
)

// swagger:operation PUT /api/v1/user/favorites users SaveUserFavorites
//
// Save the current authenticated user's favorites
//
// ---
// produces:
// - application/json
// security:
// - ApiKeyAuth: []
// responses:
// '204':
// description: Successfully saved the current user's favorites
// '401':
// description: Unauthorized
// schema:
// "$ref": "#/definitions/Error"

// SaveUserFavorites represents the API handler to save the
// currently authenticated user's favorites.
func SaveUserFavorites(c *gin.Context) {
// capture middleware values
u := user.Retrieve(c)
ctx := c.Request.Context()

favorites := new([]*types.Favorite)

err := c.Bind(favorites)
if err != nil {
retErr := err

util.HandleError(ctx, http.StatusBadRequest, retErr)

return
}

err = database.FromContext(c).UpdateFavorites(ctx, u, *favorites)
if err != nil {
retErr := err

util.HandleError(ctx, http.StatusInternalServerError, retErr)

return
}

c.JSON(http.StatusOK, favorites)
}
3 changes: 3 additions & 0 deletions constants/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ const (
// TableDeployment defines the table type for the database deployments table.
TableDeployment = "deployments"

// TableFavorite defines the table type for the database favorites table.
TableFavorite = "favorites"

// TableHook defines the table type for the database hooks table.
TableHook = "hooks"

Expand Down
2 changes: 2 additions & 0 deletions database/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/go-vela/server/database/dashboard"
"github.com/go-vela/server/database/deployment"
"github.com/go-vela/server/database/executable"
"github.com/go-vela/server/database/favorite"
"github.com/go-vela/server/database/hook"
"github.com/go-vela/server/database/jwk"
"github.com/go-vela/server/database/log"
Expand Down Expand Up @@ -86,6 +87,7 @@ type (
dashboard.DashboardInterface
executable.BuildExecutableInterface
deployment.DeploymentInterface
favorite.FavoriteInterface
hook.HookInterface
jwk.JWKInterface
log.LogInterface
Expand Down
28 changes: 28 additions & 0 deletions database/favorite/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: Apache-2.0

package favorite

import (
"context"

"github.com/sirupsen/logrus"

api "github.com/go-vela/server/api/types"
)

// CreateFavorite creates a new favorite in the database.
func (e *Engine) CreateFavorite(ctx context.Context, u *api.User, f *api.Favorite) error {
e.logger.WithFields(logrus.Fields{
"repo": f.GetRepo(),
}).Tracef("creating favorite for user %s", u.GetName())

return e.client.
WithContext(ctx).
Exec(
`INSERT INTO favorites (user_id, repo_id, position)
SELECT ?, id, ? FROM repos WHERE full_name = ?;`,
u.GetID(),
f.GetPosition(),
f.GetRepo(),
).Error
}
88 changes: 88 additions & 0 deletions database/favorite/create_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// SPDX-License-Identifier: Apache-2.0

package favorite

import (
"context"
"testing"

"github.com/DATA-DOG/go-sqlmock"
"github.com/go-vela/server/constants"
"github.com/go-vela/server/database/testutils"
"github.com/go-vela/server/database/types"
)

func TestFavorite_Engine_CreateFavorite(t *testing.T) {
// setup types
_user := testutils.APIUser()
_user.SetID(1)

_repo := testutils.APIRepo()
_repo.SetID(1)
_repo.SetFullName("foo/bar")

_favorite := testutils.APIFavorite()
_favorite.SetRepo("foo/bar")
_favorite.SetPosition(1)

_postgres, _mock := testPostgres(t)

defer func() { _sql, _ := _postgres.client.DB(); _sql.Close() }()

// ensure the mock expects the query
_mock.ExpectExec(`INSERT INTO favorites
(user_id, repo_id, position)
SELECT $1, id, $2 FROM repos WHERE full_name = $3;`).
WithArgs(1, 1, "foo/bar").WillReturnResult(sqlmock.NewResult(1, 1))

_sqlite := testSqlite(t)

err := _sqlite.client.AutoMigrate(&types.Repo{})
if err != nil {
t.Errorf("unable to create build table for sqlite: %v", err)
}

err = _sqlite.client.Table(constants.TableRepo).Create(types.RepoFromAPI(_repo)).Error
if err != nil {
t.Errorf("unable to create test user for sqlite: %v", err)
}

defer func() { _sql, _ := _sqlite.client.DB(); _sql.Close() }()

// setup tests
tests := []struct {
failure bool
name string
database *Engine
}{
{
failure: false,
name: "postgres",
database: _postgres,
},
{
failure: false,
name: "sqlite3",
database: _sqlite,
},
}

// run tests
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
err := test.database.CreateFavorite(context.TODO(), _user, _favorite)

if test.failure {
if err == nil {
t.Errorf("CreateUser for %s should have returned err", test.name)
}

return
}

if err != nil {
t.Errorf("CreateUser for %s returned err: %v", test.name, err)
}
})
}
}
27 changes: 27 additions & 0 deletions database/favorite/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: Apache-2.0

package favorite

import (
"context"

"github.com/sirupsen/logrus"

api "github.com/go-vela/server/api/types"
)

// DeleteFavorite deletes a user favorite in the database.
func (e *Engine) DeleteFavorite(ctx context.Context, u *api.User, f *api.Favorite) error {
e.logger.WithFields(logrus.Fields{
"repo": f.GetRepo(),
}).Tracef("deleting favorite for user %s", u.GetName())

return e.client.
WithContext(ctx).
Exec(
`DELETE FROM favorites
WHERE user_id = ? AND repo_id = (SELECT id FROM repos WHERE full_name = ?)`,
u.GetID(),
f.GetRepo(),
).Error
}
Loading
Loading