forked from vikunja/vikunja
feat(projects): cleanup namespace leftovers
This commit is contained in:
parent
c244a0f145
commit
92f0a50996
|
@ -191,7 +191,7 @@ var userCreateCmd = &cobra.Command{
|
||||||
err = models.CreateNewProjectForUser(s, newUser)
|
err = models.CreateNewProjectForUser(s, newUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = s.Rollback()
|
_ = 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 {
|
if err := s.Commit(); err != nil {
|
||||||
|
|
|
@ -17,7 +17,6 @@
|
||||||
package integrations
|
package integrations
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"code.vikunja.io/api/pkg/models"
|
"code.vikunja.io/api/pkg/models"
|
||||||
|
@ -26,32 +25,27 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// This tests the following behaviour:
|
// This tests the following behaviour:
|
||||||
// 1. A namespace should not be editable if it is archived.
|
// 2. A project which belongs to an archived project cannot be edited.
|
||||||
// 1. With the exception being to un-archive it.
|
|
||||||
// 2. A project which belongs to an archived namespace cannot be edited.
|
|
||||||
// 3. An archived project should not be editable.
|
// 3. An archived project should not be editable.
|
||||||
// 1. Except for un-archiving it.
|
// 1. Except for un-archiving it.
|
||||||
// 4. It is not possible to un-archive a project individually if its namespace is archived.
|
// 4. It is not possible to un-archive a project individually if its parent project is archived.
|
||||||
// 5. Creating new projects on an archived namespace should not work.
|
// 5. Creating new child projects in an archived project should not work.
|
||||||
// 6. Creating new tasks on 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.
|
// 8. Editing tasks on an archived project should not work.
|
||||||
// 9. Editing tasks on a project who's namespace is archived should not work.
|
// 9. Editing tasks on a project whose parent project 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 list with all projects.
|
||||||
// 11. Archived projects should not appear in the project with all projects.
|
// 12. Projects whose parent project is archived 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.
|
|
||||||
//
|
//
|
||||||
// All of this is tested through integration tests because it's not yet clear if this will be implemented directly
|
// 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.
|
// 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
|
// 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
|
// project is archived. The archived flag would then be used to not accedentially unarchive projects which were
|
||||||
// already individually archived when the namespace was archived.
|
// already individually archived when the parent project was archived.
|
||||||
// Should still test it all though.
|
|
||||||
//
|
//
|
||||||
// Namespace 16 is archived
|
// Project 21 belongs to project 16
|
||||||
// Project 21 belongs to namespace 16
|
|
||||||
// Project 22 is archived individually
|
// Project 22 is archived individually
|
||||||
|
|
||||||
func TestArchived(t *testing.T) {
|
func TestArchived(t *testing.T) {
|
||||||
|
@ -62,13 +56,6 @@ func TestArchived(t *testing.T) {
|
||||||
},
|
},
|
||||||
t: t,
|
t: t,
|
||||||
}
|
}
|
||||||
testNamespaceHandler := webHandlerTest{
|
|
||||||
user: &testuser1,
|
|
||||||
strFunc: func() handler.CObject {
|
|
||||||
return &models.Namespace{}
|
|
||||||
},
|
|
||||||
t: t,
|
|
||||||
}
|
|
||||||
testTaskHandler := webHandlerTest{
|
testTaskHandler := webHandlerTest{
|
||||||
user: &testuser1,
|
user: &testuser1,
|
||||||
strFunc: func() handler.CObject {
|
strFunc: func() handler.CObject {
|
||||||
|
@ -105,34 +92,6 @@ func TestArchived(t *testing.T) {
|
||||||
t: 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) {
|
t.Run("project", func(t *testing.T) {
|
||||||
|
|
||||||
taskTests := func(taskID string, errCode int, 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
|
// The project belongs to an archived parent project
|
||||||
t.Run("archived namespace", func(t *testing.T) {
|
t.Run("archived parent project", func(t *testing.T) {
|
||||||
t.Run("not editable", 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}`)
|
_, err := testProjectHandler.testUpdateWithUser(nil, map[string]string{"project": "21"}, `{"title":"TestIpsum","is_archived":true}`)
|
||||||
assert.Error(t, err)
|
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 is the name of the key we use to store total users in redis
|
||||||
UserCountKey = `usercount`
|
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 is the name of the key we use to store the amount of total tasks in redis
|
||||||
TaskCountKey = `taskcount`
|
TaskCountKey = `taskcount`
|
||||||
|
|
||||||
|
@ -89,18 +86,6 @@ func InitMetrics() {
|
||||||
log.Criticalf("Could not register metrics for %s: %s", UserCountKey, err)
|
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
|
// Register total Tasks count metric
|
||||||
err = registry.Register(promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
err = registry.Register(promauto.NewGaugeFunc(prometheus.GaugeOpts{
|
||||||
Name: "vikunja_task_count",
|
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."}
|
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
|
// ErrProjectCannotBelongToAPseudoParentProject represents an error where a project cannot belong to a pseudo project
|
||||||
type ErrProjectCannotBelongToAPseudoNamespace struct {
|
type ErrProjectCannotBelongToAPseudoParentProject struct {
|
||||||
ProjectID int64
|
ProjectID int64
|
||||||
NamespaceID int64
|
ParentProjectID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsErrProjectCannotBelongToAPseudoNamespace checks if an error is a project is archived error.
|
// IsErrProjectCannotBelongToAPseudoParentProject checks if an error is a project is archived error.
|
||||||
func IsErrProjectCannotBelongToAPseudoNamespace(err error) bool {
|
func IsErrProjectCannotBelongToAPseudoParentProject(err error) bool {
|
||||||
_, ok := err.(*ErrProjectCannotBelongToAPseudoNamespace)
|
_, ok := err.(*ErrProjectCannotBelongToAPseudoParentProject)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (err *ErrProjectCannotBelongToAPseudoNamespace) Error() string {
|
func (err *ErrProjectCannotBelongToAPseudoParentProject) Error() string {
|
||||||
return fmt.Sprintf("Project cannot belong to a pseudo namespace [ProjectID: %d, NamespaceID: %d]", err.ProjectID, err.NamespaceID)
|
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
|
// ErrCodeProjectCannotBelongToAPseudoParentProject holds the unique world-error code of this error
|
||||||
const ErrCodeProjectCannotBelongToAPseudoNamespace = 3009
|
const ErrCodeProjectCannotBelongToAPseudoParentProject = 3009
|
||||||
|
|
||||||
// HTTPError holds the http error description
|
// HTTPError holds the http error description
|
||||||
func (err *ErrProjectCannotBelongToAPseudoNamespace) HTTPError() web.HTTPError {
|
func (err *ErrProjectCannotBelongToAPseudoParentProject) HTTPError() web.HTTPError {
|
||||||
return web.HTTPError{
|
return web.HTTPError{
|
||||||
HTTPCode: http.StatusPreconditionFailed,
|
HTTPCode: http.StatusPreconditionFailed,
|
||||||
Code: ErrCodeProjectCannotBelongToAPseudoNamespace,
|
Code: ErrCodeProjectCannotBelongToAPseudoParentProject,
|
||||||
Message: "This project cannot belong a dynamically generated namespace.",
|
Message: "This project cannot belong a dynamically generated project.",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrProjectMustBelongToANamespace represents an error where a project must belong to a namespace
|
// ==============
|
||||||
type ErrProjectMustBelongToANamespace struct {
|
// Project errors
|
||||||
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
|
|
||||||
// ================
|
|
||||||
|
|
||||||
// ErrTaskCannotBeEmpty represents a "ErrProjectDoesNotExist" kind of error. Used if the project does not exist.
|
// ErrTaskCannotBeEmpty represents a "ErrProjectDoesNotExist" kind of error. Used if the project does not exist.
|
||||||
type ErrTaskCannotBeEmpty struct{}
|
type ErrTaskCannotBeEmpty struct{}
|
||||||
|
@ -875,176 +847,6 @@ func (err ErrUserAlreadyAssigned) HTTPError() web.HTTPError {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// =================
|
|
||||||
// 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
|
// Team errors
|
||||||
// ============
|
// ============
|
||||||
|
@ -1054,7 +856,7 @@ type ErrTeamNameCannotBeEmpty struct {
|
||||||
TeamID int64
|
TeamID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsErrTeamNameCannotBeEmpty checks if an error is a ErrNamespaceDoesNotExist.
|
// IsErrTeamNameCannotBeEmpty checks if an error is a ErrTeamNameCannotBeEmpty.
|
||||||
func IsErrTeamNameCannotBeEmpty(err error) bool {
|
func IsErrTeamNameCannotBeEmpty(err error) bool {
|
||||||
_, ok := err.(ErrTeamNameCannotBeEmpty)
|
_, ok := err.(ErrTeamNameCannotBeEmpty)
|
||||||
return ok
|
return ok
|
||||||
|
@ -1095,7 +897,7 @@ func (err ErrTeamDoesNotExist) HTTPError() web.HTTPError {
|
||||||
return web.HTTPError{HTTPCode: http.StatusNotFound, Code: ErrCodeTeamDoesNotExist, Message: "This team does not exist."}
|
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 {
|
type ErrTeamAlreadyHasAccess struct {
|
||||||
TeamID int64
|
TeamID int64
|
||||||
ID int64
|
ID int64
|
||||||
|
@ -1195,7 +997,7 @@ func (err ErrTeamDoesNotHaveAccessToProject) HTTPError() web.HTTPError {
|
||||||
// User <-> Project errors
|
// 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 {
|
type ErrUserAlreadyHasAccess struct {
|
||||||
UserID int64
|
UserID int64
|
||||||
ProjectID int64
|
ProjectID int64
|
||||||
|
|
|
@ -104,43 +104,6 @@ func (t *TaskCommentUpdatedEvent) Name() string {
|
||||||
return "task.comment.edited"
|
return "task.comment.edited"
|
||||||
}
|
}
|
||||||
|
|
||||||
//////////////////////
|
|
||||||
// Namespace Events //
|
|
||||||
//////////////////////
|
|
||||||
|
|
||||||
// NamespaceCreatedEvent represents an event where a namespace has been created
|
|
||||||
type NamespaceCreatedEvent struct {
|
|
||||||
Namespace *Namespace
|
|
||||||
Doer web.Auth
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name defines the name for NamespaceCreatedEvent
|
|
||||||
func (n *NamespaceCreatedEvent) Name() string {
|
|
||||||
return "namespace.created"
|
|
||||||
}
|
|
||||||
|
|
||||||
// NamespaceUpdatedEvent represents an event where a namespace has been updated
|
|
||||||
type NamespaceUpdatedEvent struct {
|
|
||||||
Namespace *Namespace
|
|
||||||
Doer web.Auth
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name defines the name for NamespaceUpdatedEvent
|
|
||||||
func (n *NamespaceUpdatedEvent) Name() string {
|
|
||||||
return "namespace.updated"
|
|
||||||
}
|
|
||||||
|
|
||||||
// NamespaceDeletedEvent represents a NamespaceDeletedEvent event
|
|
||||||
type NamespaceDeletedEvent struct {
|
|
||||||
Namespace *Namespace
|
|
||||||
Doer web.Auth
|
|
||||||
}
|
|
||||||
|
|
||||||
// TopicName defines the name for NamespaceDeletedEvent
|
|
||||||
func (t *NamespaceDeletedEvent) Name() string {
|
|
||||||
return "namespace.deleted"
|
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////
|
/////////////////
|
||||||
// Project Events //
|
// Project Events //
|
||||||
/////////////////
|
/////////////////
|
||||||
|
@ -206,30 +169,6 @@ func (l *ProjectSharedWithTeamEvent) Name() string {
|
||||||
return "project.shared.team"
|
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 //
|
// Team Events //
|
||||||
/////////////////
|
/////////////////
|
||||||
|
|
|
@ -57,12 +57,12 @@ func ExportUserData(s *xorm.Session, u *user.User) (err error) {
|
||||||
defer dumpWriter.Close()
|
defer dumpWriter.Close()
|
||||||
|
|
||||||
// Get the data
|
// Get the data
|
||||||
err = exportProjectsAndTasks(s, u, dumpWriter)
|
taskIDs, err := exportProjectsAndTasks(s, u, dumpWriter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Task attachment files
|
// Task attachment files
|
||||||
err = exportTaskAttachments(s, u, dumpWriter)
|
err = exportTaskAttachments(s, u, dumpWriter, taskIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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) {
|
func exportProjectsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (taskIDs []int64, 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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get all projects
|
// 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 {
|
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{
|
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,
|
perPage: -1,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return taskIDs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
taskMap := make(map[int64]*TaskWithComments, len(tasks))
|
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{
|
taskMap[t.ID] = &TaskWithComments{
|
||||||
Task: *t,
|
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)
|
log.Debugf("[User Data Export] Project %d does not exist for task %d, omitting", t.ProjectID, t.ID)
|
||||||
continue
|
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{}
|
comments := []*TaskComment{}
|
||||||
|
@ -212,43 +197,22 @@ func exportProjectsAndTasks(s *xorm.Session, u *user.User, wr *zip.Writer) (err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, b := range buckets {
|
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)
|
log.Debugf("[User Data Export] Project %d does not exist for bucket %d, omitting", b.ProjectID, b.ID)
|
||||||
continue
|
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 {
|
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) {
|
func exportTaskAttachments(s *xorm.Session, u *user.User, wr *zip.Writer, taskIDs []int64) (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)
|
|
||||||
}
|
|
||||||
|
|
||||||
tas, err := getTaskAttachmentsByTaskIDs(s, taskIDs)
|
tas, err := getTaskAttachmentsByTaskIDs(s, taskIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -35,8 +35,6 @@ import (
|
||||||
func RegisterListeners() {
|
func RegisterListeners() {
|
||||||
events.RegisterListener((&ProjectCreatedEvent{}).Name(), &IncreaseProjectCounter{})
|
events.RegisterListener((&ProjectCreatedEvent{}).Name(), &IncreaseProjectCounter{})
|
||||||
events.RegisterListener((&ProjectDeletedEvent{}).Name(), &DecreaseProjectCounter{})
|
events.RegisterListener((&ProjectDeletedEvent{}).Name(), &DecreaseProjectCounter{})
|
||||||
events.RegisterListener((&NamespaceCreatedEvent{}).Name(), &IncreaseNamespaceCounter{})
|
|
||||||
events.RegisterListener((&NamespaceDeletedEvent{}).Name(), &DecreaseNamespaceCounter{})
|
|
||||||
events.RegisterListener((&TaskCreatedEvent{}).Name(), &IncreaseTaskCounter{})
|
events.RegisterListener((&TaskCreatedEvent{}).Name(), &IncreaseTaskCounter{})
|
||||||
events.RegisterListener((&TaskDeletedEvent{}).Name(), &DecreaseTaskCounter{})
|
events.RegisterListener((&TaskDeletedEvent{}).Name(), &DecreaseTaskCounter{})
|
||||||
events.RegisterListener((&TeamDeletedEvent{}).Name(), &DecreaseTeamCounter{})
|
events.RegisterListener((&TeamDeletedEvent{}).Name(), &DecreaseTeamCounter{})
|
||||||
|
@ -478,37 +476,6 @@ func (s *SendProjectCreatedNotification) Handle(msg *message.Message) (err error
|
||||||
return nil
|
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(msg *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"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hanlde is executed when the event DecreaseNamespaceCounter listens on is fired
|
|
||||||
func (s *DecreaseNamespaceCounter) Handle(msg *message.Message) (err error) {
|
|
||||||
return keyvalue.DecrBy(metrics.NamespaceCountKey, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
///////
|
///////
|
||||||
// Team Events
|
// Team Events
|
||||||
|
|
||||||
|
|
|
@ -44,10 +44,7 @@ func GetTables() []interface{} {
|
||||||
&Team{},
|
&Team{},
|
||||||
&TeamMember{},
|
&TeamMember{},
|
||||||
&TeamProject{},
|
&TeamProject{},
|
||||||
&TeamNamespace{},
|
|
||||||
&Namespace{},
|
|
||||||
&ProjectUser{},
|
&ProjectUser{},
|
||||||
&NamespaceUser{},
|
|
||||||
&TaskAssginee{},
|
&TaskAssginee{},
|
||||||
&Label{},
|
&Label{},
|
||||||
&LabelTask{},
|
&LabelTask{},
|
||||||
|
|
|
@ -37,7 +37,7 @@ import (
|
||||||
type Project struct {
|
type Project struct {
|
||||||
// The unique, numeric id of this project.
|
// The unique, numeric id of this project.
|
||||||
ID int64 `xorm:"bigint autoincr not null unique pk" json:"id" param:"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"`
|
Title string `xorm:"varchar(250) not null" json:"title" valid:"required,runelength(1|250)" minLength:"1" maxLength:"250"`
|
||||||
// The description of the project.
|
// The description of the project.
|
||||||
Description string `xorm:"longtext null" json:"description"`
|
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.
|
// 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"`
|
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"`
|
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.
|
// 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(),
|
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{
|
var FavoriteProjectsPseudoProject = &Project{
|
||||||
ID: -2,
|
ID: -2,
|
||||||
Title: "Favorites",
|
Title: "Favorites",
|
||||||
|
@ -123,7 +123,7 @@ var FavoriteProjectsPseudoProject = &Project{
|
||||||
Updated: time.Now(),
|
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{
|
var SavedFiltersPseudoProject = &Project{
|
||||||
ID: -3,
|
ID: -3,
|
||||||
Title: "Filters",
|
Title: "Filters",
|
||||||
|
@ -267,13 +267,11 @@ func (p *Project) ReadOne(s *xorm.Session, a web.Auth) (err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if !p.IsArchived {
|
||||||
err = p.CheckIsArchived(s)
|
err = p.CheckIsArchived(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !IsErrNamespaceIsArchived(err) && !IsErrProjectIsArchived(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.IsArchived = true
|
p.IsArchived = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -561,7 +559,7 @@ func addProjectDetails(s *xorm.Session, projects map[int64]*Project, a web.Auth)
|
||||||
return
|
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) {
|
func (p *Project) CheckIsArchived(s *xorm.Session) (err error) {
|
||||||
// When creating a new project, we check if the parent is archived
|
// When creating a new project, we check if the parent is archived
|
||||||
if p.ID == 0 {
|
if p.ID == 0 {
|
||||||
|
@ -569,14 +567,14 @@ func (p *Project) CheckIsArchived(s *xorm.Session) (err error) {
|
||||||
return p.CheckIsArchived(s)
|
return p.CheckIsArchived(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
p, err := GetProjectSimpleByID(s, p.ID)
|
project, err := GetProjectSimpleByID(s, p.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: parent project
|
// TODO: parent project
|
||||||
|
|
||||||
if p.IsArchived {
|
if project.IsArchived {
|
||||||
return ErrProjectIsArchived{ProjectID: p.ID}
|
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 {
|
func checkProjectBeforeUpdateOrDelete(s *xorm.Session, project *Project) error {
|
||||||
if project.ParentProjectID < 0 {
|
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
|
// Check if the parent project exists
|
||||||
|
|
|
@ -28,8 +28,8 @@ import (
|
||||||
type ProjectDuplicate struct {
|
type ProjectDuplicate struct {
|
||||||
// The project id of the project to duplicate
|
// The project id of the project to duplicate
|
||||||
ProjectID int64 `json:"-" param:"projectid"`
|
ProjectID int64 `json:"-" param:"projectid"`
|
||||||
// The target namespace ID
|
// The target parent project
|
||||||
NamespaceID int64 `json:"namespace_id,omitempty"`
|
ParentProjectID int64 `json:"parent_project_id,omitempty"`
|
||||||
|
|
||||||
// The copied project
|
// The copied project
|
||||||
Project *Project `json:",omitempty"`
|
Project *Project `json:",omitempty"`
|
||||||
|
@ -47,23 +47,27 @@ func (ld *ProjectDuplicate) CanCreate(s *xorm.Session, a web.Auth) (canCreate bo
|
||||||
return canRead, err
|
return canRead, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Namespace exists + user has write access to is (-> can create new projects)
|
if ld.ParentProjectID == 0 { // no parent project
|
||||||
ld.Project.NamespaceID = ld.NamespaceID
|
return canRead, err
|
||||||
return ld.Project.CanCreate(s, a)
|
}
|
||||||
|
|
||||||
|
// 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
|
// Create duplicates a project
|
||||||
// @Summary Duplicate an existing 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
|
// @tags project
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security JWTKeyAuth
|
// @Security JWTKeyAuth
|
||||||
// @Param projectID path int true "The project ID to duplicate"
|
// @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."
|
// @Success 201 {object} models.ProjectDuplicate "The created project."
|
||||||
// @Failure 400 {object} web.HTTPError "Invalid project duplicate object provided."
|
// @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"
|
// @Failure 500 {object} models.Message "Internal error"
|
||||||
// @Router /projects/{projectID}/duplicate [put]
|
// @Router /projects/{projectID}/duplicate [put]
|
||||||
//
|
//
|
||||||
|
@ -153,7 +157,7 @@ func (ld *ProjectDuplicate) Create(s *xorm.Session, doer web.Auth) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rights / Shares
|
// 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{}
|
users := []*ProjectUser{}
|
||||||
err = s.Where("project_id = ?", ld.ProjectID).Find(&users)
|
err = s.Where("project_id = ?", ld.ProjectID).Find(&users)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package models
|
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
|
type Right int
|
||||||
|
|
||||||
// define unknown right
|
// define unknown right
|
||||||
|
@ -30,7 +30,7 @@ const (
|
||||||
RightRead Right = iota
|
RightRead Right = iota
|
||||||
// Can write in a like projects and tasks. Cannot create new projects.
|
// Can write in a like projects and tasks. Cannot create new projects.
|
||||||
RightWrite
|
RightWrite
|
||||||
// Can manage a project/namespace, can do everything
|
// Can manage a project, can do everything
|
||||||
RightAdmin
|
RightAdmin
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ type SavedFilter struct {
|
||||||
// The user who owns this filter
|
// The user who owns this filter
|
||||||
Owner *user.User `xorm:"-" json:"owner" valid:"-"`
|
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"`
|
IsFavorite bool `xorm:"default false" json:"is_favorite"`
|
||||||
|
|
||||||
// A timestamp when this filter was created. You cannot change this value.
|
// 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 {
|
func (sf *SavedFilter) toProject() *Project {
|
||||||
return &Project{
|
return &Project{
|
||||||
ID: getProjectIDFromSavedFilterID(sf.ID),
|
ID: getProjectIDFromSavedFilterID(sf.ID),
|
||||||
Title: sf.Title,
|
Title: sf.Title,
|
||||||
Description: sf.Description,
|
Description: sf.Description,
|
||||||
IsFavorite: sf.IsFavorite,
|
IsFavorite: sf.IsFavorite,
|
||||||
Created: sf.Created,
|
Created: sf.Created,
|
||||||
Updated: sf.Updated,
|
Updated: sf.Updated,
|
||||||
Owner: sf.Owner,
|
Owner: sf.Owner,
|
||||||
NamespaceID: SavedFiltersPseudoProject.ID,
|
ParentProjectID: SavedFiltersPseudoProject.ID,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,16 +30,15 @@ import (
|
||||||
type SubscriptionEntityType int
|
type SubscriptionEntityType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SubscriptionEntityUnknown = iota
|
SubscriptionEntityUnknown = iota
|
||||||
SubscriptionEntityNamespace
|
SubscriptionEntityNamespace // Kept even though not used anymore since we don't want to manually change all ids
|
||||||
SubscriptionEntityProject
|
SubscriptionEntityProject
|
||||||
SubscriptionEntityTask
|
SubscriptionEntityTask
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
entityNamespace = `namespace`
|
entityProject = `project`
|
||||||
entityProject = `project`
|
entityTask = `task`
|
||||||
entityTask = `task`
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Subscription represents a subscription for an entity
|
// Subscription represents a subscription for an entity
|
||||||
|
@ -70,8 +69,6 @@ func (sb *Subscription) TableName() string {
|
||||||
|
|
||||||
func getEntityTypeFromString(entityType string) SubscriptionEntityType {
|
func getEntityTypeFromString(entityType string) SubscriptionEntityType {
|
||||||
switch entityType {
|
switch entityType {
|
||||||
case entityNamespace:
|
|
||||||
return SubscriptionEntityNamespace
|
|
||||||
case entityProject:
|
case entityProject:
|
||||||
return SubscriptionEntityProject
|
return SubscriptionEntityProject
|
||||||
case entityTask:
|
case entityTask:
|
||||||
|
@ -84,8 +81,6 @@ func getEntityTypeFromString(entityType string) SubscriptionEntityType {
|
||||||
// String returns a human-readable string of an entity
|
// String returns a human-readable string of an entity
|
||||||
func (et SubscriptionEntityType) String() string {
|
func (et SubscriptionEntityType) String() string {
|
||||||
switch et {
|
switch et {
|
||||||
case SubscriptionEntityNamespace:
|
|
||||||
return entityNamespace
|
|
||||||
case SubscriptionEntityProject:
|
case SubscriptionEntityProject:
|
||||||
return entityProject
|
return entityProject
|
||||||
case SubscriptionEntityTask:
|
case SubscriptionEntityTask:
|
||||||
|
@ -96,8 +91,7 @@ func (et SubscriptionEntityType) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (et SubscriptionEntityType) validate() error {
|
func (et SubscriptionEntityType) validate() error {
|
||||||
if et == SubscriptionEntityNamespace ||
|
if et == SubscriptionEntityProject ||
|
||||||
et == SubscriptionEntityProject ||
|
|
||||||
et == SubscriptionEntityTask {
|
et == SubscriptionEntityTask {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -112,7 +106,7 @@ func (et SubscriptionEntityType) validate() error {
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security JWTKeyAuth
|
// @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."
|
// @Param entityID path string true "The numeric id of the entity to subscribe to."
|
||||||
// @Success 201 {object} models.Subscription "The subscription"
|
// @Success 201 {object} models.Subscription "The subscription"
|
||||||
// @Failure 403 {object} web.HTTPError "The user does not have access to subscribe to this entity."
|
// @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
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security JWTKeyAuth
|
// @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."
|
// @Param entityID path string true "The numeric id of the subscribed entity to."
|
||||||
// @Success 200 {object} models.Subscription "The subscription"
|
// @Success 200 {object} models.Subscription "The subscription"
|
||||||
// @Failure 403 {object} web.HTTPError "The user does not have access to subscribe to this entity."
|
// @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) {
|
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 {
|
if entityType == SubscriptionEntityProject {
|
||||||
cond = builder.Or(
|
return builder.And(
|
||||||
builder.And(
|
builder.Eq{"entity_id": entityID},
|
||||||
builder.Eq{"entity_id": entityID},
|
builder.Eq{"entity_type": SubscriptionEntityProject},
|
||||||
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},
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
// TODO: parent?
|
||||||
}
|
}
|
||||||
|
|
||||||
if entityType == SubscriptionEntityTask {
|
if entityType == SubscriptionEntityTask {
|
||||||
cond = builder.Or(
|
return builder.Or(
|
||||||
builder.And(
|
builder.And(
|
||||||
builder.Eq{"entity_id": entityID},
|
builder.Eq{"entity_id": entityID},
|
||||||
builder.Eq{"entity_type": SubscriptionEntityTask},
|
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.And(
|
||||||
builder.Eq{"entity_id": builder.
|
builder.Eq{"entity_id": builder.
|
||||||
Select("project_id").
|
Select("project_id").
|
||||||
|
@ -225,8 +194,8 @@ func getSubscriberCondForEntity(entityType SubscriptionEntityType, entityID int6
|
||||||
|
|
||||||
// GetSubscription returns a matching subscription for an entity and user.
|
// 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
|
// 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
|
// that task, if there is none it will look for a subscription on the project the task belongs to.
|
||||||
// doesn't exist it will check for a subscription for the namespace the project is belonging to.
|
// TODO: check parent projects
|
||||||
func GetSubscription(s *xorm.Session, entityType SubscriptionEntityType, entityID int64, a web.Auth) (subscription *Subscription, err error) {
|
func GetSubscription(s *xorm.Session, entityType SubscriptionEntityType, entityID int64, a web.Auth) (subscription *Subscription, err error) {
|
||||||
subs, err := GetSubscriptions(s, entityType, []int64{entityID}, a)
|
subs, err := GetSubscriptions(s, entityType, []int64{entityID}, a)
|
||||||
if err != nil || len(subs) == 0 {
|
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)
|
sb.EntityType = getEntityTypeFromString(sb.Entity)
|
||||||
|
|
||||||
switch sb.EntityType {
|
switch sb.EntityType {
|
||||||
case SubscriptionEntityNamespace:
|
|
||||||
n := &Namespace{ID: sb.EntityID}
|
|
||||||
can, _, err = n.CanRead(s, a)
|
|
||||||
case SubscriptionEntityProject:
|
case SubscriptionEntityProject:
|
||||||
l := &Project{ID: sb.EntityID}
|
l := &Project{ID: sb.EntityID}
|
||||||
can, _, err = l.CanRead(s, a)
|
can, _, err = l.CanRead(s, a)
|
||||||
|
|
|
@ -237,24 +237,6 @@ func getNativeValueForTaskField(fieldName string, comparator taskFilterComparato
|
||||||
|
|
||||||
realFieldName := strings.ReplaceAll(strcase.ToCamel(fieldName), "Id", "ID")
|
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" {
|
if realFieldName == "Assignees" {
|
||||||
vals := strings.Split(value, ",")
|
vals := strings.Split(value, ",")
|
||||||
valueSlice := append([]string{}, vals...)
|
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
|
// Create is the handler to create a team
|
||||||
// @Summary Creates a new 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
|
// @tags team
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
|
@ -307,12 +307,6 @@ func (t *Team) Delete(s *xorm.Session, a web.Auth) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete team <-> namespace relations
|
|
||||||
_, err = s.Where("team_id = ?", t.ID).Delete(&TeamNamespace{})
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete team <-> projects relations
|
// Delete team <-> projects relations
|
||||||
_, err = s.Where("team_id = ?", t.ID).Delete(&TeamProject{})
|
_, err = s.Where("team_id = ?", t.ID).Delete(&TeamProject{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -48,7 +48,6 @@ func SetupTests() {
|
||||||
"labels",
|
"labels",
|
||||||
"link_shares",
|
"link_shares",
|
||||||
"projects",
|
"projects",
|
||||||
"namespaces",
|
|
||||||
"task_assignees",
|
"task_assignees",
|
||||||
"task_attachments",
|
"task_attachments",
|
||||||
"task_comments",
|
"task_comments",
|
||||||
|
@ -57,12 +56,10 @@ func SetupTests() {
|
||||||
"tasks",
|
"tasks",
|
||||||
"team_projects",
|
"team_projects",
|
||||||
"team_members",
|
"team_members",
|
||||||
"team_namespaces",
|
|
||||||
"teams",
|
"teams",
|
||||||
"users",
|
"users",
|
||||||
"user_tokens",
|
"user_tokens",
|
||||||
"users_projects",
|
"users_projects",
|
||||||
"users_namespaces",
|
|
||||||
"buckets",
|
"buckets",
|
||||||
"saved_filters",
|
"saved_filters",
|
||||||
"subscriptions",
|
"subscriptions",
|
||||||
|
|
|
@ -22,14 +22,11 @@ import (
|
||||||
"xorm.io/xorm"
|
"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 {
|
type ProjectUIDs struct {
|
||||||
ProjectOwnerID int64 `xorm:"projectOwner"`
|
ProjectOwnerID int64 `xorm:"projectOwner"`
|
||||||
NamespaceUserID int64 `xorm:"unID"`
|
ProjectUserID int64 `xorm:"ulID"`
|
||||||
ProjectUserID int64 `xorm:"ulID"`
|
TeamProjectUserID int64 `xorm:"tlUID"`
|
||||||
NamespaceOwnerUserID int64 `xorm:"nOwner"`
|
|
||||||
TeamNamespaceUserID int64 `xorm:"tnUID"`
|
|
||||||
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
|
// 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.
|
err = s.
|
||||||
Select(`l.owner_id as projectOwner,
|
Select(`l.owner_id as projectOwner,
|
||||||
un.user_id as unID,
|
|
||||||
ul.user_id as ulID,
|
ul.user_id as ulID,
|
||||||
n.owner_id as nOwner,
|
|
||||||
tm.user_id as tnUID,
|
|
||||||
tm2.user_id as tlUID`).
|
tm2.user_id as tlUID`).
|
||||||
Table("projects").
|
Table("projects").
|
||||||
Alias("l").
|
Alias("l").
|
||||||
// User stuff
|
// 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{"users_projects", "ul"}, "ul.project_id = l.id").
|
||||||
Join("LEFT", []string{"namespaces", "n"}, "n.id = l.namespace_id").
|
|
||||||
// Team stuff
|
// 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_projects", "tl"}, "l.id = tl.project_id").
|
||||||
Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id").
|
Join("LEFT", []string{"team_members", "tm2"}, "tm2.team_id = tl.team_id").
|
||||||
// The actual condition
|
// The actual condition
|
||||||
Where(
|
Where(
|
||||||
builder.Or(
|
builder.Or(
|
||||||
builder.Or(builder.Eq{"ul.right": RightRead}),
|
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{"tl.right": RightRead}),
|
||||||
builder.Or(builder.Eq{"tn.right": RightRead}),
|
|
||||||
|
|
||||||
builder.Or(builder.Eq{"ul.right": RightWrite}),
|
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{"tl.right": RightWrite}),
|
||||||
builder.Or(builder.Eq{"tn.right": RightWrite}),
|
|
||||||
|
|
||||||
builder.Or(builder.Eq{"ul.right": RightAdmin}),
|
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{"tl.right": RightAdmin}),
|
||||||
builder.Or(builder.Eq{"tn.right": RightAdmin}),
|
|
||||||
),
|
),
|
||||||
builder.Eq{"l.id": l.ID},
|
builder.Eq{"l.id": l.ID},
|
||||||
).
|
).
|
||||||
|
@ -85,10 +69,7 @@ func ListUsersFromProject(s *xorm.Session, l *Project, search string) (users []*
|
||||||
uidmap[l.OwnerID] = true
|
uidmap[l.OwnerID] = true
|
||||||
for _, u := range userids {
|
for _, u := range userids {
|
||||||
uidmap[u.ProjectUserID] = true
|
uidmap[u.ProjectUserID] = true
|
||||||
uidmap[u.NamespaceOwnerUserID] = true
|
|
||||||
uidmap[u.NamespaceUserID] = true
|
|
||||||
uidmap[u.TeamProjectUserID] = true
|
uidmap[u.TeamProjectUserID] = true
|
||||||
uidmap[u.TeamNamespaceUserID] = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uids := make([]int64, 0, len(uidmap))
|
uids := make([]int64, 0, len(uidmap))
|
||||||
|
|
|
@ -244,7 +244,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)
|
err = models.CreateNewProjectForUser(s, u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -62,7 +62,7 @@ func RegisterUser(c echo.Context) error {
|
||||||
return handler.HandleHTTPError(err, c)
|
return handler.HandleHTTPError(err, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add its namespace
|
// Create their initial project
|
||||||
err = models.CreateNewProjectForUser(s, newUser)
|
err = models.CreateNewProjectForUser(s, newUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_ = s.Rollback()
|
_ = s.Rollback()
|
||||||
|
|
|
@ -51,10 +51,6 @@ func setupMetrics(a *echo.Group) {
|
||||||
metrics.UserCountKey,
|
metrics.UserCountKey,
|
||||||
user.User{},
|
user.User{},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
metrics.NamespaceCountKey,
|
|
||||||
models.Namespace{},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
metrics.TaskCountKey,
|
metrics.TaskCountKey,
|
||||||
models.Task{},
|
models.Task{},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user