forked from vikunja/vikunja
feat(projects): cleanup namespace leftovers
This commit is contained in:
parent
16de7cd591
commit
fef253312c
@ -191,7 +191,7 @@ var userCreateCmd = &cobra.Command{
|
||||
err = models.CreateNewProjectForUser(s, newUser)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
log.Fatalf("Error creating new namespace for user: %s", err)
|
||||
log.Fatalf("Error creating new project for user: %s", err)
|
||||
}
|
||||
|
||||
if err := s.Commit(); err != nil {
|
||||
|
@ -17,7 +17,6 @@
|
||||
package integrations
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"code.vikunja.io/api/pkg/models"
|
||||
@ -26,32 +25,27 @@ import (
|
||||
)
|
||||
|
||||
// This tests the following behaviour:
|
||||
// 1. A namespace should not be editable if it is archived.
|
||||
// 1. With the exception being to un-archive it.
|
||||
// 2. A project which belongs to an archived namespace cannot be edited.
|
||||
// 2. A project which belongs to an archived project cannot be edited.
|
||||
// 3. An archived project should not be editable.
|
||||
// 1. Except for un-archiving it.
|
||||
// 4. It is not possible to un-archive a project individually if its namespace is archived.
|
||||
// 5. Creating new projects on an archived namespace should not work.
|
||||
// 4. It is not possible to un-archive a project individually if its parent project is archived.
|
||||
// 5. Creating new child projects in an archived project should not work.
|
||||
// 6. Creating new tasks on an archived project should not work.
|
||||
// 7. Creating new tasks on a project who's namespace is archived should not work.
|
||||
// 7. Creating new tasks on a project whose parent project is archived should not work.
|
||||
// 8. Editing tasks on an archived project should not work.
|
||||
// 9. Editing tasks on a project who's namespace is archived should not work.
|
||||
// 10. Archived namespaces should not appear in the project with all namespaces.
|
||||
// 11. Archived projects should not appear in the project with all projects.
|
||||
// 12. Projects who's namespace is archived should not appear in the project with all projects.
|
||||
// 9. Editing tasks on a project whose parent project is archived should not work.
|
||||
// 11. Archived projects should not appear in the list with all projects.
|
||||
// 12. Projects whose parent project is archived should not appear in the project with all projects.
|
||||
//
|
||||
// All of this is tested through integration tests because it's not yet clear if this will be implemented directly
|
||||
// or with some kind of middleware.
|
||||
//
|
||||
// Maybe the inheritance of projects from namespaces could be solved with some kind of is_archived_inherited flag -
|
||||
// Maybe the inheritance of projects from parents could be solved with some kind of is_archived_inherited flag -
|
||||
// that way I'd only need to implement the checking on a project level and update the flag for all projects once the
|
||||
// namespace is archived. The archived flag would then be used to not accedentially unarchive projects which were
|
||||
// already individually archived when the namespace was archived.
|
||||
// Should still test it all though.
|
||||
// project is archived. The archived flag would then be used to not accedentially unarchive projects which were
|
||||
// already individually archived when the parent project was archived.
|
||||
//
|
||||
// Namespace 16 is archived
|
||||
// Project 21 belongs to namespace 16
|
||||
// Project 21 belongs to project 16
|
||||
// Project 22 is archived individually
|
||||
|
||||
func TestArchived(t *testing.T) {
|
||||
@ -62,13 +56,6 @@ func TestArchived(t *testing.T) {
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testNamespaceHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
return &models.Namespace{}
|
||||
},
|
||||
t: t,
|
||||
}
|
||||
testTaskHandler := webHandlerTest{
|
||||
user: &testuser1,
|
||||
strFunc: func() handler.CObject {
|
||||
@ -105,34 +92,6 @@ func TestArchived(t *testing.T) {
|
||||
t: t,
|
||||
}
|
||||
|
||||
t.Run("namespace", func(t *testing.T) {
|
||||
t.Run("not editable", func(t *testing.T) {
|
||||
_, err := testNamespaceHandler.testUpdateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"TestIpsum","is_archived":true}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
|
||||
})
|
||||
t.Run("unarchivable", func(t *testing.T) {
|
||||
rec, err := testNamespaceHandler.testUpdateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"TestIpsum","is_archived":false}`)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"is_archived":false`)
|
||||
})
|
||||
t.Run("no new projects", func(t *testing.T) {
|
||||
_, err := testProjectHandler.testCreateWithUser(nil, map[string]string{"namespace": "16"}, `{"title":"Lorem"}`)
|
||||
assert.Error(t, err)
|
||||
assertHandlerErrorCode(t, err, models.ErrCodeNamespaceIsArchived)
|
||||
})
|
||||
t.Run("should not appear in the project", func(t *testing.T) {
|
||||
rec, err := testNamespaceHandler.testReadAllWithUser(nil, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.NotContains(t, rec.Body.String(), `"title":"Archived testnamespace16"`)
|
||||
})
|
||||
t.Run("should appear in the project if explicitly requested", func(t *testing.T) {
|
||||
rec, err := testNamespaceHandler.testReadAllWithUser(url.Values{"is_archived": []string{"true"}}, nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, rec.Body.String(), `"title":"Archived testnamespace16"`)
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("project", func(t *testing.T) {
|
||||
|
||||
taskTests := func(taskID string, errCode int, t *testing.T) {
|
||||
@ -194,8 +153,8 @@ func TestArchived(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// The project belongs to an archived namespace
|
||||
t.Run("archived namespace", func(t *testing.T) {
|
||||
// The project belongs to an archived parent project
|
||||
t.Run("archived parent project", func(t *testing.T) {
|
||||
t.Run("not editable", func(t *testing.T) {
|
||||
_, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"TestIpsum","is_archived":true}`)
|
||||
assert.Error(t, err)
|
||||
|
@ -34,9 +34,6 @@ const (
|
||||
// UserCountKey is the name of the key we use to store total users in redis
|
||||
UserCountKey = `usercount`
|
||||
|
||||
// NamespaceCountKey is the name of the key we use to store the amount of total namespaces in redis
|
||||
NamespaceCountKey = `namespacecount`
|
||||
|
||||
// TaskCountKey is the name of the key we use to store the amount of total tasks in redis
|
||||
TaskCountKey = `taskcount`
|
||||
|
||||
@ -89,18 +86,6 @@ func InitMetrics() {
|
||||
log.Criticalf("Could not register metrics for %s: %s", UserCountKey, err)
|
||||
}
|
||||
|
||||
// Register total Namespaces count metric
|
||||
err = registry.Register(promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
Name: "vikunja_namespace_count",
|
||||
Help: "The total number of namespaces on this instance",
|
||||
}, func() float64 {
|
||||
count, _ := GetCount(NamespaceCountKey)
|
||||
return float64(count)
|
||||
}))
|
||||
if err != nil {
|
||||
log.Criticalf("Could not register metrics for %s: %s", NamespaceCountKey, err)
|
||||
}
|
||||
|
||||
// Register total Tasks count metric
|
||||
err = registry.Register(promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
Name: "vikunja_task_count",
|
||||
|
@ -255,65 +255,37 @@ func (err ErrProjectIsArchived) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeProjectIsArchived, Message: "This project is archived. Editing or creating new tasks is not possible."}
|
||||
}
|
||||
|
||||
// ErrProjectCannotBelongToAPseudoNamespace represents an error where a project cannot belong to a pseudo namespace
|
||||
type ErrProjectCannotBelongToAPseudoNamespace struct {
|
||||
ProjectID int64
|
||||
NamespaceID int64
|
||||
// ErrProjectCannotBelongToAPseudoParentProject represents an error where a project cannot belong to a pseudo project
|
||||
type ErrProjectCannotBelongToAPseudoParentProject struct {
|
||||
ProjectID int64
|
||||
ParentProjectID int64
|
||||
}
|
||||
|
||||
// IsErrProjectCannotBelongToAPseudoNamespace checks if an error is a project is archived error.
|
||||
func IsErrProjectCannotBelongToAPseudoNamespace(err error) bool {
|
||||
_, ok := err.(*ErrProjectCannotBelongToAPseudoNamespace)
|
||||
// IsErrProjectCannotBelongToAPseudoParentProject checks if an error is a project is archived error.
|
||||
func IsErrProjectCannotBelongToAPseudoParentProject(err error) bool {
|
||||
_, ok := err.(*ErrProjectCannotBelongToAPseudoParentProject)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err *ErrProjectCannotBelongToAPseudoNamespace) Error() string {
|
||||
return fmt.Sprintf("Project cannot belong to a pseudo namespace [ProjectID: %d, NamespaceID: %d]", err.ProjectID, err.NamespaceID)
|
||||
func (err *ErrProjectCannotBelongToAPseudoParentProject) Error() string {
|
||||
return fmt.Sprintf("Project cannot belong to a pseudo parent project [ProjectID: %d, ParentProjectID: %d]", err.ProjectID, err.ParentProjectID)
|
||||
}
|
||||
|
||||
// ErrCodeProjectCannotBelongToAPseudoNamespace holds the unique world-error code of this error
|
||||
const ErrCodeProjectCannotBelongToAPseudoNamespace = 3009
|
||||
// ErrCodeProjectCannotBelongToAPseudoParentProject holds the unique world-error code of this error
|
||||
const ErrCodeProjectCannotBelongToAPseudoParentProject = 3009
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err *ErrProjectCannotBelongToAPseudoNamespace) HTTPError() web.HTTPError {
|
||||
func (err *ErrProjectCannotBelongToAPseudoParentProject) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusPreconditionFailed,
|
||||
Code: ErrCodeProjectCannotBelongToAPseudoNamespace,
|
||||
Message: "This project cannot belong a dynamically generated namespace.",
|
||||
Code: ErrCodeProjectCannotBelongToAPseudoParentProject,
|
||||
Message: "This project cannot belong a dynamically generated project.",
|
||||
}
|
||||
}
|
||||
|
||||
// ErrProjectMustBelongToANamespace represents an error where a project must belong to a namespace
|
||||
type ErrProjectMustBelongToANamespace struct {
|
||||
ProjectID int64
|
||||
NamespaceID int64
|
||||
}
|
||||
|
||||
// IsErrProjectMustBelongToANamespace checks if an error is a project must belong to a namespace error.
|
||||
func IsErrProjectMustBelongToANamespace(err error) bool {
|
||||
_, ok := err.(*ErrProjectMustBelongToANamespace)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err *ErrProjectMustBelongToANamespace) Error() string {
|
||||
return fmt.Sprintf("Project must belong to a namespace [ProjectID: %d, NamespaceID: %d]", err.ProjectID, err.NamespaceID)
|
||||
}
|
||||
|
||||
// ErrCodeProjectMustBelongToANamespace holds the unique world-error code of this error
|
||||
const ErrCodeProjectMustBelongToANamespace = 3010
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err *ErrProjectMustBelongToANamespace) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusPreconditionFailed,
|
||||
Code: ErrCodeProjectMustBelongToANamespace,
|
||||
Message: "This project must belong to a namespace.",
|
||||
}
|
||||
}
|
||||
|
||||
// ================
|
||||
// Project task errors
|
||||
// ================
|
||||
// ==============
|
||||
// Project errors
|
||||
// ==============
|
||||
|
||||
// ErrTaskCannotBeEmpty represents a "ErrProjectDoesNotExist" kind of error. Used if the project does not exist.
|
||||
type ErrTaskCannotBeEmpty struct{}
|
||||
@ -875,203 +847,6 @@ func (err ErrUserAlreadyAssigned) HTTPError() web.HTTPError {
|
||||
}
|
||||
}
|
||||
|
||||
// ErrReminderRelativeToMissing represents an error where a task has a relative reminder without reference date
|
||||
type ErrReminderRelativeToMissing struct {
|
||||
TaskID int64
|
||||
}
|
||||
|
||||
// IsErrReminderRelativeToMissing checks if an error is ErrReminderRelativeToMissing.
|
||||
func IsErrReminderRelativeToMissing(err error) bool {
|
||||
_, ok := err.(ErrReminderRelativeToMissing)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrReminderRelativeToMissing) Error() string {
|
||||
return fmt.Sprintf("Task [TaskID: %v] has a relative reminder without relative_to", err.TaskID)
|
||||
}
|
||||
|
||||
// ErrCodeRelationDoesNotExist holds the unique world-error code of this error
|
||||
const ErrCodeReminderRelativeToMissing = 4022
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrReminderRelativeToMissing) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{
|
||||
HTTPCode: http.StatusBadRequest,
|
||||
Code: ErrCodeReminderRelativeToMissing,
|
||||
Message: "Please provide what the reminder date is relative to",
|
||||
}
|
||||
}
|
||||
|
||||
// =================
|
||||
// Namespace errors
|
||||
// =================
|
||||
|
||||
// ErrNamespaceDoesNotExist represents a "ErrNamespaceDoesNotExist" kind of error. Used if the namespace does not exist.
|
||||
type ErrNamespaceDoesNotExist struct {
|
||||
ID int64
|
||||
}
|
||||
|
||||
// IsErrNamespaceDoesNotExist checks if an error is a ErrNamespaceDoesNotExist.
|
||||
func IsErrNamespaceDoesNotExist(err error) bool {
|
||||
_, ok := err.(ErrNamespaceDoesNotExist)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrNamespaceDoesNotExist) Error() string {
|
||||
return fmt.Sprintf("Namespace does not exist [ID: %d]", err.ID)
|
||||
}
|
||||
|
||||
// ErrCodeNamespaceDoesNotExist holds the unique world-error code of this error
|
||||
const ErrCodeNamespaceDoesNotExist = 5001
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrNamespaceDoesNotExist) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeNamespaceDoesNotExist, Message: "Namespace not found."}
|
||||
}
|
||||
|
||||
// ErrUserDoesNotHaveAccessToNamespace represents an error, where the user is not the owner of that namespace (used i.e. when deleting a namespace)
|
||||
type ErrUserDoesNotHaveAccessToNamespace struct {
|
||||
NamespaceID int64
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// IsErrUserDoesNotHaveAccessToNamespace checks if an error is a ErrNamespaceDoesNotExist.
|
||||
func IsErrUserDoesNotHaveAccessToNamespace(err error) bool {
|
||||
_, ok := err.(ErrUserDoesNotHaveAccessToNamespace)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUserDoesNotHaveAccessToNamespace) Error() string {
|
||||
return fmt.Sprintf("User does not have access to the namespace [NamespaceID: %d, UserID: %d]", err.NamespaceID, err.UserID)
|
||||
}
|
||||
|
||||
// ErrCodeUserDoesNotHaveAccessToNamespace holds the unique world-error code of this error
|
||||
const ErrCodeUserDoesNotHaveAccessToNamespace = 5003
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrUserDoesNotHaveAccessToNamespace) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeUserDoesNotHaveAccessToNamespace, Message: "This user does not have access to the namespace."}
|
||||
}
|
||||
|
||||
// ErrNamespaceNameCannotBeEmpty represents an error, where a namespace name is empty.
|
||||
type ErrNamespaceNameCannotBeEmpty struct {
|
||||
NamespaceID int64
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// IsErrNamespaceNameCannotBeEmpty checks if an error is a ErrNamespaceDoesNotExist.
|
||||
func IsErrNamespaceNameCannotBeEmpty(err error) bool {
|
||||
_, ok := err.(ErrNamespaceNameCannotBeEmpty)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrNamespaceNameCannotBeEmpty) Error() string {
|
||||
return fmt.Sprintf("Namespace name cannot be empty [NamespaceID: %d, UserID: %d]", err.NamespaceID, err.UserID)
|
||||
}
|
||||
|
||||
// ErrCodeNamespaceNameCannotBeEmpty holds the unique world-error code of this error
|
||||
const ErrCodeNamespaceNameCannotBeEmpty = 5006
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrNamespaceNameCannotBeEmpty) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusBadRequest, Code: ErrCodeNamespaceNameCannotBeEmpty, Message: "The namespace name cannot be empty."}
|
||||
}
|
||||
|
||||
// ErrNeedToHaveNamespaceReadAccess represents an error, where the user is not the owner of that namespace (used i.e. when deleting a namespace)
|
||||
type ErrNeedToHaveNamespaceReadAccess struct {
|
||||
NamespaceID int64
|
||||
UserID int64
|
||||
}
|
||||
|
||||
// IsErrNeedToHaveNamespaceReadAccess checks if an error is a ErrNamespaceDoesNotExist.
|
||||
func IsErrNeedToHaveNamespaceReadAccess(err error) bool {
|
||||
_, ok := err.(ErrNeedToHaveNamespaceReadAccess)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrNeedToHaveNamespaceReadAccess) Error() string {
|
||||
return fmt.Sprintf("User does not have access to that namespace [NamespaceID: %d, UserID: %d]", err.NamespaceID, err.UserID)
|
||||
}
|
||||
|
||||
// ErrCodeNeedToHaveNamespaceReadAccess holds the unique world-error code of this error
|
||||
const ErrCodeNeedToHaveNamespaceReadAccess = 5009
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrNeedToHaveNamespaceReadAccess) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeNeedToHaveNamespaceReadAccess, Message: "You need to have namespace read access to do this."}
|
||||
}
|
||||
|
||||
// ErrTeamDoesNotHaveAccessToNamespace represents an error, where the Team is not the owner of that namespace (used i.e. when deleting a namespace)
|
||||
type ErrTeamDoesNotHaveAccessToNamespace struct {
|
||||
NamespaceID int64
|
||||
TeamID int64
|
||||
}
|
||||
|
||||
// IsErrTeamDoesNotHaveAccessToNamespace checks if an error is a ErrNamespaceDoesNotExist.
|
||||
func IsErrTeamDoesNotHaveAccessToNamespace(err error) bool {
|
||||
_, ok := err.(ErrTeamDoesNotHaveAccessToNamespace)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrTeamDoesNotHaveAccessToNamespace) Error() string {
|
||||
return fmt.Sprintf("Team does not have access to that namespace [NamespaceID: %d, TeamID: %d]", err.NamespaceID, err.TeamID)
|
||||
}
|
||||
|
||||
// ErrCodeTeamDoesNotHaveAccessToNamespace holds the unique world-error code of this error
|
||||
const ErrCodeTeamDoesNotHaveAccessToNamespace = 5010
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrTeamDoesNotHaveAccessToNamespace) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusForbidden, Code: ErrCodeTeamDoesNotHaveAccessToNamespace, Message: "You need to have access to this namespace to do this."}
|
||||
}
|
||||
|
||||
// ErrUserAlreadyHasNamespaceAccess represents an error where a user already has access to a namespace
|
||||
type ErrUserAlreadyHasNamespaceAccess struct {
|
||||
UserID int64
|
||||
NamespaceID int64
|
||||
}
|
||||
|
||||
// IsErrUserAlreadyHasNamespaceAccess checks if an error is ErrUserAlreadyHasNamespaceAccess.
|
||||
func IsErrUserAlreadyHasNamespaceAccess(err error) bool {
|
||||
_, ok := err.(ErrUserAlreadyHasNamespaceAccess)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrUserAlreadyHasNamespaceAccess) Error() string {
|
||||
return fmt.Sprintf("User already has access to that namespace. [User ID: %d, Namespace ID: %d]", err.UserID, err.NamespaceID)
|
||||
}
|
||||
|
||||
// ErrCodeUserAlreadyHasNamespaceAccess holds the unique world-error code of this error
|
||||
const ErrCodeUserAlreadyHasNamespaceAccess = 5011
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrUserAlreadyHasNamespaceAccess) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusConflict, Code: ErrCodeUserAlreadyHasNamespaceAccess, Message: "This user already has access to this namespace."}
|
||||
}
|
||||
|
||||
// ErrNamespaceIsArchived represents an error where a namespace is archived
|
||||
type ErrNamespaceIsArchived struct {
|
||||
NamespaceID int64
|
||||
}
|
||||
|
||||
// IsErrNamespaceIsArchived checks if an error is a .
|
||||
func IsErrNamespaceIsArchived(err error) bool {
|
||||
_, ok := err.(ErrNamespaceIsArchived)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (err ErrNamespaceIsArchived) Error() string {
|
||||
return fmt.Sprintf("Namespace is archived [NamespaceID: %d]", err.NamespaceID)
|
||||
}
|
||||
|
||||
// ErrCodeNamespaceIsArchived holds the unique world-error code of this error
|
||||
const ErrCodeNamespaceIsArchived = 5012
|
||||
|
||||
// HTTPError holds the http error description
|
||||
func (err ErrNamespaceIsArchived) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusPreconditionFailed, Code: ErrCodeNamespaceIsArchived, Message: "This namespaces is archived. Editing or creating new projects is not possible."}
|
||||
}
|
||||
|
||||
// ============
|
||||
// Team errors
|
||||
// ============
|
||||
@ -1081,7 +856,7 @@ type ErrTeamNameCannotBeEmpty struct {
|
||||
TeamID int64
|
||||
}
|
||||
|
||||
// IsErrTeamNameCannotBeEmpty checks if an error is a ErrNamespaceDoesNotExist.
|
||||
// IsErrTeamNameCannotBeEmpty checks if an error is a ErrTeamNameCannotBeEmpty.
|
||||
func IsErrTeamNameCannotBeEmpty(err error) bool {
|
||||
_, ok := err.(ErrTeamNameCannotBeEmpty)
|
||||
return ok
|
||||
@ -1122,7 +897,7 @@ func (err ErrTeamDoesNotExist) HTTPError() web.HTTPError {
|
||||
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTeamDoesNotExist, Message: "This team does not exist."}
|
||||
}
|
||||
|
||||
// ErrTeamAlreadyHasAccess represents an error where a team already has access to a project/namespace
|
||||
// ErrTeamAlreadyHasAccess represents an error where a team already has access to a project
|
||||
type ErrTeamAlreadyHasAccess struct {
|
||||
TeamID int64
|
||||
ID int64
|
||||
@ -1222,7 +997,7 @@ func (err ErrTeamDoesNotHaveAccessToProject) HTTPError() web.HTTPError {
|
||||
// User <-> Project errors
|
||||
// ====================
|
||||
|
||||
// ErrUserAlreadyHasAccess represents an error where a user already has access to a project/namespace
|
||||
// ErrUserAlreadyHasAccess represents an error where a user already has access to a project
|
||||
type ErrUserAlreadyHasAccess struct {
|
||||
UserID int64
|
||||
ProjectID int64
|
||||
|
@ -278,30 +278,6 @@ func (l *ProjectSharedWithTeamEvent) Name() string {
|
||||
return "project.shared.team"
|
||||
}
|
||||
|
||||
// NamespaceSharedWithUserEvent represents an event where a namespace has been shared with a user
|
||||
type NamespaceSharedWithUserEvent struct {
|
||||
Namespace *Namespace
|
||||
User *user.User
|
||||
Doer web.Auth
|
||||
}
|
||||
|
||||
// Name defines the name for NamespaceSharedWithUserEvent
|
||||
func (n *NamespaceSharedWithUserEvent) Name() string {
|
||||
return "namespace.shared.user"
|
||||
}
|
||||
|
||||
// NamespaceSharedWithTeamEvent represents an event where a namespace has been shared with a team
|
||||
type NamespaceSharedWithTeamEvent struct {
|
||||
Namespace *Namespace
|
||||
Team *Team
|
||||
Doer web.Auth
|
||||
}
|
||||
|
||||
// Name defines the name for NamespaceSharedWithTeamEvent
|
||||
func (n *NamespaceSharedWithTeamEvent) Name() string {
|
||||
return "namespace.shared.team"
|
||||
}
|
||||
|
||||
/////////////////
|
||||
// Team Events //
|
||||
/////////////////
|
||||
|
@ -57,12 +57,12 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
|
||||
defer dumpWriter.Close()
|
||||
|
||||
// Get the data
|
||||
err = exportProjectsAndTasks(s, u, dumpWriter)
|
||||
taskIDs, err := exportProjectsAndTasks(s, u, dumpWriter)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Task attachment files
|
||||
err = exportTaskAttachments(s, u, dumpWriter)
|
||||
err = exportTaskAttachments(s, u, dumpWriter, taskIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -121,51 +121,35 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
|
||||
})
|
||||
}
|
||||
|
||||
func exportProjectsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
|
||||
|
||||
namspaces, _, _, err := (&Namespace{IsArchived: true}).ReadAll(s, u, "", -1, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
namespaceIDs := []int64{}
|
||||
namespaces := []*NamespaceWithProjectsAndTasks{}
|
||||
projectMap := make(map[int64]*ProjectWithTasksAndBuckets)
|
||||
projectIDs := []int64{}
|
||||
for _, n := range namspaces.([]*NamespaceWithProjects) {
|
||||
if n.ID < 1 {
|
||||
// Don't include filters
|
||||
continue
|
||||
}
|
||||
|
||||
nn := &NamespaceWithProjectsAndTasks{
|
||||
Namespace: n.Namespace,
|
||||
Projects: []*ProjectWithTasksAndBuckets{},
|
||||
}
|
||||
|
||||
for _, l := range n.Projects {
|
||||
ll := &ProjectWithTasksAndBuckets{
|
||||
Project: *l,
|
||||
BackgroundFileID: l.BackgroundFileID,
|
||||
Tasks: []*TaskWithComments{},
|
||||
}
|
||||
nn.Projects = append(nn.Projects, ll)
|
||||
projectMap[l.ID] = ll
|
||||
projectIDs = append(projectIDs, l.ID)
|
||||
}
|
||||
|
||||
namespaceIDs = append(namespaceIDs, n.ID)
|
||||
namespaces = append(namespaces, nn)
|
||||
}
|
||||
|
||||
if len(namespaceIDs) == 0 {
|
||||
return nil
|
||||
}
|
||||
func exportProjectsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (taskIDs []int64, err error) {
|
||||
|
||||
// Get all projects
|
||||
projects, err := getProjectsForNamespaces(s, namespaceIDs, true)
|
||||
rawProjectsMap, _, _, err := getRawProjectsForUser(
|
||||
s,
|
||||
&projectOptions{
|
||||
search: "",
|
||||
user: u,
|
||||
page: 0,
|
||||
perPage: -1,
|
||||
getArchived: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return taskIDs, err
|
||||
}
|
||||
|
||||
if len(rawProjectsMap) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
projects := []*Project{}
|
||||
projectsMap := make(map[int64]*ProjectWithTasksAndBuckets, len(rawProjectsMap))
|
||||
projectIDs := []int64{}
|
||||
for _, p := range rawProjectsMap {
|
||||
projects = append(projects, p)
|
||||
projectsMap[p.ID] = &ProjectWithTasksAndBuckets{
|
||||
Project: *p,
|
||||
}
|
||||
projectIDs = append(projectIDs, p.ID)
|
||||
}
|
||||
|
||||
tasks, _, _, err := getTasksForProjects(s, projects, u, &taskOptions{
|
||||
@ -173,7 +157,7 @@ func exportProjectsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err
|
||||
perPage: -1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return taskIDs, err
|
||||
}
|
||||
|
||||
taskMap := make(map[int64]*TaskWithComments, len(tasks))
|
||||
@ -181,11 +165,12 @@ func exportProjectsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err
|
||||
taskMap[t.ID] = &TaskWithComments{
|
||||
Task: *t,
|
||||
}
|
||||
if _, exists := projectMap[t.ProjectID]; !exists {
|
||||
if _, exists := projectsMap[t.ProjectID]; !exists {
|
||||
log.Debugf("[User Data Export] Project %d does not exist for task %d, omitting", t.ProjectID, t.ID)
|
||||
continue
|
||||
}
|
||||
projectMap[t.ProjectID].Tasks = append(projectMap[t.ProjectID].Tasks, taskMap[t.ID])
|
||||
projectsMap[t.ProjectID].Tasks = append(projectsMap[t.ProjectID].Tasks, taskMap[t.ID])
|
||||
taskIDs = append(taskIDs, t.ID)
|
||||
}
|
||||
|
||||
comments := []*TaskComment{}
|
||||
@ -212,43 +197,22 @@ func exportProjectsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err
|
||||
}
|
||||
|
||||
for _, b := range buckets {
|
||||
if _, exists := projectMap[b.ProjectID]; !exists {
|
||||
if _, exists := projectsMap[b.ProjectID]; !exists {
|
||||
log.Debugf("[User Data Export] Project %d does not exist for bucket %d, omitting", b.ProjectID, b.ID)
|
||||
continue
|
||||
}
|
||||
projectMap[b.ProjectID].Buckets = append(projectMap[b.ProjectID].Buckets, b)
|
||||
projectsMap[b.ProjectID].Buckets = append(projectsMap[b.ProjectID].Buckets, b)
|
||||
}
|
||||
|
||||
data, err := json.Marshal(namespaces)
|
||||
data, err := json.Marshal(projects)
|
||||
if err != nil {
|
||||
return err
|
||||
return taskIDs, err
|
||||
}
|
||||
|
||||
return utils.WriteBytesToZip("data.json", data, wr)
|
||||
return taskIDs, utils.WriteBytesToZip("data.json", data, wr)
|
||||
}
|
||||
|
||||
func exportTaskAttachments(s *xorm.Session, u *user.User, wr *zip.Writer) (err error) {
|
||||
projects, _, _, err := getRawProjectsForUser(
|
||||
s,
|
||||
&projectOptions{
|
||||
user: u,
|
||||
page: -1,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tasks, _, _, err := getRawTasksForProjects(s, projects, u, &taskOptions{page: -1})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
taskIDs := []int64{}
|
||||
for _, t := range tasks {
|
||||
taskIDs = append(taskIDs, t.ID)
|
||||
}
|
||||
|
||||
func exportTaskAttachments(s *xorm.Session, u *user.User, wr *zip.Writer, taskIDs []int64) (err error) {
|
||||
tas, err := getTaskAttachmentsByTaskIDs(s, taskIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -35,8 +35,6 @@ import (
|
||||
func RegisterListeners() {
|
||||
events.RegisterListener((&ProjectCreatedEvent{}).Name(), &IncreaseProjectCounter{})
|
||||
events.RegisterListener((&ProjectDeletedEvent{}).Name(), &DecreaseProjectCounter{})
|
||||
events.RegisterListener((&NamespaceCreatedEvent{}).Name(), &IncreaseNamespaceCounter{})
|
||||
events.RegisterListener((&NamespaceDeletedEvent{}).Name(), &DecreaseNamespaceCounter{})
|
||||
events.RegisterListener((&TaskCreatedEvent{}).Name(), &IncreaseTaskCounter{})
|
||||
events.RegisterListener((&TaskDeletedEvent{}).Name(), &DecreaseTaskCounter{})
|
||||
events.RegisterListener((&TeamDeletedEvent{}).Name(), &DecreaseTeamCounter{})
|
||||
@ -540,37 +538,6 @@ func (s *SendProjectCreatedNotification) Handle(msg *message.Message) (err error
|
||||
return nil
|
||||
}
|
||||
|
||||
//////
|
||||
// Namespace events
|
||||
|
||||
// IncreaseNamespaceCounter represents a listener
|
||||
type IncreaseNamespaceCounter struct {
|
||||
}
|
||||
|
||||
// Name defines the name for the IncreaseNamespaceCounter listener
|
||||
func (s *IncreaseNamespaceCounter) Name() string {
|
||||
return "namespace.counter.increase"
|
||||
}
|
||||
|
||||
// Hanlde is executed when the event IncreaseNamespaceCounter listens on is fired
|
||||
func (s *IncreaseNamespaceCounter) Handle(_ *message.Message) (err error) {
|
||||
return keyvalue.IncrBy(metrics.NamespaceCountKey, 1)
|
||||
}
|
||||
|
||||
// DecreaseNamespaceCounter represents a listener
|
||||
type DecreaseNamespaceCounter struct {
|
||||
}
|
||||
|
||||
// Name defines the name for the DecreaseNamespaceCounter listener
|
||||
func (s *DecreaseNamespaceCounter) Name() string {
|
||||
return "namespace.counter.decrease"
|
||||
}
|
||||
|
||||
// Handle is executed when the event DecreaseNamespaceCounter listens on is fired
|
||||
func (s *DecreaseNamespaceCounter) Handle(_ *message.Message) (err error) {
|
||||
return keyvalue.DecrBy(metrics.NamespaceCountKey, 1)
|
||||
}
|
||||
|
||||
///////
|
||||
// Team Events
|
||||
|
||||
|
@ -44,10 +44,7 @@ func GetTables() []interface{} {
|
||||
&Team{},
|
||||
&TeamMember{},
|
||||
&TeamProject{},
|
||||
&TeamNamespace{},
|
||||
&Namespace{},
|
||||
&ProjectUser{},
|
||||
&NamespaceUser{},
|
||||
&TaskAssginee{},
|
||||
&Label{},
|
||||
&LabelTask{},
|
||||
|
@ -37,7 +37,7 @@ import (
|
||||
type Project struct {
|
||||
// The unique, numeric id of this project.
|
||||
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"project"`
|
||||
// The title of the project. You'll see this in the namespace overview.
|
||||
// The title of the project. You'll see this in the overview.
|
||||
Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250"`
|
||||
// The description of the project.
|
||||
Description string `xorm:"longtext null" json:"description"`
|
||||
@ -64,7 +64,7 @@ type Project struct {
|
||||
// Contains a very small version of the project background to use as a blurry preview until the actual background is loaded. Check out https://blurha.sh/ to learn how it works.
|
||||
BackgroundBlurHash string `xorm:"varchar(50) null" json:"background_blur_hash"`
|
||||
|
||||
// True if a project is a favorite. Favorite projects show up in a separate namespace. This value depends on the user making the call to the api.
|
||||
// True if a project is a favorite. Favorite projects show up in a separate parent project. This value depends on the user making the call to the api.
|
||||
IsFavorite bool `xorm:"-" json:"is_favorite"`
|
||||
|
||||
// The subscription status for the user reading this project. You can only read this property, use the subscription endpoints to modify it.
|
||||
@ -114,7 +114,7 @@ var SharedProjectsPseudoProject = &Project{
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
// FavoriteProjectsPseudoProject is a pseudo namespace used to hold favorite projects and tasks
|
||||
// FavoriteProjectsPseudoProject is a pseudo parent project used to hold favorite projects and tasks
|
||||
var FavoriteProjectsPseudoProject = &Project{
|
||||
ID: -2,
|
||||
Title: "Favorites",
|
||||
@ -123,7 +123,7 @@ var FavoriteProjectsPseudoProject = &Project{
|
||||
Updated: time.Now(),
|
||||
}
|
||||
|
||||
// SavedFiltersPseudoProject is a pseudo namespace used to hold saved filters
|
||||
// SavedFiltersPseudoProject is a pseudo parent project used to hold saved filters
|
||||
var SavedFiltersPseudoProject = &Project{
|
||||
ID: -3,
|
||||
Title: "Filters",
|
||||
@ -267,13 +267,11 @@ func (p *Project) ReadOne(s *xorm.Session, a web.Auth) (err error) {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Check if the namespace is archived and set the namespace to archived if it is not already archived individually.
|
||||
|
||||
// Check if the project is archived and set it to archived if it is not already archived individually.
|
||||
if !p.IsArchived {
|
||||
err = p.CheckIsArchived(s)
|
||||
if err != nil {
|
||||
if !IsErrNamespaceIsArchived(err) && !IsErrProjectIsArchived(err) {
|
||||
return
|
||||
}
|
||||
p.IsArchived = true
|
||||
}
|
||||
}
|
||||
@ -561,7 +559,7 @@ func addProjectDetails(s *xorm.Session, projects map[int64]*Project, a web.Auth)
|
||||
return
|
||||
}
|
||||
|
||||
// CheckIsArchived returns an ErrProjectIsArchived or ErrNamespaceIsArchived if the project or any of its parent projects is archived.
|
||||
// CheckIsArchived returns an ErrProjectIsArchived if the project or any of its parent projects is archived.
|
||||
func (p *Project) CheckIsArchived(s *xorm.Session) (err error) {
|
||||
// When creating a new project, we check if the parent is archived
|
||||
if p.ID == 0 {
|
||||
@ -569,14 +567,14 @@ func (p *Project) CheckIsArchived(s *xorm.Session) (err error) {
|
||||
return p.CheckIsArchived(s)
|
||||
}
|
||||
|
||||
p, err := GetProjectSimpleByID(s, p.ID)
|
||||
project, err := GetProjectSimpleByID(s, p.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: parent project
|
||||
|
||||
if p.IsArchived {
|
||||
if project.IsArchived {
|
||||
return ErrProjectIsArchived{ProjectID: p.ID}
|
||||
}
|
||||
|
||||
@ -585,7 +583,7 @@ func (p *Project) CheckIsArchived(s *xorm.Session) (err error) {
|
||||
|
||||
func checkProjectBeforeUpdateOrDelete(s *xorm.Session, project *Project) error {
|
||||
if project.ParentProjectID < 0 {
|
||||
return &ErrProjectCannotBelongToAPseudoNamespace{ProjectID: project.ID, NamespaceID: project.ParentProjectID}
|
||||
return &ErrProjectCannotBelongToAPseudoParentProject{ProjectID: project.ID, ParentProjectID: project.ParentProjectID}
|
||||
}
|
||||
|
||||
// Check if the parent project exists
|
||||
|
@ -28,8 +28,8 @@ import (
|
||||
type ProjectDuplicate struct {
|
||||
// The project id of the project to duplicate
|
||||
ProjectID int64 `json:"-" param:"projectid"`
|
||||
// The target namespace ID
|
||||
NamespaceID int64 `json:"namespace_id,omitempty"`
|
||||
// The target parent project
|
||||
ParentProjectID int64 `json:"parent_project_id,omitempty"`
|
||||
|
||||
// The copied project
|
||||
Project *Project `json:",omitempty"`
|
||||
@ -47,23 +47,27 @@ func (ld *ProjectDuplicate) CanCreate(s *xorm.Session, a web.Auth) (canCreate bo
|
||||
return canRead, err
|
||||
}
|
||||
|
||||
// Namespace exists + user has write access to is (-> can create new projects)
|
||||
ld.Project.NamespaceID = ld.NamespaceID
|
||||
return ld.Project.CanCreate(s, a)
|
||||
if ld.ParentProjectID == 0 { // no parent project
|
||||
return canRead, err
|
||||
}
|
||||
|
||||
// Parent project exists + user has write access to is (-> can create new projects)
|
||||
parent := &Project{ID: ld.ParentProjectID}
|
||||
return parent.CanCreate(s, a)
|
||||
}
|
||||
|
||||
// Create duplicates a project
|
||||
// @Summary Duplicate an existing project
|
||||
// @Description Copies the project, tasks, files, kanban data, assignees, comments, attachments, lables, relations, backgrounds, user/team rights and link shares from one project to a new namespace. The user needs read access in the project and write access in the namespace of the new project.
|
||||
// @Description Copies the project, tasks, files, kanban data, assignees, comments, attachments, lables, relations, backgrounds, user/team rights and link shares from one project to a new one. The user needs read access in the project and write access in the parent of the new project.
|
||||
// @tags project
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param projectID path int true "The project ID to duplicate"
|
||||
// @Param project body models.ProjectDuplicate true "The target namespace which should hold the copied project."
|
||||
// @Param project body models.ProjectDuplicate true "The target parent project which should hold the copied project."
|
||||
// @Success 201 {object} models.ProjectDuplicate "The created project."
|
||||
// @Failure 400 {object} web.HTTPError "Invalid project duplicate object provided."
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the project or namespace"
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to the project or its parent."
|
||||
// @Failure 500 {object} models.Message "Internal error"
|
||||
// @Router /projects/{projectID}/duplicate [put]
|
||||
//
|
||||
@ -153,7 +157,7 @@ func (ld *ProjectDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
|
||||
}
|
||||
|
||||
// Rights / Shares
|
||||
// To keep it simple(r) we will only copy rights which are directly used with the project, no namespace changes.
|
||||
// To keep it simple(r) we will only copy rights which are directly used with the project, not the parent
|
||||
users := []*ProjectUser{}
|
||||
err = s.Where("project_id = ?", ld.ProjectID).Find(&users)
|
||||
if err != nil {
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
package models
|
||||
|
||||
// Right defines the rights users/teams can have for projects/namespaces
|
||||
// Right defines the rights users/teams can have for projects
|
||||
type Right int
|
||||
|
||||
// define unknown right
|
||||
@ -30,7 +30,7 @@ const (
|
||||
RightRead Right = iota
|
||||
// Can write in a like projects and tasks. Cannot create new projects.
|
||||
RightWrite
|
||||
// Can manage a project/namespace, can do everything
|
||||
// Can manage a project, can do everything
|
||||
RightAdmin
|
||||
)
|
||||
|
||||
|
@ -39,7 +39,7 @@ type SavedFilter struct {
|
||||
// The user who owns this filter
|
||||
Owner *user.User `xorm:"-" json:"owner" valid:"-"`
|
||||
|
||||
// True if the filter is a favorite. Favorite filters show up in a separate namespace together with favorite projects.
|
||||
// True if the filter is a favorite. Favorite filters show up in a separate parent project together with favorite projects.
|
||||
IsFavorite bool `xorm:"default false" json:"is_favorite"`
|
||||
|
||||
// A timestamp when this filter was created. You cannot change this value.
|
||||
@ -95,14 +95,14 @@ func getSavedFiltersForUser(s *xorm.Session, auth web.Auth) (filters []*SavedFil
|
||||
|
||||
func (sf *SavedFilter) toProject() *Project {
|
||||
return &Project{
|
||||
ID: getProjectIDFromSavedFilterID(sf.ID),
|
||||
Title: sf.Title,
|
||||
Description: sf.Description,
|
||||
IsFavorite: sf.IsFavorite,
|
||||
Created: sf.Created,
|
||||
Updated: sf.Updated,
|
||||
Owner: sf.Owner,
|
||||
NamespaceID: SavedFiltersPseudoProject.ID,
|
||||
ID: getProjectIDFromSavedFilterID(sf.ID),
|
||||
Title: sf.Title,
|
||||
Description: sf.Description,
|
||||
IsFavorite: sf.IsFavorite,
|
||||
Created: sf.Created,
|
||||
Updated: sf.Updated,
|
||||
Owner: sf.Owner,
|
||||
ParentProjectID: SavedFiltersPseudoProject.ID,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -30,16 +30,15 @@ import (
|
||||
type SubscriptionEntityType int
|
||||
|
||||
const (
|
||||
SubscriptionEntityUnknown = iota
|
||||
SubscriptionEntityNamespace
|
||||
SubscriptionEntityUnknown = iota
|
||||
SubscriptionEntityNamespace // Kept even though not used anymore since we don't want to manually change all ids
|
||||
SubscriptionEntityProject
|
||||
SubscriptionEntityTask
|
||||
)
|
||||
|
||||
const (
|
||||
entityNamespace = `namespace`
|
||||
entityProject = `project`
|
||||
entityTask = `task`
|
||||
entityProject = `project`
|
||||
entityTask = `task`
|
||||
)
|
||||
|
||||
// Subscription represents a subscription for an entity
|
||||
@ -70,8 +69,6 @@ func (sb *Subscription) TableName() string {
|
||||
|
||||
func getEntityTypeFromString(entityType string) SubscriptionEntityType {
|
||||
switch entityType {
|
||||
case entityNamespace:
|
||||
return SubscriptionEntityNamespace
|
||||
case entityProject:
|
||||
return SubscriptionEntityProject
|
||||
case entityTask:
|
||||
@ -84,8 +81,6 @@ func getEntityTypeFromString(entityType string) SubscriptionEntityType {
|
||||
// String returns a human-readable string of an entity
|
||||
func (et SubscriptionEntityType) String() string {
|
||||
switch et {
|
||||
case SubscriptionEntityNamespace:
|
||||
return entityNamespace
|
||||
case SubscriptionEntityProject:
|
||||
return entityProject
|
||||
case SubscriptionEntityTask:
|
||||
@ -96,8 +91,7 @@ func (et SubscriptionEntityType) String() string {
|
||||
}
|
||||
|
||||
func (et SubscriptionEntityType) validate() error {
|
||||
if et == SubscriptionEntityNamespace ||
|
||||
et == SubscriptionEntityProject ||
|
||||
if et == SubscriptionEntityProject ||
|
||||
et == SubscriptionEntityTask {
|
||||
return nil
|
||||
}
|
||||
@ -112,7 +106,7 @@ func (et SubscriptionEntityType) validate() error {
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param entity path string true "The entity the user subscribes to. Can be either `namespace`, `project` or `task`."
|
||||
// @Param entity path string true "The entity the user subscribes to. Can be either `project` or `task`."
|
||||
// @Param entityID path string true "The numeric id of the entity to subscribe to."
|
||||
// @Success 201 {object} models.Subscription "The subscription"
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to subscribe to this entity."
|
||||
@ -153,7 +147,7 @@ func (sb *Subscription) Create(s *xorm.Session, auth web.Auth) (err error) {
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security JWTKeyAuth
|
||||
// @Param entity path string true "The entity the user subscribed to. Can be either `namespace`, `project` or `task`."
|
||||
// @Param entity path string true "The entity the user subscribed to. Can be either `project` or `task`."
|
||||
// @Param entityID path string true "The numeric id of the subscribed entity to."
|
||||
// @Success 200 {object} models.Subscription "The subscription"
|
||||
// @Failure 403 {object} web.HTTPError "The user does not have access to subscribe to this entity."
|
||||
@ -170,45 +164,20 @@ func (sb *Subscription) Delete(s *xorm.Session, auth web.Auth) (err error) {
|
||||
}
|
||||
|
||||
func getSubscriberCondForEntity(entityType SubscriptionEntityType, entityID int64) (cond builder.Cond) {
|
||||
if entityType == SubscriptionEntityNamespace {
|
||||
cond = builder.And(
|
||||
builder.Eq{"entity_id": entityID},
|
||||
builder.Eq{"entity_type": SubscriptionEntityNamespace},
|
||||
)
|
||||
}
|
||||
|
||||
if entityType == SubscriptionEntityProject {
|
||||
cond = builder.Or(
|
||||
builder.And(
|
||||
builder.Eq{"entity_id": entityID},
|
||||
builder.Eq{"entity_type": SubscriptionEntityProject},
|
||||
),
|
||||
builder.And(
|
||||
builder.Eq{"entity_id": builder.
|
||||
Select("namespace_id").
|
||||
From("projects").
|
||||
Where(builder.Eq{"id": entityID}),
|
||||
},
|
||||
builder.Eq{"entity_type": SubscriptionEntityNamespace},
|
||||
),
|
||||
return builder.And(
|
||||
builder.Eq{"entity_id": entityID},
|
||||
builder.Eq{"entity_type": SubscriptionEntityProject},
|
||||
)
|
||||
// TODO: parent?
|
||||
}
|
||||
|
||||
if entityType == SubscriptionEntityTask {
|
||||
cond = builder.Or(
|
||||
return builder.Or(
|
||||
builder.And(
|
||||
builder.Eq{"entity_id": entityID},
|
||||
builder.Eq{"entity_type": SubscriptionEntityTask},
|
||||
),
|
||||
builder.And(
|
||||
builder.Eq{"entity_id": builder.
|
||||
Select("namespace_id").
|
||||
From("projects").
|
||||
Join("INNER", "tasks", "projects.id = tasks.project_id").
|
||||
Where(builder.Eq{"tasks.id": entityID}),
|
||||
},
|
||||
builder.Eq{"entity_type": SubscriptionEntityNamespace},
|
||||
),
|
||||
builder.And(
|
||||
builder.Eq{"entity_id": builder.
|
||||
Select("project_id").
|
||||
@ -225,8 +194,8 @@ func getSubscriberCondForEntity(entityType SubscriptionEntityType, entityID int6
|
||||
|
||||
// GetSubscription returns a matching subscription for an entity and user.
|
||||
// It will return the next parent of a subscription. That means for tasks, it will first look for a subscription for
|
||||
// that task, if there is none it will look for a subscription on the project the task belongs to and if that also
|
||||
// doesn't exist it will check for a subscription for the namespace the project is belonging to.
|
||||
// that task, if there is none it will look for a subscription on the project the task belongs to.
|
||||
// TODO: check parent projects
|
||||
func GetSubscription(s *xorm.Session, entityType SubscriptionEntityType, entityID int64, a web.Auth) (subscription *Subscription, err error) {
|
||||
subs, err := GetSubscriptions(s, entityType, []int64{entityID}, a)
|
||||
if err != nil || len(subs) == 0 {
|
||||
|
@ -30,9 +30,6 @@ func (sb *Subscription) CanCreate(s *xorm.Session, a web.Auth) (can bool, err er
|
||||
sb.EntityType = getEntityTypeFromString(sb.Entity)
|
||||
|
||||
switch sb.EntityType {
|
||||
case SubscriptionEntityNamespace:
|
||||
n := &Namespace{ID: sb.EntityID}
|
||||
can, _, err = n.CanRead(s, a)
|
||||
case SubscriptionEntityProject:
|
||||
l := &Project{ID: sb.EntityID}
|
||||
can, _, err = l.CanRead(s, a)
|
||||
|
@ -237,24 +237,6 @@ func getNativeValueForTaskField(fieldName string, comparator taskFilterComparato
|
||||
|
||||
realFieldName := strings.ReplaceAll(strcase.ToCamel(fieldName), "Id", "ID")
|
||||
|
||||
if realFieldName == "Namespace" {
|
||||
if comparator == taskFilterComparatorIn {
|
||||
vals := strings.Split(value, ",")
|
||||
valueSlice := []interface{}{}
|
||||
for _, val := range vals {
|
||||
v, err := strconv.ParseInt(val, 10, 64)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
valueSlice = append(valueSlice, v)
|
||||
}
|
||||
return nil, valueSlice, nil
|
||||
}
|
||||
|
||||
nativeValue, err = strconv.ParseInt(value, 10, 64)
|
||||
return
|
||||
}
|
||||
|
||||
if realFieldName == "Assignees" {
|
||||
vals := strings.Split(value, ",")
|
||||
valueSlice := append([]string{}, vals...)
|
||||
|
@ -241,7 +241,7 @@ func (t *Team) ReadAll(s *xorm.Session, a web.Auth, search string, page int, per
|
||||
|
||||
// Create is the handler to create a team
|
||||
// @Summary Creates a new team
|
||||
// @Description Creates a new team in a given namespace. The user needs write-access to the namespace.
|
||||
// @Description Creates a new team.
|
||||
// @tags team
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
@ -307,12 +307,6 @@ func (t *Team) Delete(s *xorm.Session, a web.Auth) (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Delete team <-> namespace relations
|
||||
_, err = s.Where("team_id = ?", t.ID).Delete(&TeamNamespace{})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Delete team <-> projects relations
|
||||
_, err = s.Where("team_id = ?", t.ID).Delete(&TeamProject{})
|
||||
if err != nil {
|
||||
|
@ -48,7 +48,6 @@ func SetupTests() {
|
||||
"labels",
|
||||
"link_shares",
|
||||
"projects",
|
||||
"namespaces",
|
||||
"task_assignees",
|
||||
"task_attachments",
|
||||
"task_comments",
|
||||
@ -57,12 +56,10 @@ func SetupTests() {
|
||||
"tasks",
|
||||
"team_projects",
|
||||
"team_members",
|
||||
"team_namespaces",
|
||||
"teams",
|
||||
"users",
|
||||
"user_tokens",
|
||||
"users_projects",
|
||||
"users_namespaces",
|
||||
"buckets",
|
||||
"saved_filters",
|
||||
"subscriptions",
|
||||
|
@ -22,14 +22,11 @@ import (
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
// ProjectUIDs hold all kinds of user IDs from accounts who have somehow access to a project
|
||||
// ProjectUIDs hold all kinds of user IDs from accounts who have access to a project
|
||||
type ProjectUIDs struct {
|
||||
ProjectOwnerID int64 `xorm:"projectOwner"`
|
||||
NamespaceUserID int64 `xorm:"unID"`
|
||||
ProjectUserID int64 `xorm:"ulID"`
|
||||
NamespaceOwnerUserID int64 `xorm:"nOwner"`
|
||||
TeamNamespaceUserID int64 `xorm:"tnUID"`
|
||||
TeamProjectUserID int64 `xorm:"tlUID"`
|
||||
ProjectOwnerID int64 `xorm:"projectOwner"`
|
||||
ProjectUserID int64 `xorm:"ulID"`
|
||||
TeamProjectUserID int64 `xorm:"tlUID"`
|
||||
}
|
||||
|
||||
// ListUsersFromProject returns a list with all users who have access to a project, regardless of the method which gave them access
|
||||
@ -39,39 +36,26 @@ func ListUsersFromProject(s *xorm.Session, l *Project, search string) (users []*
|
||||
|
||||
err = s.
|
||||
Select(`l.owner_id as projectOwner,
|
||||
un.user_id as unID,
|
||||
ul.user_id as ulID,
|
||||
n.owner_id as nOwner,
|
||||
tm.user_id as tnUID,
|
||||
tm2.user_id as tlUID`).
|
||||
Table("projects").
|
||||
Alias("l").
|
||||
// User stuff
|
||||
Join("LEFT", []string{"users_namespaces", "un"}, "un.namespace_id = l.namespace_id").
|
||||
Join("LEFT", []string{"users_projects", "ul"}, "ul.project_id = l.id").
|
||||
Join("LEFT", []string{"namespaces", "n"}, "n.id = l.namespace_id").
|
||||
// Team stuff
|
||||
Join("LEFT", []string{"team_namespaces", "tn"}, " l.namespace_id = tn.namespace_id").
|
||||
Join("LEFT", []string{"team_members", "tm"}, "tm.team_id = tn.team_id").
|
||||
Join("LEFT", []string{"team_projects", "tl"}, "l.id = tl.project_id").
|
||||
Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id").
|
||||
// The actual condition
|
||||
Where(
|
||||
builder.Or(
|
||||
builder.Or(builder.Eq{"ul.right": RightRead}),
|
||||
builder.Or(builder.Eq{"un.right": RightRead}),
|
||||
builder.Or(builder.Eq{"tl.right": RightRead}),
|
||||
builder.Or(builder.Eq{"tn.right": RightRead}),
|
||||
|
||||
builder.Or(builder.Eq{"ul.right": RightWrite}),
|
||||
builder.Or(builder.Eq{"un.right": RightWrite}),
|
||||
builder.Or(builder.Eq{"tl.right": RightWrite}),
|
||||
builder.Or(builder.Eq{"tn.right": RightWrite}),
|
||||
|
||||
builder.Or(builder.Eq{"ul.right": RightAdmin}),
|
||||
builder.Or(builder.Eq{"un.right": RightAdmin}),
|
||||
builder.Or(builder.Eq{"tl.right": RightAdmin}),
|
||||
builder.Or(builder.Eq{"tn.right": RightAdmin}),
|
||||
),
|
||||
builder.Eq{"l.id": l.ID},
|
||||
).
|
||||
@ -85,10 +69,7 @@ func ListUsersFromProject(s *xorm.Session, l *Project, search string) (users []*
|
||||
uidmap[l.OwnerID] = true
|
||||
for _, u := range userids {
|
||||
uidmap[u.ProjectUserID] = true
|
||||
uidmap[u.NamespaceOwnerUserID] = true
|
||||
uidmap[u.NamespaceUserID] = true
|
||||
uidmap[u.TeamProjectUserID] = true
|
||||
uidmap[u.TeamNamespaceUserID] = true
|
||||
}
|
||||
|
||||
uids := make([]int64, 0, len(uidmap))
|
||||
|
@ -243,7 +243,7 @@ func getOrCreateUser(s *xorm.Session, cl *claims, issuer, subject string) (u *us
|
||||
}
|
||||
}
|
||||
|
||||
// And create its namespace
|
||||
// And create their project
|
||||
err = models.CreateNewProjectForUser(s, u)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -62,7 +62,7 @@ func RegisterUser(c echo.Context) error {
|
||||
return handler.HandleHTTPError(err, c)
|
||||
}
|
||||
|
||||
// Add its namespace
|
||||
// Create their initial project
|
||||
err = models.CreateNewProjectForUser(s, newUser)
|
||||
if err != nil {
|
||||
_ = s.Rollback()
|
||||
|
@ -51,10 +51,6 @@ func setupMetrics(a *echo.Group) {
|
||||
metrics.UserCountKey,
|
||||
user.User{},
|
||||
},
|
||||
{
|
||||
metrics.NamespaceCountKey,
|
||||
models.Namespace{},
|
||||
},
|
||||
{
|
||||
metrics.TaskCountKey,
|
||||
models.Task{},
|
||||
|
Loading…
x
Reference in New Issue
Block a user