feat(projects): cleanup namespace leftovers

This commit is contained in:
kolaente 2022-12-29 17:51:55 +01:00
parent c244a0f145
commit 92f0a50996
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
21 changed files with 127 additions and 596 deletions

View File

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

View File

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

View File

@ -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",

View File

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

View File

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

View File

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

View File

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

View File

@ -44,10 +44,7 @@ func GetTables() []interface{} {
&Team{}, &Team{},
&TeamMember{}, &TeamMember{},
&TeamProject{}, &TeamProject{},
&TeamNamespace{},
&Namespace{},
&ProjectUser{}, &ProjectUser{},
&NamespaceUser{},
&TaskAssginee{}, &TaskAssginee{},
&Label{}, &Label{},
&LabelTask{}, &LabelTask{},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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...)

View File

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

View File

@ -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",

View File

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

View File

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

View File

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

View File

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