mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-09-27 06:00:14 +00:00
Add star lists
This commit is contained in:
parent
27fa12427c
commit
9309ce5612
38 changed files with 2184 additions and 5 deletions
|
@ -997,6 +997,9 @@ LEVEL = Info
|
||||||
;; Disable stars feature.
|
;; Disable stars feature.
|
||||||
;DISABLE_STARS = false
|
;DISABLE_STARS = false
|
||||||
;;
|
;;
|
||||||
|
;; Disable star lists feature.
|
||||||
|
;DISABLE_STAR_LISTS = false
|
||||||
|
;;
|
||||||
;; Disable repository forking.
|
;; Disable repository forking.
|
||||||
;DISABLE_FORKS = false
|
;DISABLE_FORKS = false
|
||||||
;;
|
;;
|
||||||
|
|
20
models/fixtures/star_list.yml
Normal file
20
models/fixtures/star_list.yml
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
-
|
||||||
|
id: 1
|
||||||
|
user_id: 1
|
||||||
|
name: "First List"
|
||||||
|
description: "Description for first List"
|
||||||
|
is_private: false
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 2
|
||||||
|
user_id: 1
|
||||||
|
name: "Second List"
|
||||||
|
description: "This is private"
|
||||||
|
is_private: true
|
||||||
|
|
||||||
|
-
|
||||||
|
id: 3
|
||||||
|
user_id: 2
|
||||||
|
name: "Third List"
|
||||||
|
description: "It's a Secret to Everybody"
|
||||||
|
is_private: false
|
4
models/fixtures/star_list_repos.yml
Normal file
4
models/fixtures/star_list_repos.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
-
|
||||||
|
id: 1
|
||||||
|
star_list_id: 1
|
||||||
|
repo_id: 1
|
|
@ -156,6 +156,7 @@ type SearchRepoOptions struct {
|
||||||
OrderBy db.SearchOrderBy
|
OrderBy db.SearchOrderBy
|
||||||
Private bool // Include private repositories in results
|
Private bool // Include private repositories in results
|
||||||
StarredByID int64
|
StarredByID int64
|
||||||
|
StarListID int64
|
||||||
WatchedByID int64
|
WatchedByID int64
|
||||||
AllPublic bool // Include also all public repositories of users and public organisations
|
AllPublic bool // Include also all public repositories of users and public organisations
|
||||||
AllLimited bool // Include also all public repositories of limited organisations
|
AllLimited bool // Include also all public repositories of limited organisations
|
||||||
|
@ -409,6 +410,11 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
|
||||||
cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID})))
|
cond = cond.And(builder.In("id", builder.Select("repo_id").From("star").Where(builder.Eq{"uid": opts.StarredByID})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Restrict to repos in a star list
|
||||||
|
if opts.StarListID > 0 {
|
||||||
|
cond = cond.And(builder.In("id", builder.Select("repo_id").From("star_list_repos").Where(builder.Eq{"star_list_id": opts.StarListID})))
|
||||||
|
}
|
||||||
|
|
||||||
// Restrict to watched repositories
|
// Restrict to watched repositories
|
||||||
if opts.WatchedByID > 0 {
|
if opts.WatchedByID > 0 {
|
||||||
cond = cond.And(builder.In("id", builder.Select("repo_id").From("watch").Where(builder.Eq{"user_id": opts.WatchedByID})))
|
cond = cond.And(builder.In("id", builder.Select("repo_id").From("watch").Where(builder.Eq{"user_id": opts.WatchedByID})))
|
||||||
|
|
|
@ -60,6 +60,10 @@ func StarRepo(ctx context.Context, userID, repoID int64, star bool) error {
|
||||||
if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars = num_stars - 1 WHERE id = ?", userID); err != nil {
|
if _, err := db.Exec(ctx, "UPDATE `user` SET num_stars = num_stars - 1 WHERE id = ?", userID); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
// Delete the repo from all star lists of this user
|
||||||
|
if _, err := db.Exec(ctx, "DELETE FROM star_list_repos WHERE repo_id = ? AND star_list_id IN (SELECT id FROM star_list WHERE user_id = ?)", repoID, userID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return committer.Commit()
|
return committer.Commit()
|
||||||
|
|
351
models/repo/star_list.go
Normal file
351
models/repo/star_list.go
Normal file
|
@ -0,0 +1,351 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
|
|
||||||
|
"xorm.io/builder"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrStarListNotFound struct {
|
||||||
|
Name string
|
||||||
|
ID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrStarListNotFound) Error() string {
|
||||||
|
if err.Name == "" {
|
||||||
|
return fmt.Sprintf("A star list with the ID %d was not found", err.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("A star list with the name %s was not found", err.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrStarListNotFound returns if the error is, that the star is not found
|
||||||
|
func IsErrStarListNotFound(err error) bool {
|
||||||
|
_, ok := err.(ErrStarListNotFound)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
type ErrStarListExists struct {
|
||||||
|
Name string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (err ErrStarListExists) Error() string {
|
||||||
|
return fmt.Sprintf("A star list with the name %s exists", err.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsErrIssueMaxPinReached returns if the error is, that the star list exists
|
||||||
|
func IsErrStarListExists(err error) bool {
|
||||||
|
_, ok := err.(ErrStarListExists)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
type StarList struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
UserID int64 `xorm:"INDEX UNIQUE(name)"`
|
||||||
|
Name string `xorm:"INDEX UNIQUE(name)"`
|
||||||
|
Description string
|
||||||
|
IsPrivate bool
|
||||||
|
RepositoryCount int64 `xorm:"-"`
|
||||||
|
User *user_model.User `xorm:"-"`
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
|
RepoIDs *[]int64 `xorm:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StarListRepos struct {
|
||||||
|
ID int64 `xorm:"pk autoincr"`
|
||||||
|
StarListID int64 `xorm:"INDEX UNIQUE(repo)"`
|
||||||
|
RepoID int64 `xorm:"INDEX UNIQUE(repo)"`
|
||||||
|
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StarListSlice []*StarList
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
db.RegisterModel(new(StarList))
|
||||||
|
db.RegisterModel(new(StarListRepos))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStarListByID returne the star list for the given ID.
|
||||||
|
// If the ID do not exists, it returns a ErrStarListNotFound error.
|
||||||
|
func GetStarListByID(ctx context.Context, id int64) (*StarList, error) {
|
||||||
|
var starList StarList
|
||||||
|
|
||||||
|
found, err := db.GetEngine(ctx).Table("star_list").ID(id).Get(&starList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, ErrStarListNotFound{ID: id}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &starList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStarListByID returne the star list of the given user with the given name.
|
||||||
|
// If the name do not exists, it returns a ErrStarListNotFound error.
|
||||||
|
func GetStarListByName(ctx context.Context, userID int64, name string) (*StarList, error) {
|
||||||
|
var starList StarList
|
||||||
|
|
||||||
|
found, err := db.GetEngine(ctx).Table("star_list").Where("user_id = ?", userID).And("LOWER(name) = ?", strings.ToLower(name)).Get(&starList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !found {
|
||||||
|
return nil, ErrStarListNotFound{Name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &starList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStarListsByUserID retruns all star lists for the given user
|
||||||
|
func GetStarListsByUserID(ctx context.Context, userID int64, includePrivate bool) (StarListSlice, error) {
|
||||||
|
cond := builder.NewCond().And(builder.Eq{"user_id": userID})
|
||||||
|
|
||||||
|
if !includePrivate {
|
||||||
|
cond = cond.And(builder.Eq{"is_private": false})
|
||||||
|
}
|
||||||
|
|
||||||
|
starLists := make(StarListSlice, 0)
|
||||||
|
err := db.GetEngine(ctx).Table("star_list").Where(cond).Asc("created_unix").Asc("id").Find(&starLists)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return starLists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateStarLists creates a new star list
|
||||||
|
// It returns a ErrStarListExists if the user already have a star list with this name
|
||||||
|
func CreateStarList(ctx context.Context, userID int64, name, description string, isPrivate bool) (*StarList, error) {
|
||||||
|
_, err := GetStarListByName(ctx, userID, name)
|
||||||
|
if err != nil {
|
||||||
|
if !IsErrStarListNotFound(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, ErrStarListExists{Name: name}
|
||||||
|
}
|
||||||
|
|
||||||
|
starList := StarList{
|
||||||
|
UserID: userID,
|
||||||
|
Name: name,
|
||||||
|
Description: description,
|
||||||
|
IsPrivate: isPrivate,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.GetEngine(ctx).Insert(starList)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &starList, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteStarListByID deletes the star list with the given ID
|
||||||
|
func DeleteStarListByID(ctx context.Context, id int64) error {
|
||||||
|
ctx, committer, err := db.TxContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.GetEngine(ctx).Exec("DELETE FROM star_list_repos WHERE star_list_id = ?", id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.GetEngine(ctx).Exec("DELETE FROM star_list WHERE id = ?", id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return committer.Commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadRepositoryCount loads just the RepositoryCount.
|
||||||
|
// The count checks if how many repos in the list the actor is able to see.
|
||||||
|
func (starList *StarList) LoadRepositoryCount(ctx context.Context, actor *user_model.User) error {
|
||||||
|
count, err := CountRepository(ctx, &SearchRepoOptions{Actor: actor, StarListID: starList.ID})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
starList.RepositoryCount = count
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadUser loads the User field
|
||||||
|
func (starList *StarList) LoadUser(ctx context.Context) error {
|
||||||
|
user, err := user_model.GetUserByID(ctx, starList.UserID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
starList.User = user
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadRepoIDs loads all repo ids which are in the list
|
||||||
|
func (starList *StarList) LoadRepoIDs(ctx context.Context) error {
|
||||||
|
repoIDs := make([]int64, 0)
|
||||||
|
err := db.GetEngine(ctx).Table("star_list_repos").Where("star_list_id = ?", starList.ID).Cols("repo_id").Find(&repoIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
starList.RepoIDs = &repoIDs
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retruns if the list contains the given repo id.
|
||||||
|
// This function needs the repo ids loaded to work.
|
||||||
|
func (starList *StarList) ContainsRepoID(repoID int64) bool {
|
||||||
|
return slices.Contains(*starList.RepoIDs, repoID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRepo adds the given repo to the list
|
||||||
|
func (starList *StarList) AddRepo(ctx context.Context, repoID int64) error {
|
||||||
|
err := starList.LoadRepoIDs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if starList.ContainsRepoID(repoID) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starList.LoadUser(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
repo, err := GetRepositoryByID(ctx, repoID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = StarRepo(ctx, starList.User.ID, repo.ID, true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
starListRepo := StarListRepos{
|
||||||
|
StarListID: starList.ID,
|
||||||
|
RepoID: repoID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = db.GetEngine(ctx).Insert(starListRepo)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveRepo removes the given repo from the list
|
||||||
|
func (starList *StarList) RemoveRepo(ctx context.Context, repoID int64) error {
|
||||||
|
_, err := db.GetEngine(ctx).Exec("DELETE FROM star_list_repos WHERE star_list_id = ? AND repo_id = ?", starList.ID, repoID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditData edits the star list and save it to the database
|
||||||
|
// It returns a ErrStarListExists if the user already have a star list with this name
|
||||||
|
func (starList *StarList) EditData(ctx context.Context, name, description string, isPrivate bool) error {
|
||||||
|
if !strings.EqualFold(starList.Name, name) {
|
||||||
|
_, err := GetStarListByName(ctx, starList.UserID, name)
|
||||||
|
if err != nil {
|
||||||
|
if !IsErrStarListNotFound(err) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return ErrStarListExists{Name: name}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
oldName := starList.Name
|
||||||
|
oldDescription := starList.Description
|
||||||
|
oldIsPrivate := starList.IsPrivate
|
||||||
|
|
||||||
|
starList.Name = name
|
||||||
|
starList.Description = description
|
||||||
|
starList.IsPrivate = isPrivate
|
||||||
|
|
||||||
|
_, err := db.GetEngine(ctx).Table("star_list").ID(starList.ID).Cols("name", "description", "is_private").Update(starList)
|
||||||
|
if err != nil {
|
||||||
|
starList.Name = oldName
|
||||||
|
starList.Description = oldDescription
|
||||||
|
starList.IsPrivate = oldIsPrivate
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasAccess retruns if the given user has access to this star list
|
||||||
|
func (starList *StarList) HasAccess(user *user_model.User) bool {
|
||||||
|
if !starList.IsPrivate {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if user == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return starList.UserID == user.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustHaveAccess returns a ErrStarListNotFound if the given user has no access to the star list
|
||||||
|
func (starList *StarList) MustHaveAccess(user *user_model.User) error {
|
||||||
|
if !starList.HasAccess(user) {
|
||||||
|
return ErrStarListNotFound{ID: starList.ID, Name: starList.Name}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a Link to the star list.
|
||||||
|
// This function needs the user loaded to work.
|
||||||
|
func (starList *StarList) Link() string {
|
||||||
|
return fmt.Sprintf("%s/-/starlist/%s", starList.User.HomeLink(), url.PathEscape(starList.Name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadUser calls LoadUser on all elements of the list
|
||||||
|
func (starLists StarListSlice) LoadUser(ctx context.Context) error {
|
||||||
|
for _, list := range starLists {
|
||||||
|
err := list.LoadUser(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadRepositoryCount calls LoadRepositoryCount on all elements of the list
|
||||||
|
func (starLists StarListSlice) LoadRepositoryCount(ctx context.Context, actor *user_model.User) error {
|
||||||
|
for _, list := range starLists {
|
||||||
|
err := list.LoadRepositoryCount(ctx, actor)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadRepoIDs calls LoadRepoIDs on all elements of the list
|
||||||
|
func (starLists StarListSlice) LoadRepoIDs(ctx context.Context) error {
|
||||||
|
for _, list := range starLists {
|
||||||
|
err := list.LoadRepoIDs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
162
models/repo/star_list_test.go
Normal file
162
models/repo/star_list_test.go
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/db"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetStarListByID(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
starList, err := repo_model.GetStarListByID(db.DefaultContext, 1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "First List", starList.Name)
|
||||||
|
assert.Equal(t, "Description for first List", starList.Description)
|
||||||
|
assert.False(t, starList.IsPrivate)
|
||||||
|
|
||||||
|
// Check if ErrStarListNotFound is returned on an not existing ID
|
||||||
|
starList, err = repo_model.GetStarListByID(db.DefaultContext, -1)
|
||||||
|
assert.True(t, repo_model.IsErrStarListNotFound(err))
|
||||||
|
assert.Nil(t, starList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetStarListByName(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
starList, err := repo_model.GetStarListByName(db.DefaultContext, 1, "First List")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), starList.ID)
|
||||||
|
assert.Equal(t, "Description for first List", starList.Description)
|
||||||
|
assert.False(t, starList.IsPrivate)
|
||||||
|
|
||||||
|
// Check if ErrStarListNotFound is returned on an not existing Name
|
||||||
|
starList, err = repo_model.GetStarListByName(db.DefaultContext, 1, "NotExistingList")
|
||||||
|
assert.True(t, repo_model.IsErrStarListNotFound(err))
|
||||||
|
assert.Nil(t, starList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetStarListByUserID(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
// Get only public lists
|
||||||
|
starLists, err := repo_model.GetStarListsByUserID(db.DefaultContext, 1, false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, starLists, 1)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), starLists[0].ID)
|
||||||
|
assert.Equal(t, "First List", starLists[0].Name)
|
||||||
|
assert.Equal(t, "Description for first List", starLists[0].Description)
|
||||||
|
assert.False(t, starLists[0].IsPrivate)
|
||||||
|
|
||||||
|
// Get also private lists
|
||||||
|
starLists, err = repo_model.GetStarListsByUserID(db.DefaultContext, 1, true)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Len(t, starLists, 2)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), starLists[0].ID)
|
||||||
|
assert.Equal(t, "First List", starLists[0].Name)
|
||||||
|
assert.Equal(t, "Description for first List", starLists[0].Description)
|
||||||
|
assert.False(t, starLists[0].IsPrivate)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(2), starLists[1].ID)
|
||||||
|
assert.Equal(t, "Second List", starLists[1].Name)
|
||||||
|
assert.Equal(t, "This is private", starLists[1].Description)
|
||||||
|
assert.True(t, starLists[1].IsPrivate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCreateStarList(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
// Check that you can't create two list with the same name for the same user
|
||||||
|
starList, err := repo_model.CreateStarList(db.DefaultContext, 1, "First List", "Test", false)
|
||||||
|
assert.True(t, repo_model.IsErrStarListExists(err))
|
||||||
|
assert.Nil(t, starList)
|
||||||
|
|
||||||
|
// Now create the star list for real
|
||||||
|
starList, err = repo_model.CreateStarList(db.DefaultContext, 1, "My new List", "Test", false)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, "My new List", starList.Name)
|
||||||
|
assert.Equal(t, "Test", starList.Description)
|
||||||
|
assert.False(t, starList.IsPrivate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStarListRepositoryCount(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
starList := unittest.AssertExistsAndLoadBean(t, &repo_model.StarList{ID: 1})
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
assert.NoError(t, starList.LoadRepositoryCount(db.DefaultContext, user))
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), starList.RepositoryCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStarListAddRepo(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
const repoID = 4
|
||||||
|
|
||||||
|
starList := unittest.AssertExistsAndLoadBean(t, &repo_model.StarList{ID: 1})
|
||||||
|
|
||||||
|
assert.NoError(t, starList.AddRepo(db.DefaultContext, repoID))
|
||||||
|
|
||||||
|
assert.NoError(t, starList.LoadRepoIDs(db.DefaultContext))
|
||||||
|
|
||||||
|
assert.True(t, starList.ContainsRepoID(repoID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStarListRemoveRepo(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
const repoID = 1
|
||||||
|
|
||||||
|
starList := unittest.AssertExistsAndLoadBean(t, &repo_model.StarList{ID: 1})
|
||||||
|
|
||||||
|
assert.NoError(t, starList.RemoveRepo(db.DefaultContext, repoID))
|
||||||
|
|
||||||
|
assert.NoError(t, starList.LoadRepoIDs(db.DefaultContext))
|
||||||
|
|
||||||
|
assert.False(t, starList.ContainsRepoID(repoID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStarListEditData(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
starList := unittest.AssertExistsAndLoadBean(t, &repo_model.StarList{ID: 1})
|
||||||
|
|
||||||
|
assert.True(t, repo_model.IsErrStarListExists(starList.EditData(db.DefaultContext, "Second List", "New Description", false)))
|
||||||
|
|
||||||
|
assert.NoError(t, starList.EditData(db.DefaultContext, "First List", "New Description", false))
|
||||||
|
|
||||||
|
assert.Equal(t, "First List", starList.Name)
|
||||||
|
assert.Equal(t, "New Description", starList.Description)
|
||||||
|
assert.False(t, starList.IsPrivate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStarListHasAccess(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
starList := unittest.AssertExistsAndLoadBean(t, &repo_model.StarList{ID: 2})
|
||||||
|
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
|
||||||
|
assert.True(t, starList.HasAccess(user1))
|
||||||
|
assert.False(t, starList.HasAccess(user2))
|
||||||
|
|
||||||
|
assert.NoError(t, starList.MustHaveAccess(user1))
|
||||||
|
assert.True(t, repo_model.IsErrStarListNotFound(starList.MustHaveAccess(user2)))
|
||||||
|
}
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"code.gitea.io/gitea/models/db"
|
"code.gitea.io/gitea/models/db"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
@ -69,3 +70,22 @@ func TestClearRepoStars(t *testing.T) {
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Len(t, gazers, 0)
|
assert.Len(t, gazers, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestUnstarRepo(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
|
||||||
|
assert.NoError(t, repo_model.StarRepo(db.DefaultContext, user.ID, repo.ID, false))
|
||||||
|
|
||||||
|
assert.False(t, repo_model.IsStaring(db.DefaultContext, user.ID, repo.ID))
|
||||||
|
|
||||||
|
// Check if the repo is removed from the star list
|
||||||
|
starList, err := repo_model.GetStarListByID(db.DefaultContext, 1)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
assert.NoError(t, starList.LoadRepoIDs(db.DefaultContext))
|
||||||
|
|
||||||
|
assert.False(t, starList.ContainsRepoID(repo.ID))
|
||||||
|
}
|
||||||
|
|
|
@ -487,6 +487,15 @@ func (u *User) IsMailable() bool {
|
||||||
return u.IsActive
|
return u.IsActive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSameUser checks if both user are the same
|
||||||
|
func (u *User) IsSameUser(user *User) bool {
|
||||||
|
if user == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return u.ID == user.ID
|
||||||
|
}
|
||||||
|
|
||||||
// IsUserExist checks if given user name exist,
|
// IsUserExist checks if given user name exist,
|
||||||
// the user name should be noncased unique.
|
// the user name should be noncased unique.
|
||||||
// If uid is presented, then check will rule out that one,
|
// If uid is presented, then check will rule out that one,
|
||||||
|
|
|
@ -591,6 +591,17 @@ func Test_NormalizeUserFromEmail(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIsSameUser(t *testing.T) {
|
||||||
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||||
|
|
||||||
|
assert.False(t, user1.IsSameUser(nil))
|
||||||
|
assert.False(t, user1.IsSameUser(user4))
|
||||||
|
assert.True(t, user1.IsSameUser(user1))
|
||||||
|
}
|
||||||
|
|
||||||
func TestDisabledUserFeatures(t *testing.T) {
|
func TestDisabledUserFeatures(t *testing.T) {
|
||||||
assert.NoError(t, unittest.PrepareTestDatabase())
|
assert.NoError(t, unittest.PrepareTestDatabase())
|
||||||
|
|
||||||
|
|
|
@ -50,6 +50,7 @@ var (
|
||||||
PrefixArchiveFiles bool
|
PrefixArchiveFiles bool
|
||||||
DisableMigrations bool
|
DisableMigrations bool
|
||||||
DisableStars bool
|
DisableStars bool
|
||||||
|
DisableStarLists bool
|
||||||
DisableForks bool
|
DisableForks bool
|
||||||
DefaultBranch string
|
DefaultBranch string
|
||||||
AllowAdoptionOfUnadoptedRepositories bool
|
AllowAdoptionOfUnadoptedRepositories bool
|
||||||
|
@ -172,6 +173,7 @@ var (
|
||||||
PrefixArchiveFiles: true,
|
PrefixArchiveFiles: true,
|
||||||
DisableMigrations: false,
|
DisableMigrations: false,
|
||||||
DisableStars: false,
|
DisableStars: false,
|
||||||
|
DisableStarLists: false,
|
||||||
DisableForks: false,
|
DisableForks: false,
|
||||||
DefaultBranch: "main",
|
DefaultBranch: "main",
|
||||||
AllowForkWithoutMaximumLimit: true,
|
AllowForkWithoutMaximumLimit: true,
|
||||||
|
|
|
@ -9,6 +9,7 @@ type GeneralRepoSettings struct {
|
||||||
HTTPGitDisabled bool `json:"http_git_disabled"`
|
HTTPGitDisabled bool `json:"http_git_disabled"`
|
||||||
MigrationsDisabled bool `json:"migrations_disabled"`
|
MigrationsDisabled bool `json:"migrations_disabled"`
|
||||||
StarsDisabled bool `json:"stars_disabled"`
|
StarsDisabled bool `json:"stars_disabled"`
|
||||||
|
StarListsDisabled bool `json:"star_lists_disabled"`
|
||||||
ForksDisabled bool `json:"forks_disabled"`
|
ForksDisabled bool `json:"forks_disabled"`
|
||||||
TimeTrackingDisabled bool `json:"time_tracking_disabled"`
|
TimeTrackingDisabled bool `json:"time_tracking_disabled"`
|
||||||
LFSDisabled bool `json:"lfs_disabled"`
|
LFSDisabled bool `json:"lfs_disabled"`
|
||||||
|
|
|
@ -116,3 +116,26 @@ type UpdateUserAvatarOption struct {
|
||||||
// image must be base64 encoded
|
// image must be base64 encoded
|
||||||
Image string `json:"image" binding:"Required"`
|
Image string `json:"image" binding:"Required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StarList represents a star list
|
||||||
|
type StarList struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
IsPrivate bool `json:"is_private"`
|
||||||
|
RepositoryCount int64 `json:"repository_count"`
|
||||||
|
User *User `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEditStarListOptions when creating or editing a star list
|
||||||
|
type CreateEditStarListOptions struct {
|
||||||
|
Name string `json:"name" binding:"Required"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
IsPrivate bool `json:"is_private"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StarListRepoInfo represents a star list and if the repo contains this star list
|
||||||
|
type StarListRepoInfo struct {
|
||||||
|
StarList *StarList `json:"star_list"`
|
||||||
|
Contains bool `json:"contains"`
|
||||||
|
}
|
||||||
|
|
|
@ -145,6 +145,9 @@ confirm_delete_selected = Confirm to delete all selected items?
|
||||||
name = Name
|
name = Name
|
||||||
value = Value
|
value = Value
|
||||||
|
|
||||||
|
repository_count_1 = 1 repository
|
||||||
|
repository_count_n = %d repositories
|
||||||
|
|
||||||
filter = Filter
|
filter = Filter
|
||||||
filter.clear = Clear filters
|
filter.clear = Clear filters
|
||||||
filter.is_archived = Archived
|
filter.is_archived = Archived
|
||||||
|
@ -3773,3 +3776,17 @@ submodule = Submodule
|
||||||
filepreview.line = Line %[1]d in %[2]s
|
filepreview.line = Line %[1]d in %[2]s
|
||||||
filepreview.lines = Lines %[1]d to %[2]d in %[3]s
|
filepreview.lines = Lines %[1]d to %[2]d in %[3]s
|
||||||
filepreview.truncated = Preview has been truncated
|
filepreview.truncated = Preview has been truncated
|
||||||
|
|
||||||
|
[starlist]
|
||||||
|
list_header = Lists
|
||||||
|
edit_header = Edit list
|
||||||
|
add_header = Add list
|
||||||
|
delete_header = Delete list
|
||||||
|
name_label = Name:
|
||||||
|
name_placeholder = The name of your starlist
|
||||||
|
description_label = Description:
|
||||||
|
description_placeholder = A Description of your list
|
||||||
|
private = This list is private
|
||||||
|
name_exists_error = You already have a list with the name %s
|
||||||
|
delete_success_message = List %s was successfully deleted
|
||||||
|
no_star_lists_text = It looks like you have no star lists yet. Try to create one.
|
||||||
|
|
|
@ -716,6 +716,40 @@ func mustEnableAttachments(ctx *context.APIContext) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mustEnableStarLists(ctx *context.APIContext) {
|
||||||
|
if setting.Repository.DisableStarLists {
|
||||||
|
ctx.Error(http.StatusNotImplemented, "StarListsDisabled", fmt.Errorf("star lists are disabled on this instance"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func starListAssignment(ctx *context.APIContext) {
|
||||||
|
var owner *user_model.User
|
||||||
|
if ctx.ContextUser == nil {
|
||||||
|
owner = ctx.Doer
|
||||||
|
} else {
|
||||||
|
owner = ctx.ContextUser
|
||||||
|
}
|
||||||
|
|
||||||
|
starList, err := repo_model.GetStarListByName(ctx, owner.ID, ctx.Params("starlist"))
|
||||||
|
if err != nil {
|
||||||
|
if repo_model.IsErrStarListNotFound(err) {
|
||||||
|
ctx.NotFound("GetStarListByName", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetStarListByName", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starList.MustHaveAccess(ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.NotFound("GetStarListByName", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Starlist = starList
|
||||||
|
}
|
||||||
|
|
||||||
// bind binding an obj to a func(ctx *context.APIContext)
|
// bind binding an obj to a func(ctx *context.APIContext)
|
||||||
func bind[T any](_ T) any {
|
func bind[T any](_ T) any {
|
||||||
return func(ctx *context.APIContext) {
|
return func(ctx *context.APIContext) {
|
||||||
|
@ -828,6 +862,11 @@ func Routes() *web.Route {
|
||||||
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
|
}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth())
|
||||||
|
|
||||||
m.Get("/activities/feeds", user.ListUserActivityFeeds)
|
m.Get("/activities/feeds", user.ListUserActivityFeeds)
|
||||||
|
m.Get("/starlists", mustEnableStarLists, user.ListUserStarLists)
|
||||||
|
m.Group("/starlist/{starlist}", func() {
|
||||||
|
m.Get("", user.GetUserStarListByName)
|
||||||
|
m.Get("/repos", user.GetUserStarListRepos)
|
||||||
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), mustEnableStarLists, starListAssignment)
|
||||||
}, context.UserAssignmentAPI(), individualPermsChecker)
|
}, context.UserAssignmentAPI(), individualPermsChecker)
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser))
|
||||||
|
|
||||||
|
@ -963,8 +1002,27 @@ func Routes() *web.Route {
|
||||||
m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar)
|
m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar)
|
||||||
m.Delete("", user.DeleteAvatar)
|
m.Delete("", user.DeleteAvatar)
|
||||||
}, reqToken())
|
}, reqToken())
|
||||||
|
|
||||||
|
m.Group("/starlists", func() {
|
||||||
|
m.Get("", user.ListOwnStarLists)
|
||||||
|
m.Post("", bind(api.CreateEditStarListOptions{}), user.CreateStarList)
|
||||||
|
m.Get("/repoinfo/{username}/{reponame}", repoAssignment(), user.GetStarListRepoInfo)
|
||||||
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), mustEnableStarLists)
|
||||||
|
|
||||||
|
m.Group("/starlist/{starlist}", func() {
|
||||||
|
m.Get("", user.GetOwnStarListByName)
|
||||||
|
m.Patch("", bind(api.CreateEditStarListOptions{}), user.EditStarList)
|
||||||
|
m.Delete("", user.DeleteStarList)
|
||||||
|
m.Get("/repos", user.GetOwnStarListRepos)
|
||||||
|
m.Group("/{username}/{reponame}", func() {
|
||||||
|
m.Put("", user.AddRepoToStarList)
|
||||||
|
m.Delete("", user.RemoveRepoFromStarList)
|
||||||
|
}, repoAssignment())
|
||||||
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), mustEnableStarLists, starListAssignment)
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
|
||||||
|
|
||||||
|
m.Get("/starlist/{id}", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository), mustEnableStarLists, user.GetStarListByID)
|
||||||
|
|
||||||
// Repositories (requires repo scope, org scope)
|
// Repositories (requires repo scope, org scope)
|
||||||
m.Post("/org/{org}/repos",
|
m.Post("/org/{org}/repos",
|
||||||
tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository),
|
tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository),
|
||||||
|
|
|
@ -61,6 +61,7 @@ func GetGeneralRepoSettings(ctx *context.APIContext) {
|
||||||
HTTPGitDisabled: setting.Repository.DisableHTTPGit,
|
HTTPGitDisabled: setting.Repository.DisableHTTPGit,
|
||||||
MigrationsDisabled: setting.Repository.DisableMigrations,
|
MigrationsDisabled: setting.Repository.DisableMigrations,
|
||||||
StarsDisabled: setting.Repository.DisableStars,
|
StarsDisabled: setting.Repository.DisableStars,
|
||||||
|
StarListsDisabled: setting.Repository.DisableStarLists,
|
||||||
ForksDisabled: setting.Repository.DisableForks,
|
ForksDisabled: setting.Repository.DisableForks,
|
||||||
TimeTrackingDisabled: !setting.Service.EnableTimetracking,
|
TimeTrackingDisabled: !setting.Service.EnableTimetracking,
|
||||||
LFSDisabled: !setting.LFS.StartServer,
|
LFSDisabled: !setting.LFS.StartServer,
|
||||||
|
|
|
@ -205,4 +205,7 @@ type swaggerParameterBodies struct {
|
||||||
|
|
||||||
// in:body
|
// in:body
|
||||||
UpdateVariableOption api.UpdateVariableOption
|
UpdateVariableOption api.UpdateVariableOption
|
||||||
|
|
||||||
|
// in:body
|
||||||
|
CreateEditStarListOptions api.CreateEditStarListOptions
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,3 +48,24 @@ type swaggerResponseUserSettings struct {
|
||||||
// in:body
|
// in:body
|
||||||
Body []api.UserSettings `json:"body"`
|
Body []api.UserSettings `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StarList
|
||||||
|
// swagger:response StarList
|
||||||
|
type swaggerResponseStarList struct {
|
||||||
|
// in:body
|
||||||
|
Body api.StarList `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StarListSlice
|
||||||
|
// swagger:response StarListSlice
|
||||||
|
type swaggerResponseStarListSlice struct {
|
||||||
|
// in:body
|
||||||
|
Body []api.StarList `json:"body"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// StarListRepoInfo
|
||||||
|
// swagger:response StarListRepoInfo
|
||||||
|
type swaggerResponseStarListRepoInfo struct {
|
||||||
|
// in:body
|
||||||
|
Body []api.StarListRepoInfo `json:"body"`
|
||||||
|
}
|
||||||
|
|
594
routers/api/v1/user/star_list.go
Normal file
594
routers/api/v1/user/star_list.go
Normal file
|
@ -0,0 +1,594 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
access_model "code.gitea.io/gitea/models/perm/access"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
unit_model "code.gitea.io/gitea/models/unit"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/convert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func listUserStarListsInternal(ctx *context.APIContext, user *user_model.User) {
|
||||||
|
starLists, err := repo_model.GetStarListsByUserID(ctx, user.ID, user.IsSameUser(ctx.Doer))
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetUserStarListsByUserID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starLists.LoadUser(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starLists.LoadRepositoryCount(ctx, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadRepositoryCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToStarLists(ctx, starLists, ctx.Doer))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUserStarLists list the given user's star lists
|
||||||
|
func ListUserStarLists(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /users/{username}/starlists user userGetUserStarLists
|
||||||
|
// ---
|
||||||
|
// summary: List the given user's star lists
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: username
|
||||||
|
// in: path
|
||||||
|
// description: username of user
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/StarListSlice"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "501":
|
||||||
|
// "$ref": "#/responses/featureDisabled"
|
||||||
|
listUserStarListsInternal(ctx, ctx.ContextUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListOwnStarLists list the authenticated user's star lists
|
||||||
|
func ListOwnStarLists(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /user/starlists user userGetOwnStarLists
|
||||||
|
// ---
|
||||||
|
// summary: List the authenticated user's star lists
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/StarListSlice"
|
||||||
|
// "401":
|
||||||
|
// "$ref": "#/responses/unauthorized"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "501":
|
||||||
|
// "$ref": "#/responses/featureDisabled"
|
||||||
|
listUserStarListsInternal(ctx, ctx.Doer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStarListRepoInfo gets all star lists of the user together with the information, if the given repo is in the list
|
||||||
|
func GetStarListRepoInfo(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /user/starlists/repoinfo/{owner}/{repo} user userGetStarListRepoInfo
|
||||||
|
// ---
|
||||||
|
// summary: Gets all star lists of the user together with the information, if the given repo is in the list
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo to star
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo to star
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/StarListRepoInfo"
|
||||||
|
// "401":
|
||||||
|
// "$ref": "#/responses/unauthorized"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "501":
|
||||||
|
// "$ref": "#/responses/featureDisabled"
|
||||||
|
starLists, err := repo_model.GetStarListsByUserID(ctx, ctx.Doer.ID, true)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetStarListsByUserID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starLists.LoadUser(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starLists.LoadRepositoryCount(ctx, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadRepositoryCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starLists.LoadRepoIDs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadRepoIDs", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
repoInfo := make([]api.StarListRepoInfo, len(starLists))
|
||||||
|
for i, list := range starLists {
|
||||||
|
repoInfo[i] = api.StarListRepoInfo{StarList: convert.ToStarList(ctx, list, ctx.Doer), Contains: list.ContainsRepoID(ctx.Repo.Repository.ID)}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, repoInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateStarList creates a star list
|
||||||
|
func CreateStarList(ctx *context.APIContext) {
|
||||||
|
// swagger:operation POST /user/starlists user userCreateStarList
|
||||||
|
// ---
|
||||||
|
// summary: Creates a star list
|
||||||
|
// parameters:
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// required: true
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/CreateEditStarListOptions"
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// "$ref": "#/responses/StarList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "401":
|
||||||
|
// "$ref": "#/responses/unauthorized"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "501":
|
||||||
|
// "$ref": "#/responses/featureDisabled"
|
||||||
|
opts := web.GetForm(ctx).(*api.CreateEditStarListOptions)
|
||||||
|
|
||||||
|
starList, err := repo_model.CreateStarList(ctx, ctx.Doer.ID, opts.Name, opts.Description, opts.IsPrivate)
|
||||||
|
if err != nil {
|
||||||
|
if repo_model.IsErrStarListExists(err) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "CreateStarList", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "CreateStarList", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starList.LoadUser(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusCreated, starList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStarListByNameInternal(ctx *context.APIContext) {
|
||||||
|
err := ctx.Starlist.LoadUser(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ctx.Starlist.LoadRepositoryCount(ctx, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadRepositoryCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToStarList(ctx, ctx.Starlist, ctx.Doer))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserStarListByName get the star list of the given user with the given name
|
||||||
|
func GetUserStarListByName(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /users/{username}/starlist/{name} user userGetUserStarListByName
|
||||||
|
// ---
|
||||||
|
// summary: Get the star list of the given user with the given name
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: username
|
||||||
|
// in: path
|
||||||
|
// description: username of user
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: name
|
||||||
|
// in: path
|
||||||
|
// description: name of the star list
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/StarList"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "501":
|
||||||
|
// "$ref": "#/responses/featureDisabled"
|
||||||
|
getStarListByNameInternal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOwnStarListByName get the star list of the authenticated user with the given name
|
||||||
|
func GetOwnStarListByName(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /user/starlist/{name} user userGetOwnStarListByName
|
||||||
|
// ---
|
||||||
|
// summary: Get the star list of the authenticated user with the given name
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: name
|
||||||
|
// in: path
|
||||||
|
// description: name of the star list
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/StarList"
|
||||||
|
// "401":
|
||||||
|
// "$ref": "#/responses/unauthorized"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "501":
|
||||||
|
// "$ref": "#/responses/featureDisabled"
|
||||||
|
getStarListByNameInternal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EditStarList edits a star list
|
||||||
|
func EditStarList(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PATCH /user/starlist/{name} user userEditStarList
|
||||||
|
// ---
|
||||||
|
// summary: Edits a star list
|
||||||
|
// parameters:
|
||||||
|
// - name: name
|
||||||
|
// in: path
|
||||||
|
// description: name of the star list
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: body
|
||||||
|
// in: body
|
||||||
|
// required: true
|
||||||
|
// schema:
|
||||||
|
// "$ref": "#/definitions/CreateEditStarListOptions"
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/StarList"
|
||||||
|
// "400":
|
||||||
|
// "$ref": "#/responses/error"
|
||||||
|
// "401":
|
||||||
|
// "$ref": "#/responses/unauthorized"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "501":
|
||||||
|
// "$ref": "#/responses/featureDisabled"
|
||||||
|
opts := web.GetForm(ctx).(*api.CreateEditStarListOptions)
|
||||||
|
|
||||||
|
err := ctx.Starlist.EditData(ctx, opts.Name, opts.Description, opts.IsPrivate)
|
||||||
|
if err != nil {
|
||||||
|
if repo_model.IsErrStarListExists(err) {
|
||||||
|
ctx.Error(http.StatusBadRequest, "EditData", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "EditData", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ctx.Starlist.LoadUser(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ctx.Starlist.LoadRepositoryCount(ctx, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadRepositoryCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToStarList(ctx, ctx.Starlist, ctx.Doer))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteStarList deletes a star list
|
||||||
|
func DeleteStarList(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /user/starlist/{name} user userDeleteStarList
|
||||||
|
// ---
|
||||||
|
// summary: Deletes a star list
|
||||||
|
// parameters:
|
||||||
|
// - name: name
|
||||||
|
// in: path
|
||||||
|
// description: name of the star list
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "401":
|
||||||
|
// "$ref": "#/responses/unauthorized"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "501":
|
||||||
|
// "$ref": "#/responses/featureDisabled"
|
||||||
|
err := repo_model.DeleteStarListByID(ctx, ctx.Starlist.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadRepositoryCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getStarListReposInternal(ctx *context.APIContext) {
|
||||||
|
opts := utils.GetListOptions(ctx)
|
||||||
|
|
||||||
|
repos, count, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{Actor: ctx.Doer, StarListID: ctx.Starlist.ID})
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "SearchRepository", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repos.LoadAttributes(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadAttributes", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
apiRepos := make([]*api.Repository, 0, len(repos))
|
||||||
|
for i := range repos {
|
||||||
|
permission, err := access_model.GetUserRepoPermission(ctx, repos[i], ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ctx.IsSigned && ctx.Doer.IsAdmin || permission.UnitAccessMode(unit_model.TypeCode) >= perm.AccessModeRead {
|
||||||
|
apiRepos = append(apiRepos, convert.ToRepo(ctx, repos[i], permission))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.SetLinkHeader(int(count), opts.PageSize)
|
||||||
|
ctx.SetTotalCountHeader(count)
|
||||||
|
ctx.JSON(http.StatusOK, &apiRepos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserStarListRepos get the repos of the star list of the given user with the given name
|
||||||
|
func GetUserStarListRepos(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /users/{username}/starlist/{name}/repos user userGetUserStarListRepos
|
||||||
|
// ---
|
||||||
|
// summary: Get the repos of the star list of the given user with the given name
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: username
|
||||||
|
// in: path
|
||||||
|
// description: username of user
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: name
|
||||||
|
// in: path
|
||||||
|
// description: name of the star list
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/RepositoryList"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "501":
|
||||||
|
// "$ref": "#/responses/featureDisabled"
|
||||||
|
getStarListReposInternal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOwnStarListRepos get the repos of the star list of the authenticated user with the given name
|
||||||
|
func GetOwnStarListRepos(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /user/starlist/{name}/repos user userGetOwnStarListRepos
|
||||||
|
// ---
|
||||||
|
// summary: Get the repos of the star list of the authenticated user with the given name
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// parameters:
|
||||||
|
// - name: name
|
||||||
|
// in: path
|
||||||
|
// description: name of the star list
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: page
|
||||||
|
// in: query
|
||||||
|
// description: page number of results to return (1-based)
|
||||||
|
// type: integer
|
||||||
|
// - name: limit
|
||||||
|
// in: query
|
||||||
|
// description: page size of results
|
||||||
|
// type: integer
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/RepositoryList"
|
||||||
|
// "401":
|
||||||
|
// "$ref": "#/responses/unauthorized"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "501":
|
||||||
|
// "$ref": "#/responses/featureDisabled"
|
||||||
|
getStarListReposInternal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRepoToStarList adds a Repo to a Star List
|
||||||
|
func AddRepoToStarList(ctx *context.APIContext) {
|
||||||
|
// swagger:operation PUT /user/starlist/{name}/{owner}/{repo} user userAddRepoToStarList
|
||||||
|
// ---
|
||||||
|
// summary: Adds a Repo to a Star List
|
||||||
|
// parameters:
|
||||||
|
// - name: name
|
||||||
|
// in: path
|
||||||
|
// description: name of the star list
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo to star
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo to star
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// responses:
|
||||||
|
// "201":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "401":
|
||||||
|
// "$ref": "#/responses/unauthorized"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "501":
|
||||||
|
// "$ref": "#/responses/featureDisabled"
|
||||||
|
err := ctx.Starlist.AddRepo(ctx, ctx.Repo.Repository.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "AddRepo", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Status(http.StatusCreated)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveReoFromStarList removes a Repo from a Star List
|
||||||
|
func RemoveRepoFromStarList(ctx *context.APIContext) {
|
||||||
|
// swagger:operation DELETE /user/starlist/{name}/{owner}/{repo} user userRemoveRepoFromStarList
|
||||||
|
// ---
|
||||||
|
// summary: Removes a Repo from a Star List
|
||||||
|
// parameters:
|
||||||
|
// - name: name
|
||||||
|
// in: path
|
||||||
|
// description: name of the star list
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: owner
|
||||||
|
// in: path
|
||||||
|
// description: owner of the repo to star
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// - name: repo
|
||||||
|
// in: path
|
||||||
|
// description: name of the repo to star
|
||||||
|
// type: string
|
||||||
|
// required: true
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// responses:
|
||||||
|
// "204":
|
||||||
|
// "$ref": "#/responses/empty"
|
||||||
|
// "401":
|
||||||
|
// "$ref": "#/responses/unauthorized"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "501":
|
||||||
|
// "$ref": "#/responses/featureDisabled"
|
||||||
|
err := ctx.Starlist.RemoveRepo(ctx, ctx.Repo.Repository.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "RemoveRepo", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ctx.Status(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStarListByID get a star list by id
|
||||||
|
func GetStarListByID(ctx *context.APIContext) {
|
||||||
|
// swagger:operation GET /starlist/{id} user userGetStarListByID
|
||||||
|
// ---
|
||||||
|
// summary: Get a star list by id
|
||||||
|
// parameters:
|
||||||
|
// - name: id
|
||||||
|
// in: path
|
||||||
|
// description: id of the star list to get
|
||||||
|
// type: integer
|
||||||
|
// format: int64
|
||||||
|
// required: true
|
||||||
|
// produces:
|
||||||
|
// - application/json
|
||||||
|
// responses:
|
||||||
|
// "200":
|
||||||
|
// "$ref": "#/responses/StarList"
|
||||||
|
// "403":
|
||||||
|
// "$ref": "#/responses/forbidden"
|
||||||
|
// "404":
|
||||||
|
// "$ref": "#/responses/notFound"
|
||||||
|
// "501":
|
||||||
|
// "$ref": "#/responses/featureDisabled"
|
||||||
|
starList, err := repo_model.GetStarListByID(ctx, ctx.ParamsInt64(":id"))
|
||||||
|
if err != nil {
|
||||||
|
if repo_model.IsErrStarListNotFound(err) {
|
||||||
|
ctx.NotFound("GetStarListByID", err)
|
||||||
|
} else {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "GetStarListByID", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !starList.HasAccess(ctx.Doer) {
|
||||||
|
ctx.NotFound("GetStarListByID", repo_model.ErrStarListNotFound{ID: ctx.ParamsInt64(":id")})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starList.LoadUser(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starList.LoadRepositoryCount(ctx, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(http.StatusInternalServerError, "LoadRepositoryCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusOK, convert.ToStarList(ctx, starList, ctx.Doer))
|
||||||
|
}
|
47
routers/web/repo/star_list.go
Normal file
47
routers/web/repo/star_list.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package repo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/forms"
|
||||||
|
)
|
||||||
|
|
||||||
|
func StarListPost(ctx *context.Context) {
|
||||||
|
form := web.GetForm(ctx).(*forms.StarListRepoEditForm)
|
||||||
|
|
||||||
|
starListSlice, err := repo_model.GetStarListsByUserID(ctx, ctx.Doer.ID, true)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetStarListsByUserID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starListSlice.LoadRepoIDs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("LoadRepoIDs", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, starList := range starListSlice {
|
||||||
|
if slices.Contains(form.StarListID, starList.ID) {
|
||||||
|
err = starList.AddRepo(ctx, ctx.Repo.Repository.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("StarListAddRepo", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = starList.RemoveRepo(ctx, ctx.Repo.Repository.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("StarListRemoveRepo", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Redirect(ctx.Repo.Repository.Link())
|
||||||
|
}
|
|
@ -237,6 +237,30 @@ func prepareUserProfileTabData(ctx *context.Context, showPrivate bool, profileDb
|
||||||
}
|
}
|
||||||
|
|
||||||
total = int(count)
|
total = int(count)
|
||||||
|
|
||||||
|
if !setting.Repository.DisableStarLists {
|
||||||
|
starLists, err := repo_model.GetStarListsByUserID(ctx, ctx.ContextUser.ID, ctx.ContextUser.IsSameUser(ctx.Doer))
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetUserStarListsByUserID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, list := range starLists {
|
||||||
|
list.User = ctx.ContextUser
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starLists.LoadRepositoryCount(ctx, ctx.Doer)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("StarListsLoadRepositoryCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data["StarLists"] = starLists
|
||||||
|
ctx.Data["StarListEditRedirect"] = fmt.Sprintf("%s?tab=stars", ctx.ContextUser.HomeLink())
|
||||||
|
ctx.Data["EditStarListURL"] = fmt.Sprintf("%s/-/starlist_edit", ctx.ContextUser.HomeLink())
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data["StarListsEnabled"] = !setting.Repository.DisableStarLists
|
||||||
case "watching":
|
case "watching":
|
||||||
repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
|
repos, count, err = repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{
|
||||||
ListOptions: db.ListOptions{
|
ListOptions: db.ListOptions{
|
||||||
|
|
191
routers/web/user/star_list.go
Normal file
191
routers/web/user/star_list.go
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/base"
|
||||||
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
"code.gitea.io/gitea/modules/web"
|
||||||
|
shared_user "code.gitea.io/gitea/routers/web/shared/user"
|
||||||
|
"code.gitea.io/gitea/services/context"
|
||||||
|
"code.gitea.io/gitea/services/forms"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tplStarListRepos base.TplName = "user/starlist/repos"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ShowStarList(ctx *context.Context) {
|
||||||
|
if setting.Repository.DisableStarLists {
|
||||||
|
ctx.NotFound("", fmt.Errorf(""))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
shared_user.PrepareContextForProfileBigAvatar(ctx)
|
||||||
|
|
||||||
|
err := shared_user.LoadHeaderCount(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("LoadHeaderCount", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
name := ctx.Params("name")
|
||||||
|
|
||||||
|
page := ctx.FormInt("page")
|
||||||
|
if page <= 0 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
keyword := ctx.FormTrim("q")
|
||||||
|
ctx.Data["Keyword"] = keyword
|
||||||
|
|
||||||
|
language := ctx.FormTrim("language")
|
||||||
|
ctx.Data["Language"] = language
|
||||||
|
|
||||||
|
pagingNum := setting.UI.User.RepoPagingNum
|
||||||
|
|
||||||
|
starList, err := repo_model.GetStarListByName(ctx, ctx.ContextUser.ID, name)
|
||||||
|
if err != nil {
|
||||||
|
if repo_model.IsErrStarListNotFound(err) {
|
||||||
|
ctx.NotFound("", fmt.Errorf(""))
|
||||||
|
} else {
|
||||||
|
ctx.ServerError("GetStarListByName", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !starList.HasAccess(ctx.Doer) {
|
||||||
|
ctx.NotFound("", fmt.Errorf(""))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starList.LoadUser(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("LoadUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
repos, count, err := repo_model.SearchRepository(ctx, &repo_model.SearchRepoOptions{Actor: ctx.Doer, StarListID: starList.ID, Keyword: keyword, Language: language})
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("SearchRepository", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Data["Repos"] = repos
|
||||||
|
ctx.Data["Total"] = count
|
||||||
|
|
||||||
|
pager := context.NewPagination(int(count), pagingNum, page, 5)
|
||||||
|
pager.SetDefaultParams(ctx)
|
||||||
|
ctx.Data["Page"] = pager
|
||||||
|
|
||||||
|
ctx.Data["TabName"] = "stars"
|
||||||
|
ctx.Data["Title"] = starList.Name
|
||||||
|
ctx.Data["CurrentStarList"] = starList
|
||||||
|
ctx.Data["PageIsProfileStarList"] = true
|
||||||
|
ctx.Data["StarListEditRedirect"] = starList.Link()
|
||||||
|
ctx.Data["ShowStarListEditButtons"] = ctx.ContextUser.IsSameUser(ctx.Doer)
|
||||||
|
ctx.Data["EditStarListURL"] = fmt.Sprintf("%s/-/starlist_edit", ctx.ContextUser.HomeLink())
|
||||||
|
|
||||||
|
ctx.HTML(http.StatusOK, tplStarListRepos)
|
||||||
|
}
|
||||||
|
|
||||||
|
func editStarList(ctx *context.Context, form forms.EditStarListForm) {
|
||||||
|
starList, err := repo_model.GetStarListByID(ctx, form.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetStarListByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starList.LoadUser(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("LoadUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the doer is the owner of the list
|
||||||
|
if ctx.Doer.ID != starList.UserID {
|
||||||
|
ctx.Flash.Error("Not the same user")
|
||||||
|
ctx.Redirect(starList.Link())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starList.EditData(ctx, form.Name, form.Description, form.Private)
|
||||||
|
if err != nil {
|
||||||
|
if repo_model.IsErrStarListExists(err) {
|
||||||
|
ctx.Flash.Error(ctx.Tr("starlist.name_exists_error", form.Name))
|
||||||
|
ctx.Redirect(starList.Link())
|
||||||
|
} else {
|
||||||
|
ctx.ServerError("EditData", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Redirect(starList.Link())
|
||||||
|
}
|
||||||
|
|
||||||
|
func addStarList(ctx *context.Context, form forms.EditStarListForm) {
|
||||||
|
starList, err := repo_model.CreateStarList(ctx, ctx.Doer.ID, form.Name, form.Description, form.Private)
|
||||||
|
if err != nil {
|
||||||
|
if repo_model.IsErrStarListExists(err) {
|
||||||
|
ctx.Flash.Error(ctx.Tr("starlist.name_exists_error", form.Name))
|
||||||
|
ctx.Redirect(form.CurrentURL)
|
||||||
|
} else {
|
||||||
|
ctx.ServerError("CreateStarList", err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = starList.LoadUser(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("LoadUser", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Redirect(starList.Link())
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteStarList(ctx *context.Context, form forms.EditStarListForm) {
|
||||||
|
starList, err := repo_model.GetStarListByID(ctx, form.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetStarListByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the doer is the owner of the list
|
||||||
|
if ctx.Doer.ID != starList.UserID {
|
||||||
|
ctx.Flash.Error("Not the same user")
|
||||||
|
ctx.Redirect(form.CurrentURL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo_model.DeleteStarListByID(ctx, starList.ID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetStarListByID", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.Flash.Success(ctx.Tr("starlist.delete_success_message", starList.Name))
|
||||||
|
|
||||||
|
ctx.Redirect(fmt.Sprintf("%s?tab=stars", ctx.ContextUser.HomeLink()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func EditStarListPost(ctx *context.Context) {
|
||||||
|
form := *web.GetForm(ctx).(*forms.EditStarListForm)
|
||||||
|
|
||||||
|
switch form.Action {
|
||||||
|
case "edit":
|
||||||
|
editStarList(ctx, form)
|
||||||
|
case "add":
|
||||||
|
addStarList(ctx, form)
|
||||||
|
case "delete":
|
||||||
|
deleteStarList(ctx, form)
|
||||||
|
default:
|
||||||
|
ctx.Flash.Error(fmt.Sprintf("Unknown action %s", form.Action))
|
||||||
|
ctx.Redirect(form.CurrentURL)
|
||||||
|
}
|
||||||
|
}
|
|
@ -763,6 +763,12 @@ func registerRoutes(m *web.Route) {
|
||||||
// ***** END: Admin *****
|
// ***** END: Admin *****
|
||||||
|
|
||||||
m.Group("", func() {
|
m.Group("", func() {
|
||||||
|
m.Group("/{username}", func() {
|
||||||
|
m.Get("", user.UsernameSubRoute)
|
||||||
|
m.Get("/-/starlist/{name}", user.ShowStarList)
|
||||||
|
m.Post("/-/starlist_edit", web.Bind(forms.EditStarListForm{}), user.EditStarListPost)
|
||||||
|
}, context.UserAssignmentWeb())
|
||||||
|
m.Get("/attachments/{uuid}", repo.GetAttachment)
|
||||||
m.Get("/{username}", user.UsernameSubRoute)
|
m.Get("/{username}", user.UsernameSubRoute)
|
||||||
m.Methods("GET, OPTIONS", "/attachments/{uuid}", optionsCorsHandler(), repo.GetAttachment)
|
m.Methods("GET, OPTIONS", "/attachments/{uuid}", optionsCorsHandler(), repo.GetAttachment)
|
||||||
}, ignSignIn)
|
}, ignSignIn)
|
||||||
|
@ -1136,6 +1142,9 @@ func registerRoutes(m *web.Route) {
|
||||||
m.Combo("/fork", reqRepoCodeReader).Get(repo.Fork).
|
m.Combo("/fork", reqRepoCodeReader).Get(repo.Fork).
|
||||||
Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost)
|
Post(web.Bind(forms.CreateRepoForm{}), repo.ForkPost)
|
||||||
}
|
}
|
||||||
|
if !setting.Repository.DisableStarLists {
|
||||||
|
m.Post("/starlistedit", web.Bind(forms.StarListRepoEditForm{}), repo.StarListPost)
|
||||||
|
}
|
||||||
m.Group("/issues", func() {
|
m.Group("/issues", func() {
|
||||||
m.Group("/new", func() {
|
m.Group("/new", func() {
|
||||||
m.Combo("").Get(context.RepoRef(), repo.NewIssue).
|
m.Combo("").Get(context.RepoRef(), repo.NewIssue).
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
issues_model "code.gitea.io/gitea/models/issues"
|
issues_model "code.gitea.io/gitea/models/issues"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
mc "code.gitea.io/gitea/modules/cache"
|
mc "code.gitea.io/gitea/modules/cache"
|
||||||
|
@ -42,6 +43,7 @@ type APIContext struct {
|
||||||
Comment *issues_model.Comment
|
Comment *issues_model.Comment
|
||||||
Org *APIOrganization
|
Org *APIOrganization
|
||||||
Package *Package
|
Package *Package
|
||||||
|
Starlist *repo_model.StarList
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -102,6 +104,18 @@ type APIRedirect struct{}
|
||||||
// swagger:response string
|
// swagger:response string
|
||||||
type APIString string
|
type APIString string
|
||||||
|
|
||||||
|
// APIUnauthorizedError is a unauthorized error response
|
||||||
|
// swagger:response unauthorized
|
||||||
|
type APIUnauthorizedError struct {
|
||||||
|
APIError
|
||||||
|
}
|
||||||
|
|
||||||
|
// APIFeatureDisabledError is a error that is retruned when the given feature is disabled
|
||||||
|
// swagger:response featureDisabled
|
||||||
|
type APIFeatureDisabledError struct {
|
||||||
|
APIError
|
||||||
|
}
|
||||||
|
|
||||||
// APIRepoArchivedError is an error that is raised when an archived repo should be modified
|
// APIRepoArchivedError is an error that is raised when an archived repo should be modified
|
||||||
// swagger:response repoArchivedError
|
// swagger:response repoArchivedError
|
||||||
type APIRepoArchivedError struct {
|
type APIRepoArchivedError struct {
|
||||||
|
|
|
@ -614,6 +614,20 @@ func RepoAssignment(ctx *Context) context.CancelFunc {
|
||||||
if ctx.IsSigned {
|
if ctx.IsSigned {
|
||||||
ctx.Data["IsWatchingRepo"] = repo_model.IsWatching(ctx, ctx.Doer.ID, repo.ID)
|
ctx.Data["IsWatchingRepo"] = repo_model.IsWatching(ctx, ctx.Doer.ID, repo.ID)
|
||||||
ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx, ctx.Doer.ID, repo.ID)
|
ctx.Data["IsStaringRepo"] = repo_model.IsStaring(ctx, ctx.Doer.ID, repo.ID)
|
||||||
|
|
||||||
|
if !setting.Repository.DisableStarLists {
|
||||||
|
starLists, err := repo_model.GetStarListsByUserID(ctx, ctx.Doer.ID, true)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetStarListsByUserID", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
err = starLists.LoadRepoIDs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("LoadRepoIDs", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ctx.Data["StarLists"] = starLists
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if repo.IsFork {
|
if repo.IsFork {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"code.gitea.io/gitea/models/perm"
|
"code.gitea.io/gitea/models/perm"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
)
|
)
|
||||||
|
@ -110,3 +111,24 @@ func ToUserAndPermission(ctx context.Context, user, doer *user_model.User, acces
|
||||||
RoleName: accessMode.String(),
|
RoleName: accessMode.String(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToStarList convert repo_model.StarList to api.StarList
|
||||||
|
func ToStarList(ctx context.Context, starList *repo_model.StarList, doer *user_model.User) *api.StarList {
|
||||||
|
return &api.StarList{
|
||||||
|
ID: starList.ID,
|
||||||
|
Name: starList.Name,
|
||||||
|
Description: starList.Description,
|
||||||
|
IsPrivate: starList.IsPrivate,
|
||||||
|
RepositoryCount: starList.RepositoryCount,
|
||||||
|
User: ToUser(ctx, starList.User, doer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToStarLists convert repo_model.StarListSLice to list of api.StarList
|
||||||
|
func ToStarLists(ctx context.Context, starLists repo_model.StarListSlice, doer *user_model.User) []*api.StarList {
|
||||||
|
apiList := make([]*api.StarList, len(starLists))
|
||||||
|
for i, list := range starLists {
|
||||||
|
apiList[i] = ToStarList(ctx, list, doer)
|
||||||
|
}
|
||||||
|
return apiList
|
||||||
|
}
|
||||||
|
|
|
@ -771,3 +771,8 @@ func (f *DeadlineForm) Validate(req *http.Request, errs binding.Errors) binding.
|
||||||
ctx := context.GetValidateContext(req)
|
ctx := context.GetValidateContext(req)
|
||||||
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Edit the star lits for a repo
|
||||||
|
type StarListRepoEditForm struct {
|
||||||
|
StarListID []int64
|
||||||
|
}
|
||||||
|
|
|
@ -451,3 +451,13 @@ func (f *PackageSettingForm) Validate(req *http.Request, errs binding.Errors) bi
|
||||||
ctx := context.GetValidateContext(req)
|
ctx := context.GetValidateContext(req)
|
||||||
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EditStarListForm form for editing/creating star lists
|
||||||
|
type EditStarListForm struct {
|
||||||
|
CurrentURL string
|
||||||
|
Action string
|
||||||
|
ID int64
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
Private bool
|
||||||
|
}
|
||||||
|
|
|
@ -10,5 +10,12 @@
|
||||||
<a hx-boost="false" class="ui basic label" href="{{$.RepoLink}}/stars">
|
<a hx-boost="false" class="ui basic label" href="{{$.RepoLink}}/stars">
|
||||||
{{CountFmt .Repository.NumStars}}
|
{{CountFmt .Repository.NumStars}}
|
||||||
</a>
|
</a>
|
||||||
|
{{if $.StarLists}}
|
||||||
|
<button type="button" class="ui compact small basic button show-modal no-border-radius-left no-margin-left" data-modal="#repo-star-list-modal">{{svg "octicon-pencil"}}</button>
|
||||||
|
{{end}}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
{{if not $.DisableStars}}
|
||||||
|
{{template "repo/starlistmodal" .}}
|
||||||
|
{{end}}
|
||||||
|
|
30
templates/repo/starlistmodal.tmpl
Normal file
30
templates/repo/starlistmodal.tmpl
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<div class="ui small modal" id="repo-star-list-modal">
|
||||||
|
<div class="header">
|
||||||
|
{{ctx.Locale.Tr "starlist.list_header"}}
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<form class="ui form" action="{{$.RepoLink}}/starlistedit" method="POST">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
|
||||||
|
{{range .StarLists}}
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<label for="star_list_id">{{.Name}}</label>
|
||||||
|
<input name="star_list_id" value="{{.ID}}" type="checkbox" {{if .ContainsRepoID $.Repository.ID}}checked{{end}}>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<div class="text right actions">
|
||||||
|
<button class="ui small basic cancel button">
|
||||||
|
{{svg "octicon-x"}}
|
||||||
|
{{ctx.Locale.Tr "cancel"}}
|
||||||
|
</button>
|
||||||
|
<button class="ui primary small approve button">
|
||||||
|
{{svg "fontawesome-save"}}
|
||||||
|
{{ctx.Locale.Tr "save"}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -8,6 +8,9 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="ui twelve wide column tw-mb-4">
|
<div class="ui twelve wide column tw-mb-4">
|
||||||
{{template "user/overview/header" .}}
|
{{template "user/overview/header" .}}
|
||||||
|
|
||||||
|
{{template "base/alert" .}}
|
||||||
|
|
||||||
{{if eq .TabName "activity"}}
|
{{if eq .TabName "activity"}}
|
||||||
{{if .ContextUser.KeepActivityPrivate}}
|
{{if .ContextUser.KeepActivityPrivate}}
|
||||||
<div class="ui info message">
|
<div class="ui info message">
|
||||||
|
@ -17,6 +20,9 @@
|
||||||
{{template "user/heatmap" .}}
|
{{template "user/heatmap" .}}
|
||||||
{{template "user/dashboard/feeds" .}}
|
{{template "user/dashboard/feeds" .}}
|
||||||
{{else if eq .TabName "stars"}}
|
{{else if eq .TabName "stars"}}
|
||||||
|
{{if and .StarListsEnabled (or .StarLists (.ContextUser.IsSameUser .SignedUser))}}
|
||||||
|
{{template "user/starlist/list" .}}
|
||||||
|
{{end}}
|
||||||
<div class="stars">
|
<div class="stars">
|
||||||
{{template "shared/repo_search" .}}
|
{{template "shared/repo_search" .}}
|
||||||
{{template "explore/repo_list" .}}
|
{{template "explore/repo_list" .}}
|
||||||
|
@ -54,3 +60,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{template "base/footer" .}}
|
{{template "base/footer" .}}
|
||||||
|
|
||||||
|
{{if and .StarListsEnabled (.ContextUser.IsSameUser .SignedUser)}}
|
||||||
|
{{template "user/starlist/edit" .}}
|
||||||
|
{{end}}
|
||||||
|
|
51
templates/user/starlist/edit.tmpl
Normal file
51
templates/user/starlist/edit.tmpl
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<div class="ui small modal" id="edit-star-list-modal">
|
||||||
|
<div class="header">
|
||||||
|
{{if .CurrentStarList}}
|
||||||
|
{{ctx.Locale.Tr "starlist.edit_header"}}
|
||||||
|
{{else}}
|
||||||
|
{{ctx.Locale.Tr "starlist.add_header"}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<form class="ui form" action="{{.EditStarListURL}}" method="post">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
|
||||||
|
<input name="current_url" type="hidden" value="{{.StarListEditRedirect}}">
|
||||||
|
|
||||||
|
{{if .CurrentStarList}}
|
||||||
|
<input name="action" type="hidden" value="edit">
|
||||||
|
<input name="id" type="hidden" value="{{.CurrentStarList.ID}}">
|
||||||
|
{{else}}
|
||||||
|
<input name="action" type="hidden" value="add">
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="name">{{ctx.Locale.Tr "starlist.name_label"}}</label>
|
||||||
|
<input id="name" name="name" placeholder="{{ctx.Locale.Tr "starlist.name_placeholder"}}" {{if .CurrentStarList}}value="{{.CurrentStarList.Name}}"{{end}} maxlength="50" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<label for="description">{{ctx.Locale.Tr "starlist.description_label"}}</label>
|
||||||
|
<textarea id="description" name="description" rows="2" placeholder="{{ctx.Locale.Tr "starlist.description_placeholder"}}" maxlength="255">{{if .CurrentStarList}}{{.CurrentStarList.Description}}{{end}}</textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<div class="ui checkbox">
|
||||||
|
<label for="private">{{ctx.Locale.Tr "starlist.private"}}</label>
|
||||||
|
<input name="private" type="checkbox" {{if .CurrentStarList}}{{if .CurrentStarList.IsPrivate}}checked{{end}}{{end}}>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text right actions">
|
||||||
|
<button class="ui small basic cancel button">
|
||||||
|
{{svg "octicon-x"}}
|
||||||
|
{{ctx.Locale.Tr "cancel"}}
|
||||||
|
</button>
|
||||||
|
<button class="ui primary small approve button">
|
||||||
|
{{svg "fontawesome-save"}}
|
||||||
|
{{if .CurrentStarList}}{{ctx.Locale.Tr "save"}}{{else}}{{ctx.Locale.Tr "add"}}{{end}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
37
templates/user/starlist/list.tmpl
Normal file
37
templates/user/starlist/list.tmpl
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<div class="star-list-box">
|
||||||
|
<h4 class="ui top attached header">
|
||||||
|
{{ctx.Locale.Tr "starlist.list_header"}}
|
||||||
|
{{if .ContextUser.IsSameUser .SignedUser}}
|
||||||
|
<div class="ui right">
|
||||||
|
<button class="ui tiny button green show-modal" data-modal="#edit-star-list-modal">{{ctx.Locale.Tr "add"}}</button>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</h4>
|
||||||
|
<div class="ui attached segment">
|
||||||
|
{{if .StarLists}}
|
||||||
|
<div class="flex-list">
|
||||||
|
{{range .StarLists}}
|
||||||
|
<div class="flex-item">
|
||||||
|
<a class="flex-item-header star-list-item" href="{{.Link}}">
|
||||||
|
<div class="flex-item-title">
|
||||||
|
{{.Name}}
|
||||||
|
{{if .IsPrivate}}
|
||||||
|
<span class="ui basic label">{{ctx.Locale.Tr "repo.desc.private"}}</span>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
<div class="flex-item-trailing">
|
||||||
|
{{if eq .RepositoryCount 1}}
|
||||||
|
<span class="text grey flex-text-inline">{{ctx.Locale.Tr "repository_count_1"}}</span>
|
||||||
|
{{else}}
|
||||||
|
<span class="text grey flex-text-inline">{{ctx.Locale.Tr "repository_count_n" .RepositoryCount}}</span>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
{{ctx.Locale.Tr "starlist.no_star_lists_text"}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</div>
|
62
templates/user/starlist/repos.tmpl
Normal file
62
templates/user/starlist/repos.tmpl
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
{{template "base/head" .}}
|
||||||
|
<div role="main" aria-label="{{.Title}}" class="page-content user profile">
|
||||||
|
<div class="ui container">
|
||||||
|
<div class="ui stackable grid">
|
||||||
|
<div class="ui four wide column">
|
||||||
|
{{template "shared/user/profile_big_avatar" .}}
|
||||||
|
</div>
|
||||||
|
<div class="ui twelve wide column">
|
||||||
|
<div class="gt-mb-4">
|
||||||
|
{{template "user/overview/header" .}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{template "base/alert" .}}
|
||||||
|
|
||||||
|
<div class="list-header">
|
||||||
|
<div class="star-list-header-info">
|
||||||
|
<h1>{{.CurrentStarList.Name}}</h1>
|
||||||
|
<p>{{.CurrentStarList.Description}}</p>
|
||||||
|
</div>
|
||||||
|
{{if .ContextUser.IsSameUser .SignedUser}}
|
||||||
|
<div>
|
||||||
|
<button class="ui button green show-modal" data-modal="#edit-star-list-modal">{{ctx.Locale.Tr "edit"}}</button>
|
||||||
|
<button class="ui button red show-modal" data-modal="#delete-star-list-modal">{{ctx.Locale.Tr "remove"}}</button>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{template "shared/repo_search" .}}
|
||||||
|
{{template "explore/repo_list" .}}
|
||||||
|
{{template "base/paginate" .}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{template "base/footer" .}}
|
||||||
|
|
||||||
|
{{if .ContextUser.IsSameUser .SignedUser}}
|
||||||
|
{{template "user/starlist/edit" .}}
|
||||||
|
|
||||||
|
<div class="ui small modal" id="delete-star-list-modal">
|
||||||
|
<div class="header">
|
||||||
|
{{ctx.Locale.Tr "starlist.delete_header"}}
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<div class="ui warning message">
|
||||||
|
{{ctx.Locale.Tr "repo.settings.delete_notices_1" | SafeHTML}}
|
||||||
|
</div>
|
||||||
|
<form class="ui form" action="{{.EditStarListURL}}" method="post">
|
||||||
|
{{.CsrfTokenHtml}}
|
||||||
|
|
||||||
|
<input name="current_url" type="hidden" value="{{.StarListEditRedirect}}">
|
||||||
|
<input name="id" type="hidden" value="{{.CurrentStarList.ID}}">
|
||||||
|
<input name="action" type="hidden" value="delete">
|
||||||
|
|
||||||
|
<div class="text right actions">
|
||||||
|
<button class="ui cancel button">{{ctx.Locale.Tr "cancel"}}</button>
|
||||||
|
<button class="ui red button">{{ctx.Locale.Tr "remove"}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
|
@ -46,6 +46,8 @@ func TestAPIExposedSettings(t *testing.T) {
|
||||||
MirrorsDisabled: !setting.Mirror.Enabled,
|
MirrorsDisabled: !setting.Mirror.Enabled,
|
||||||
HTTPGitDisabled: setting.Repository.DisableHTTPGit,
|
HTTPGitDisabled: setting.Repository.DisableHTTPGit,
|
||||||
MigrationsDisabled: setting.Repository.DisableMigrations,
|
MigrationsDisabled: setting.Repository.DisableMigrations,
|
||||||
|
StarsDisabled: setting.Repository.DisableStars,
|
||||||
|
StarListsDisabled: setting.Repository.DisableStarLists,
|
||||||
TimeTrackingDisabled: false,
|
TimeTrackingDisabled: false,
|
||||||
LFSDisabled: !setting.LFS.StartServer,
|
LFSDisabled: !setting.LFS.StartServer,
|
||||||
}, repo)
|
}, repo)
|
||||||
|
|
303
tests/integration/api_star_list_test.go
Normal file
303
tests/integration/api_star_list_test.go
Normal file
|
@ -0,0 +1,303 @@
|
||||||
|
// Copyright 2024 The Forgejo Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/models/unittest"
|
||||||
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAPIGetStarLists(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
assert.NoError(t, unittest.LoadFixtures())
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
t.Run("CurrentUser", func(t *testing.T) {
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starlists?token=%s", token)), http.StatusOK)
|
||||||
|
|
||||||
|
var starLists []api.StarList
|
||||||
|
DecodeJSON(t, resp, &starLists)
|
||||||
|
|
||||||
|
assert.Len(t, starLists, 2)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), starLists[0].ID)
|
||||||
|
assert.Equal(t, "First List", starLists[0].Name)
|
||||||
|
assert.Equal(t, "Description for first List", starLists[0].Description)
|
||||||
|
assert.False(t, starLists[0].IsPrivate)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(2), starLists[1].ID)
|
||||||
|
assert.Equal(t, "Second List", starLists[1].Name)
|
||||||
|
assert.Equal(t, "This is private", starLists[1].Description)
|
||||||
|
assert.True(t, starLists[1].IsPrivate)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("OtherUser", func(t *testing.T) {
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/starlists", user.Name)), http.StatusOK)
|
||||||
|
|
||||||
|
var starLists []api.StarList
|
||||||
|
DecodeJSON(t, resp, &starLists)
|
||||||
|
|
||||||
|
assert.Len(t, starLists, 1)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), starLists[0].ID)
|
||||||
|
assert.Equal(t, "First List", starLists[0].Name)
|
||||||
|
assert.Equal(t, "Description for first List", starLists[0].Description)
|
||||||
|
assert.False(t, starLists[0].IsPrivate)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIGetStarListRepoInfo(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
assert.NoError(t, unittest.LoadFixtures())
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/user/starlists/repoinfo/%s/%s?token=%s", repo.OwnerName, repo.Name, token)
|
||||||
|
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", urlStr), http.StatusOK)
|
||||||
|
|
||||||
|
var repoInfo []api.StarListRepoInfo
|
||||||
|
DecodeJSON(t, resp, &repoInfo)
|
||||||
|
|
||||||
|
assert.Len(t, repoInfo, 2)
|
||||||
|
|
||||||
|
assert.True(t, repoInfo[0].Contains)
|
||||||
|
assert.Equal(t, int64(1), repoInfo[0].StarList.ID)
|
||||||
|
|
||||||
|
assert.False(t, repoInfo[1].Contains)
|
||||||
|
assert.Equal(t, int64(2), repoInfo[1].StarList.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPICreateStarList(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
assert.NoError(t, unittest.LoadFixtures())
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
t.Run("Success", func(t *testing.T) {
|
||||||
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/user/starlists?token=%s", token), &api.CreateEditStarListOptions{
|
||||||
|
Name: "New Name",
|
||||||
|
Description: "Hello",
|
||||||
|
IsPrivate: false,
|
||||||
|
})
|
||||||
|
resp := MakeRequest(t, req, http.StatusCreated)
|
||||||
|
|
||||||
|
var starList api.StarList
|
||||||
|
DecodeJSON(t, resp, &starList)
|
||||||
|
|
||||||
|
assert.Equal(t, "New Name", starList.Name)
|
||||||
|
assert.Equal(t, "Hello", starList.Description)
|
||||||
|
assert.False(t, starList.IsPrivate)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("ExistingName", func(t *testing.T) {
|
||||||
|
req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/user/starlists?token=%s", token), &api.CreateEditStarListOptions{
|
||||||
|
Name: "First List",
|
||||||
|
Description: "Hello",
|
||||||
|
IsPrivate: false,
|
||||||
|
})
|
||||||
|
MakeRequest(t, req, http.StatusBadRequest)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIGetStarListByName(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
assert.NoError(t, unittest.LoadFixtures())
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
t.Run("CurrentUserPublic", func(t *testing.T) {
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starlist/%s?token=%s", url.PathEscape("First List"), token)), http.StatusOK)
|
||||||
|
|
||||||
|
var starList api.StarList
|
||||||
|
DecodeJSON(t, resp, &starList)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), starList.ID)
|
||||||
|
assert.Equal(t, "First List", starList.Name)
|
||||||
|
assert.Equal(t, "Description for first List", starList.Description)
|
||||||
|
assert.False(t, starList.IsPrivate)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("OtherUserPublic", func(t *testing.T) {
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/starlist/%s", user.Name, url.PathEscape("First List"))), http.StatusOK)
|
||||||
|
|
||||||
|
var starList api.StarList
|
||||||
|
DecodeJSON(t, resp, &starList)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), starList.ID)
|
||||||
|
assert.Equal(t, "First List", starList.Name)
|
||||||
|
assert.Equal(t, "Description for first List", starList.Description)
|
||||||
|
assert.False(t, starList.IsPrivate)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CurrentUserPrivate", func(t *testing.T) {
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", fmt.Sprintf("/api/v1/user/starlist/%s?token=%s", url.PathEscape("Second List"), token)), http.StatusOK)
|
||||||
|
|
||||||
|
var starList api.StarList
|
||||||
|
DecodeJSON(t, resp, &starList)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(2), starList.ID)
|
||||||
|
assert.Equal(t, "Second List", starList.Name)
|
||||||
|
assert.Equal(t, "This is private", starList.Description)
|
||||||
|
assert.True(t, starList.IsPrivate)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("OtherUserPublic", func(t *testing.T) {
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", fmt.Sprintf("/api/v1/users/%s/starlist/%s", user.Name, url.PathEscape("Second List"))), http.StatusNotFound)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIEditStarList(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
assert.NoError(t, unittest.LoadFixtures())
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
req := NewRequestWithJSON(t, "PATCH", fmt.Sprintf("/api/v1/user/starlist/%s?token=%s", url.PathEscape("First List"), token), &api.CreateEditStarListOptions{
|
||||||
|
Name: "New Name",
|
||||||
|
Description: "Hello",
|
||||||
|
IsPrivate: false,
|
||||||
|
})
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
var starList api.StarList
|
||||||
|
DecodeJSON(t, resp, &starList)
|
||||||
|
|
||||||
|
assert.Equal(t, "New Name", starList.Name)
|
||||||
|
assert.Equal(t, "Hello", starList.Description)
|
||||||
|
assert.False(t, starList.IsPrivate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIDeleteStarList(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
assert.NoError(t, unittest.LoadFixtures())
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "DELETE", fmt.Sprintf("/api/v1/user/starlist/%s?token=%s", url.PathEscape("First List"), token)), http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIStarListGetRepos(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
assert.NoError(t, unittest.LoadFixtures())
|
||||||
|
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
t.Run("CurrentUser", func(t *testing.T) {
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/user/starlist/%s/repos?token=%s", url.PathEscape("First List"), token)
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", urlStr), http.StatusOK)
|
||||||
|
|
||||||
|
var repoList []*api.Repository
|
||||||
|
DecodeJSON(t, resp, &repoList)
|
||||||
|
|
||||||
|
assert.Len(t, repoList, 1)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), repoList[0].ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("OtherUser", func(t *testing.T) {
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/users/%s/starlist/%s/repos", user.Name, url.PathEscape("First List"))
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", urlStr), http.StatusOK)
|
||||||
|
|
||||||
|
var repoList []*api.Repository
|
||||||
|
DecodeJSON(t, resp, &repoList)
|
||||||
|
|
||||||
|
assert.Len(t, repoList, 1)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), repoList[0].ID)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIStarListAddRepo(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
assert.NoError(t, unittest.LoadFixtures())
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 4})
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/user/starlist/%s/%s/%s?token=%s", url.PathEscape("First List"), repo.OwnerName, repo.Name, token)
|
||||||
|
MakeRequest(t, NewRequest(t, "PUT", urlStr), http.StatusCreated)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIStarListRemoveRepo(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
assert.NoError(t, unittest.LoadFixtures())
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1})
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteUser, auth_model.AccessTokenScopeWriteRepository)
|
||||||
|
|
||||||
|
urlStr := fmt.Sprintf("/api/v1/user/starlist/%s/%s/%s?token=%s", url.PathEscape("First List"), repo.OwnerName, repo.Name, token)
|
||||||
|
MakeRequest(t, NewRequest(t, "DELETE", urlStr), http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIGetStarListByID(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
assert.NoError(t, unittest.LoadFixtures())
|
||||||
|
|
||||||
|
t.Run("PublicList", func(t *testing.T) {
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", "/api/v1/starlist/1"), http.StatusOK)
|
||||||
|
|
||||||
|
var starList api.StarList
|
||||||
|
DecodeJSON(t, resp, &starList)
|
||||||
|
|
||||||
|
assert.Equal(t, int64(1), starList.ID)
|
||||||
|
assert.Equal(t, "First List", starList.Name)
|
||||||
|
assert.Equal(t, "Description for first List", starList.Description)
|
||||||
|
assert.False(t, starList.IsPrivate)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PrivateList", func(t *testing.T) {
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/starlist/2"), http.StatusNotFound)
|
||||||
|
})
|
||||||
|
}
|
|
@ -754,3 +754,21 @@ It needs some tricks to tweak the left/right borders with active state */
|
||||||
color: var(--color-green-dark-2);
|
color: var(--color-green-dark-2);
|
||||||
border-color: var(--color-green-dark-2);
|
border-color: var(--color-green-dark-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-border-radius {
|
||||||
|
border-top-right-radius: 0 !important;
|
||||||
|
border-bottom-right-radius: 0 !important;
|
||||||
|
border-top-left-radius: 0 !important;
|
||||||
|
border-bottom-left-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-border-radius-left {
|
||||||
|
border-top-right-radius: var(--border-radius) !important;
|
||||||
|
border-bottom-right-radius: var(--border-radius) !important;
|
||||||
|
border-top-left-radius: 0 !important;
|
||||||
|
border-bottom-left-radius: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-margin-left {
|
||||||
|
margin-left: -1px !important;
|
||||||
|
}
|
||||||
|
|
|
@ -161,3 +161,16 @@
|
||||||
#pronouns-dropdown, #pronouns-custom {
|
#pronouns-dropdown, #pronouns-custom {
|
||||||
width: 140px;
|
width: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.star-list-box {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.star-list-item {
|
||||||
|
width: 100%;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.star-list-header-info {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue